How to Use Skills in Agentic Flutter Development: A Handbook for Devs — Opportunihub
Course Remote

How to Use Skills in Agentic Flutter Development: A Handbook for Devs

Atuoha Anthony · Remote

At a glance

Type
Course
Organisation
Atuoha Anthony
Location
Remote
Work mode
Remote
Deadline
Rolling / not stated
Posted
3 Sep 2026

About this course

<p>One of the biggest misconceptions about AI-assisted development is that using AI means giving up the engineering experience you've built over the years. It doesn't.</p> <p>You can take the architecture patterns you've learned, the mistakes you've made, the conventions your team follows, and the standards you've developed as a Flutter engineer and teach them to your AI coding agent through agent skills. That means you don't have to choose between your experience and AI. You can bring both together.</p> <p>But almost every Flutter developer feels a specific frustration the first time they use an AI coding agent on a real project.</p> <p>You ask the agent to build a profile screen. It produces something that works. But instead of creating a clean, reusable <code>ProfileCard</code> widget in your <code>widgets/</code> folder, it writes a <code>_buildProfileCard()</code> private method buried inside the screen file.</p> <p>Instead of separating concerns and placing the <code>StatefulWidget</code> and its state where your carefully designed file structure expects them, it appends both to the bottom of a file that already has ten classes.</p> <p>The data model uses <code>Map&lt;String, dynamic&gt;</code> instead of your <code>freezed</code>-annotated classes. The imports skip your barrel files and reach directly into internal package paths. The theming ignores your design tokens and uses hardcoded hex values. The error handling uses raw strings instead of your typed failure hierarchy. The state management is Provider when your team uses Bloc.</p> <p>None of this is wrong in an absolute sense. The agent didn't make mistakes because it's bad at Dart. It made mistakes because it doesn't know how your team writes Flutter code.</p> <p>This is the problem that agent skills were built to solve.</p> <p>Agent skills are structured Markdown files that teach an AI agent the "how" of a specific task, not just the "what." When an agent picks up a skill before generating code, it's equipped with your team's conventions, your architectural patterns, your file organization rules, your naming standards, and your quality expectations. The result is code that belongs in your project.</p> <p>The Flutter team maintains an official repository of skills at <code>github.com/flutter/agent-plugins</code>, and the Dart team maintains a complementary set at <code>github.com/dart-lang/skills</code>. Together they cover responsive layouts, declarative routing, JSON serialization, unit testing, static analysis, package dependency resolution, pattern matching, and more.</p> <p>But the most powerful skills are the ones you write yourself, the ones that encode your specific experiences as an engineer, your team's specific mistakes, and your project's specific patterns. A skill you write from your own production experience is worth ten generic ones, because it prevents the exact mistakes your team has actually made in the exact codebase your team maintains.</p> <p>Skills work across every major AI coding agent. Whether your team uses Claude Code, Antigravity, OpenAI Codex, Cursor, GitHub Copilot CLI, or any other compatible agent, skills follow a universal standard. Write the skill once, and it works everywhere.</p> <p>This handbook covers everything: what skills are, how they work internally, how to install the official Flutter and Dart skills, how to configure skills for each major agent, how to read and understand an existing skill deeply, and most importantly, how to write your own skills that genuinely improve AI output on your specific codebase.</p> <p>It also covers the essential skills every Flutter team should have, the Dart skills every developer benefits from, and the advanced patterns that make skills compounding over time.</p> <p>By the end, you won't just know how to use skills. You'll write them with the same intentionality you bring to writing clean Flutter code, and you'll understand why doing so is one of the highest-leverage investments you can make in your team's engineering quality.</p> <h2 id="heading-table-of-contents">Table of Contents</h2> <ul> <li><p><a href="#heading-prerequisites">Prerequisites</a></p> </li> <li><p><a href="#heading-what-are-agent-skills">What Are Agent Skills?</a></p> </li> <li><p><a href="#heading-the-problem-why-ai-agents-get-flutter-wrong">The Problem: Why AI Agents Get Flutter Wrong</a></p> </li> <li><p><a href="#heading-how-skills-work-progressive-disclosure">How Skills Work: Progressive Disclosure</a></p> </li> <li><p><a href="#heading-the-anatomy-of-a-skill-file">The Anatomy of a Skill File</a></p> </li> <li><p><a href="#heading-installing-official-flutter-and-dart-skills">Installing Official Flutter and Dart Skills</a></p> </li> <li><p><a href="#heading-using-skills-with-claude-code">Using Skills with Claude Code</a></p> </li> <li><p><a href="#heading-using-skills-with-antigravity">Using Skills with Antigravity</a></p> </li> <li><p><a href="#heading-using-skills-with-openai-codex">Using Skills with OpenAI Codex</a></p> </li> <li><p><a href="#heading-using-skills-with-cursor">Using Skills with Cursor</a></p> </li> <li><p><a href="#heading-using-skills-with-other-agents">Using Skills with Other Agents</a></p> </li> <li><p><a href="#heading-the-official-flutter-skills-a-deep-dive">The Official Flutter Skills: A Deep Dive</a></p> </li> <li><p><a href="#heading-the-official-dart-skills-a-deep-dive">The Official Dart Skills: A Deep Dive</a></p> </li> <li><p><a href="#heading-the-flutter-file-organization-skill-a-complete-walkthrough">The flutter-file-organization Skill: A Complete Walkthrough</a></p> </li> <li><p><a href="#heading-writing-your-own-skills-the-complete-guide">Writing Your Own Skills: The Complete Guide</a></p> </li> <li><p><a href="#heading-essential-flutter-skills-every-team-should-have">Essential Flutter Skills Every Team Should Have</a></p> </li> <li><p><a href="#heading-essential-dart-skills-every-developer-should-write">Essential Dart Skills Every Developer Should Write</a></p> </li> <li><p><a href="#heading-skills-for-architecture-and-large-codebases">Skills for Architecture and Large Codebases</a></p> </li> <li><p><a href="#heading-advanced-skill-patterns">Advanced Skill Patterns</a></p> </li> <li><p><a href="#heading-package-level-skills-teaching-the-agent-your-libraries">Package-Level Skills: Teaching the Agent Your Libraries</a></p> </li> <li><p><a href="#heading-skills-vs-rules-vs-mcp-knowing-the-difference">Skills vs Rules vs MCP: Knowing the Difference</a></p> </li> <li><p><a href="#heading-organizing-skills-in-a-team">Organizing Skills in a Team</a></p> </li> <li><p><a href="#heading-best-practices-for-writing-skills">Best Practices for Writing Skills</a></p> </li> <li><p><a href="#heading-common-mistakes-when-writing-skills">Common Mistakes When Writing Skills</a></p> </li> <li><p><a href="#heading-conclusion">Conclusion</a></p> </li> <li><p><a href="#heading-references">References</a></p> </li> </ul> <h2 id="heading-prerequisites">Prerequisites</h2> <p>Before working through this guide, you should have the following in place.</p> <h3 id="heading-1-flutter-and-dart-proficiency">1. Flutter and Dart proficiency</h3> <p>You should be comfortable building multi-screen Flutter apps, working with state management patterns, and following basic clean architecture principles. You don't need to be a senior engineer, but the skill examples in this guide assume you know what a <code>StatefulWidget</code> is, what a repository pattern looks like, why sealed classes matter, and what <code>json_serializable</code> generates.</p> <h3 id="heading-2-a-working-ai-coding-agent">2. A working AI coding agent</h3> <p>Skills work with agents including Claude Code, Antigravity, OpenAI Codex, GitHub Copilot CLI, Cursor, and others. You need at least one of these installed and working. This guide covers agent-specific setup for all of them.</p> <h3 id="heading-3-nodejs-installed">3. Node.js installed</h3> <p>The <code>skills</code> CLI tool (used to install official skills) is distributed through npm. Run <code>node -v</code> to check. If Node.js isn't installed, download it from <a href="https://nodejs.org">nodejs.org</a>.</p> <h3 id="heading-4-a-flutter-or-dart-project-to-work-with">4. A Flutter or Dart project to work with</h3> <p>The examples and skill exercises in this guide work best when applied to a real project rather than followed abstractly.</p> <h3 id="heading-5-basic-markdown-familiarity">5. Basic Markdown familiarity</h3> <p>Skills are written in Markdown. You should know what a heading is (<code>##</code>), what a code block looks like (triple backticks), and what a YAML frontmatter block looks like (the <code>---</code> enclosed block at the top of a file).</p> <p>You don't need any special tools beyond these. Skills are plain text files that live in a folder in your project. There's nothing to build, compile, or install beyond the initial CLI command.</p> <h2 id="heading-what-are-agent-skills">What Are Agent Skills?</h2> <p>Think about the difference between hiring a developer who knows Dart and hiring a developer who has worked on Flutter projects similar to yours for two years.</p> <p>Both can write working Flutter code. But the experienced one knows things that aren't in any documentation: that your team always extracts widget sections into their own files rather than using private build methods, that you use a specific pattern for handling loading states, that your Bloc events are named as past-tense verbs, that you never use <code>BuildContext</code> inside async gaps without checking <code>mounted</code>, and that your team uses <code>fpdart</code> for <code>Either</code> types instead of throwing exceptions across layer boundaries.</p> <p>A skill is how you give that experienced-developer knowledge to an AI agent. It's a document that describes not just what to do but how to do it, what to avoid, and why the rules exist.</p> <p>Formally, agent skills provide a standardized way to give your AI agent a set of task-oriented blueprints to follow. By giving the agent actual domain expertise and repeatable workflows, you drastically reduce mistakes and can enforce consistent patterns.</p> <p>The key word is task-oriented. A skill isn't a style guide. It's a set of instructions tied to a specific category of work.</p> <h3 id="heading-the-universal-standard">The Universal Standard</h3> <p>Skills follow a specification maintained at <a href="https://agentskills.io">agentskills.io</a>. This specification defines the file format (Markdown with YAML frontmatter), the directory location (<code>.agents/skills/</code>), and the naming conventions.</p> <p>Because the specification is universal, the same skill files work across Claude Code, Cursor, Antigravity, Codex, and any other agent that follows the standard.</p> <p>This portability matters for teams. You don't need to write separate skills for each agent. You write one skill, commit it to your repository, and every agent your team uses benefits from it immediately.</p> <h3 id="heading-where-skills-live">Where Skills Live</h3> <p>Skills live in the <code>.agents/skills/</code> directory of your project workspace. This is the standard location that all compatible agents discover automatically when they start working on a task.</p> <pre><code class="language-plaintext">your_flutter_project/ .agents/ skills/ flutter-file-organization.md flutter-state-management-bloc.md flutter-testing.md flutter-theming.md flutter-error-handling.md flutter-navigation.md flutter-feature-architecture.md dart-unit-testing.md dart-static-analysis.md dart-pattern-matching.md lib/ android/ ios/ pubspec.yaml </code></pre> <p><code>.agents/skills/</code> is the convention established by the agent skills specification. When an agent starts a session on your project, it discovers this directory, indexes the skill files, reads their metadata to understand what capabilities are available, and loads full skill content only when a task matches a skill's description.</p> <h3 id="heading-what-makes-skills-different-from-system-prompts-or-rules">What Makes Skills Different from System Prompts or Rules</h3> <p>A one-time prompt tells the agent what you want right now, in this session. An AI rules file (like <code>.cursorrules</code> or <code>CLAUDE.md</code>) tells the agent project-wide facts that apply to every task. A skill teaches the agent how to perform a specific category of work correctly across all future requests, loaded only when relevant.</p> <p>When you write a skill for Flutter file organization, you don't need to explain your conventions in the chat every session. Every time you or a teammate asks the agent to create, split, or refactor a Flutter file, the skill loads automatically and provides the same quality guidance. When a new developer joins the team and starts using an AI agent, they get the benefit of every skill the team has written from day one, without needing to be taught the team's standards manually.</p> <h2 id="heading-the-problem-why-ai-agents-get-flutter-wrong">The Problem: Why AI Agents Get Flutter Wrong</h2> <p>To understand why skills are necessary, you need to understand the specific and predictable ways AI agents fail at Flutter and Dart without them. These failures aren't random. They trace to a handful of root causes that skills are designed to address.</p> <h3 id="heading-the-training-data-problem">The Training Data Problem</h3> <p>An AI agent has knowledge of Dart and Flutter from its training data. That training data includes millions of lines of Flutter code from public repositories, documentation, tutorials, and forum answers. It includes old patterns (pre-null-safety Dart), bad patterns (God-class widgets), and patterns that are correct in isolation but wrong for a specific team's standards.</p> <p>When an agent generates code without a skill, it draws on all of that mixed training data. It might generate code in the style of a 2021 tutorial that uses <code>setState</code> everywhere, or in the style of a repository that uses <code>ChangeNotifier</code> when your team uses Bloc, or it might use <code>Navigator.push</code> when your team carefully uses GoRouter for deep-linking support.</p> <img src="https://cdn.hashnode.com/uploads/covers/63a47b24490dd1c9cd9c32ff/4caaf86d-01e3-465d-b3f8-c372ad31b80c.png" alt="A two-part diagram comparing an AI agent without and with team skills. The top section shows broad training data flowing into patterns that may not fit the team. The bottom section shows the same training data combined with focused team rules for file organization, BLoC state management, error handling, theming, and testing, resulting in output that fits the existing codebase." style="display:block;margin:0 auto" width="600" height="400" loading="lazy"> <p>Skills don't replace the agent's existing knowledge. They give it a clear engineering context. Without skills, the agent chooses from a broad mix of patterns with varying quality. With team-defined skills, those patterns are constrained by the project's architecture, conventions, and standards, making the resulting code more consistent with the existing codebase.</p> <h3 id="heading-the-most-common-flutter-specific-failures">The Most Common Flutter-Specific Failures</h3> <h4 id="heading-1-private-build-methods-instead-of-extracted-widgets">1. Private build methods instead of extracted widgets.</h4> <p>An agent asked to build a complex screen nests private methods like <code>_buildHeader()</code>, <code>_buildStatsList()</code>, and <code>_buildActionBar()</code> inside the screen class. This is valid Dart but architecturally harmful: these sections should be separate, testable, reusable widget classes in a <code>widgets/</code> folder.</p> <h4 id="heading-2-separating-statefulwidget-from-state">2. Separating StatefulWidget from State.</h4> <p>When splitting a large file, an agent may move the <code>StatefulWidget</code> class to one file and the <code>State&lt;T&gt;</code> class to another. This breaks a fundamental Flutter compilation constraint. The two must always live in the same file.</p> <h4 id="heading-3-ignoring-your-state-management-choice">3. Ignoring your state management choice.</h4> <p>Without knowing your state management preference, the agent picks whatever pattern it finds most frequently in its training data. One session it generates Bloc. The next it generates Provider. The next it uses <code>setState</code>. All in the same codebase.</p> <h4 id="heading-4-using-map-instead-of-typed-models">4. Using Map instead of typed models.</h4> <p>Without knowing your serialization conventions, an agent defaults to <code>Map&lt;String, dynamic&gt;</code>. If your team uses <code>freezed</code> and <code>json_serializable</code>, every generated model needs to be completely rewritten.</p> <h4 id="heading-5-hardcoded-visual-values">5. Hardcoded visual values.</h4> <p>Agents default to literal values: <code>Color(0xFF6750A4)</code>, <code>EdgeInsets.all(16)</code>, and <code>BorderRadius.circular(8)</code>. If your project has a design system with theme extensions and spacing constants, the agent ignores it entirely.</p> <h4 id="heading-6-inline-comments-everywhere">6. Inline comments everywhere.</h4> <p>Many teams specifically avoid code comments in favor of self-documenting code with descriptive names. Agents default to adding explanatory comments because most training data includes them, requiring cleanup on every review.</p> <h4 id="heading-7-wrong-import-paths">7. Wrong import paths.</h4> <p>An agent may import from internal package paths (<code>package:myapp/src/internal/models/user.dart</code>) instead of going through your barrel files (<code>package:myapp/features/profile/profile.dart</code>), creating invisible coupling to internal APIs that should be hidden.</p> <h4 id="heading-8-raw-exception-handling">8. Raw exception handling.</h4> <p>Without knowing your error architecture, agents use <code>try-catch</code> with raw <code>Exception</code> objects everywhere, ignoring your team's typed failure hierarchy and making error handling inconsistent across the codebase.</p> <h2 id="heading-how-skills-work-progressive-disclosure">How Skills Work: Progressive Disclosure</h2> <p>The mechanism behind skills is elegant and efficient. Instead of loading every instruction into the context window upfront, the agent only reads the metadata first. It pulls in the heavy, detailed instructions only when it actually needs them for the task at hand.</p> <p>The Flutter documentation describes this as "progressive disclosure," analogous to deferred loading in Flutter itself.</p> <p>This design solves a real problem. An AI agent's context window isn't infinite. If every skill loaded its full content for every task, the agent would be burning context budget on irrelevant information. A navigation skill doesn't need to be in context when you're asking the agent to write unit tests. A testing skill doesn't need to be in context when you're setting up routing.</p> <img src="https://cdn.hashnode.com/uploads/covers/63a47b24490dd1c9cd9c32ff/a65999e8-d2e8-4468-9bcc-d82dce2da392.png" alt="A two-phase diagram explaining progressive disclosure for AI agent skills. Phase 1 shows the agent reading only the frontmatter from every skill file, keeping context usage lightweight. Phase 2 shows the agent matching a user request to relevant skills, loading their full content while excluding unrelated skills. The result is relevant expertise without unnecessary context usage." style="display:block;margin:0 auto" width="600" height="400" loading="lazy"> <p>Progressive disclosure keeps the agent's context focused. First, the agent indexes the lightweight metadata of all available skills. When a task arrives, it uses those descriptions to identify which skills are relevant and loads only their full instructions. Unrelated skills remain unloaded, reducing context usage while giving the agent the detailed guidance needed for the task.</p> <p>This progressive disclosure model means you can have many skills in your project without worrying about context overflow. Having twenty skills isn't twenty times more expensive than having one skill. Only the relevant subset is ever loaded for any given task.</p> <h2 id="heading-the-anatomy-of-a-skill-file">The Anatomy of a Skill File</h2> <p>Every skill follows a specific structure. Understanding this structure deeply is the prerequisite for writing effective skills.</p> <pre><code class="language-markdown">--- name: skill-name-in-kebab-case description: A clear, specific description that answers: what does this skill cover, when should it be applied, and what trigger words indicate this task needs this skill? This is the ONLY part the agent reads when deciding whether this skill is relevant. aliases: [alternative-name, another-name] sources: [chat, code] --- # Skill Title Brief introduction of what this skill covers and why it exists. ## First Major Section Content with specific, actionable rules. ## Second Major Section More rules, examples, counterexamples. ## Code Examples Concrete code demonstrating the patterns. </code></pre> <h3 id="heading-the-frontmatter-block-in-detail">The Frontmatter Block in Detail</h3> <pre><code class="language-markdown">--- name: flutter-file-organization description: Organize and split Flutter/Dart files while preserving StatefulWidget and State relationships. Use when creating, refactoring, splitting, or reorganizing Dart files and classes. Applies whenever a new screen, widget, model, or Bloc file is being created or an existing file is being restructured. aliases: [flutter-files, dart-organization] sources: [chat, code] --- </code></pre> <p><code>name</code> is the unique identifier for this skill across your project. It follows kebab-case convention (lowercase words separated by hyphens) and conventionally starts with the platform or domain (<code>flutter-</code>, <code>dart-</code>, <code>react-</code>, and so on). The name is used by the agent when referencing the skill in its reasoning and by the CLI when managing skills.</p> <p><code>description</code> is the most critical field in the entire file. It's the only field the agent reads during the lightweight Phase 1 indexing. A poorly written description means a perfectly written skill body never gets loaded. The description should answer three questions: what does this skill cover, when should it be triggered, and what are the specific trigger words or phrases that indicate this skill is relevant? Notice in the example how the description includes "Use when creating, refactoring, splitting, or reorganizing" along with a comprehensive list of file types. Each of those phrases is a potential trigger that helps the agent match task descriptions to this skill.</p> <p><code>aliases</code> provides alternative names for the skill that the agent can use to reference it. These are optional but useful when the skill might be called different things in different contexts.</p> <p><code>sources</code> indicates where this skill comes from. For custom team skills, this is typically <code>[chat]</code>. For skills coming from package authors, this might include <code>[package]</code>.</p> <h3 id="heading-the-skill-body-structure">The Skill Body Structure</h3> <p>The skill body is pure Markdown with a specific structural discipline that makes it most effective for agent consumption:</p> <pre><code class="language-markdown"># Title Section (h1) Brief context-setting paragraph. What problem does this skill solve? Why does it exist? Keep this under three sentences. ## Core Rules (h2 sections) Numbered or bulleted lists of specific, verifiable rules. Each rule should be independently actionable. ## Named Sub-Pattern (h2 sections) More specific guidance for a particular sub-domain of the skill. Lead with the rule, then show the wrong pattern, then show the right pattern. ## Code Example (h2 sections) Complete, runnable code that demonstrates the most important patterns. Always show both wrong and right versions for patterns that agents commonly get wrong. </code></pre> <p>Agents navigate heading structure to understand skill organization. Clear <code>##</code> headings that name the sub-topic they cover help the agent find the specific section relevant to its current sub-task within a larger request.</p> <h2 id="heading-installing-official-flutter-and-dart-skills">Installing Official Flutter and Dart Skills</h2> <p>The Flutter and Dart teams maintain official skill repositories that represent years of accumulated knowledge about best practices in the ecosystem. These are your starting point.</p> <h3 id="heading-installing-flutter-skills">Installing Flutter Skills</h3> <pre><code class="language-bash">npx skills add flutter/agent-plugins --skill '*' --agent universal --yes </code></pre> <p><code>npx skills add</code> runs the <code>skills</code> CLI tool via npm without requiring a permanent installation. <code>flutter/agent-plugins</code> is the GitHub repository path where the official Flutter skills are maintained by the Flutter team. <code>--skill '*'</code> is a wildcard that installs all available skills from the repository rather than selecting specific ones. <code>--agent universal</code> places the skills in the <code>.agents/skills/</code> directory, which is the universal location all compatible agents look in. <code>--yes</code> skips the interactive confirmation prompt, making this command safe to put in project setup scripts or Makefiles.</p> <p>After running this command, your project gains skills for responsive layouts, declarative routing with GoRouter, JSON serialization with <code>json_serializable</code>, integration testing setup, widget preview setup, widget testing, architecture best practices with BLoC and Clean Architecture, Bloc state management, Bloc forms, and more.</p> <h3 id="heading-installing-dart-skills">Installing Dart Skills</h3> <pre><code class="language-bash">npx skills add dart-lang/skills --skill '*' --agent universal --yes </code></pre> <p>The Dart team maintains a complementary set of skills focused on the Dart language itself, independent of Flutter's widget system. These skills are valuable for both Flutter apps and pure Dart projects like CLI tools, backend services, and packages.</p> <p>The official Dart skills cover unit test generation, static analysis configuration, package dependency management, pattern matching and sealed classes, CLI application building, test coverage collection and analysis, runtime error fixing with the LSP, mock generation with Mockito, FFI bindings with ffigen, native assets for C and C++ integration, Dart memory optimization, and migrating from old test assertion styles to modern <code>package:checks</code>.</p> <h3 id="heading-installing-both-at-once">Installing Both at Once</h3> <pre><code class="language-bash">npx skills add flutter/agent-plugins dart-lang/skills --skill '*' --agent universal --yes </code></pre> <p>Listing both repository names in a single command installs them together and runs dependency resolution once, which is slightly faster than two separate commands. This is the recommended approach for a new Flutter project setup.</p> <h3 id="heading-installing-skills-from-your-pubspec-dependencies">Installing Skills from Your pubspec Dependencies</h3> <p>One of the most powerful aspects of the skills ecosystem is that package authors can ship skills alongside their packages. The <code>skills</code> CLI (available as a Dart package) can discover and install skills from all packages in your dependency tree:</p> <pre><code class="language-bash">dart pub global activate skills skills get </code></pre> <p><code>dart pub global activate skills</code> installs the <code>skills</code> Dart CLI tool globally on your machine. <code>skills get</code> reads your <code>pubspec.yaml</code> and <code>pubspec.lock</code>, finds every package in your dependency tree that ships a <code>skills/</code> directory, and installs those skills into your project's <code>.agents/skills/</code> directory automatically.</p> <p>When you add a package to your project and run <code>skills get</code>, your agent immediately knows how to use that package correctly according to the package author's own instructions. This is a fundamental shift: instead of the agent guessing how a package works, the package author directly equips the agent with the correct usage patterns.</p> <pre><code class="language-bash"># Update skills whenever your dependencies change flutter pub get skills get </code></pre> <p>Running <code>flutter pub get</code> updates your dependencies. Running <code>skills get</code> immediately after updates the skills to match. Making this a two-step habit ensures your agent always has current skills for your current dependencies.</p> <h3 id="heading-verifying-installed-skills">Verifying Installed Skills</h3> <pre><code class="language-bash">ls -la .agents/skills/ </code></pre> <p><code>ls -la .agents/skills/</code> lists all installed skill files with details. You should see <code>.md</code> files named after each installed skill. The <code>-la</code> flags show hidden files and detailed information including file sizes and modification dates.</p> <p>Once installed, test your agent's awareness of the skills:</p> <pre><code class="language-plaintext">Which of my installed skills can help me with creating a new feature screen? </code></pre> <p>The agent responds with the skills it found that are relevant to that task, confirming they're loaded and indexed correctly. This is a good first test whenever you add skills to a project.</p> <h2 id="heading-using-skills-with-claude-code">Using Skills with Claude Code</h2> <p>Claude Code is Anthropic's agentic coding assistant that runs in your terminal. It's one of the most powerful agents for complex, multi-step Flutter development tasks and has excellent support for the skills standard.</p> <h3 id="heading-installing-the-flutter-plugin-for-claude-code">Installing the Flutter Plugin for Claude Code</h3> <p>The recommended approach for Claude Code is installing the full Flutter plugin, which bundles skills with MCP server configuration:</p> <pre><code class="language-bash">claude mcp add flutter-mcp -- dart pub global run dart_mcp_server npx skills add flutter/agent-plugins --skill '*' --agent claude-code --yes npx skills add dart-lang/skills --skill '*' --agent claude-code --yes </code></pre> <p><code>claude mcp add flutter-mcp</code> registers the Dart MCP server with Claude Code. The MCP server gives Claude Code access to Flutter documentation, pub.dev package information, and Dart tooling directly without making web searches. <code>--agent claude-code</code> in the <code>skills add</code> command places skills in the Claude Code specific location if it differs from the universal <code>.agents/skills/</code> directory, though Claude Code also reads from the universal location.</p> <h3 id="heading-claude-code-skills-directory">Claude Code Skills Directory</h3> <p>Claude Code reads skills from <code>.agents/skills/</code> (the universal location) automatically. It also reads from <code>.claude/skills/</code> if you prefer to keep Claude-specific skills separate from universal skills.</p> <pre><code class="language-plaintext">your_project/ .agents/ skills/ flutter-file-organization.md &lt;- universal, works everywhere flutter-bloc-state-management.md .claude/ skills/ claude-specific-workflow.md &lt;- Claude Code only CLAUDE.md &lt;- Claude Code rules file </code></pre> <h3 id="heading-claude-code-rules-vs-skills">Claude Code Rules vs Skills</h3> <p>Claude Code uses a <code>CLAUDE.md</code> file at the project root (or in <code>.claude/</code>) as a rules file: project-wide instructions that are always in context regardless of task.</p> <p>Skills are loaded progressively. Use <code>CLAUDE.md</code> for project facts (what package this is, what SDK version, or what state management library is installed). Use skills for task-specific expertise (how to implement Bloc, how to organize files, or how to write tests).</p> <pre><code class="language-markdown"># CLAUDE.md example This is a Flutter app called Kopa, a personal budgeting tool. ## Technical Stack - Flutter 3.47 with Dart 3.10 - State management: flutter_bloc ^9.0.0 - Navigation: go_router ^14.0.0 - Data layer: firebase_ai ^2.0.0 for AI features - Serialization: freezed + json_serializable - Testing: bloc_test, mocktail ## Package Name com.example.kopa ## Minimum SDK Android API 24, iOS 15 ## Project Structure Feature-first with clean architecture layers. See the flutter-feature-architecture skill for full structure details. </code></pre> <p><code>CLAUDE.md</code> contains facts about the project that never change between tasks: the app name, the packages in use, the SDK versions, and the minimum platform targets. Skills contain the expertise for how to work with those packages and structure that code correctly.</p> <h3 id="heading-using-skills-in-a-claude-code-session">Using Skills in a Claude Code Session</h3> <p>Once skills are installed, Claude Code uses them automatically. You don't need to invoke them manually. When you ask:</p> <pre><code class="language-plaintext">Create a UserProfile feature with Bloc state management, a repository that fetches from Firestore, and a screen that shows loading, data, and error states. </code></pre> <p>Claude Code detects that this request involves multiple skill domains (feature architecture, Bloc state management, file organization, and potentially theming and error handling), loads the relevant skill files, and generates code that follows all of your team's conventions simultaneously.</p> <p>You can also be explicit:</p> <pre><code class="language-plaintext">Using the flutter-bloc-state-management skill, implement the CartBloc for the shopping cart feature. </code></pre> <p>Naming the skill explicitly tells Claude Code to load that specific skill regardless of whether it would have detected the need automatically.</p> <h2 id="heading-using-skills-with-antigravity">Using Skills with Antigravity</h2> <p>Antigravity is Google's AI coding assistant, deeply integrated into the Flutter ecosystem and developed alongside the Flutter team. It has first-class support for agent skills and is one of the agents most thoroughly tested with the official Flutter skills.</p> <h3 id="heading-installing-the-flutter-plugin-for-antigravity">Installing the Flutter Plugin for Antigravity</h3> <pre><code class="language-plaintext">Open Settings in Antigravity by pressing Cmd+, (Mac) or Ctrl+, (Windows/Linux) Click the Customizations tab In the Build with Google Plugins section, click Customize Click Download next to the Dart and Flutter integration </code></pre> <p>This installs the official Flutter plugin for Antigravity, which bundles skills, MCP server configuration, and rules in a single step. It's the recommended installation path because it ensures all three components (skills, MCP, and rules) are correctly configured together.</p> <h3 id="heading-manual-skills-installation-for-antigravity">Manual Skills Installation for Antigravity</h3> <p>If you prefer manual installation or need to add custom team skills:</p> <pre><code class="language-bash">npx skills add flutter/agent-plugins --skill '*' --agent antigravity --yes npx skills add dart-lang/skills --skill '*' --agent antigravity --yes </code></pre> <p><code>--agent antigravity</code> targets the Antigravity-specific skills directory, though Antigravity also reads from the universal <code>.agents/skills/</code> location.</p> <h3 id="heading-antigravity-workflows-with-skills">Antigravity Workflows with Skills</h3> <p>Antigravity supports "workflows," which are pre-defined task sequences that can reference skills. You can create a workflow for common team tasks:</p> <pre><code class="language-markdown"># .antigravity/workflows/new-feature.md ## Create New Feature Workflow Apply skills: flutter-feature-architecture, flutter-bloc-state-management, flutter-testing, flutter-file-organization Steps: 1. Create the feature folder structure. 2. Create the domain model using Freezed. 3. Create the repository interface and implementation. 4. Create the Bloc with its events and states. 5. Create the screen widget. 6. Extract reusable component widgets. 7. Create unit tests for the repository. 8. Create `bloc_test` tests for the Bloc. 9. Create widget tests for the screen. </code></pre> <p>Workflows that reference skills ensure the agent applies the correct conventions for every step of a multi-step task. Without this explicit referencing, the agent might apply the file organization skill for step 1 but forget to apply the testing skill for steps 7 through 9.</p> <h2 id="heading-using-skills-with-openai-codex">Using Skills with OpenAI Codex</h2> <p>OpenAI Codex is a terminal-based agentic coding assistant similar in spirit to Claude Code. It runs in your terminal and executes multi-step tasks against your codebase.</p> <h3 id="heading-installing-skills-for-codex">Installing Skills for Codex</h3> <pre><code class="language-bash">npx skills add flutter/agent-plugins --skill '*' --agent codex --yes npx skills add dart-lang/skills --skill '*' --agent codex --yes </code></pre> <p><code>--agent codex</code> targets the Codex-specific skills directory. Codex also reads from the universal <code>.agents/skills/</code> directory, so the <code>--agent universal</code> flag works equally well.</p> <h3 id="heading-codex-rules-file">Codex Rules File</h3> <p>Similar to Claude Code's <code>CLAUDE.md</code>, Codex reads from an <code>AGENTS.md</code> file at the project root. Configure this alongside your skills:</p> <pre><code class="language-markdown"># AGENTS.md Flutter project: Kopa budgeting app Stack: flutter_bloc, go_router, firebase_ai, freezed Architecture: Feature-first with clean architecture Test framework: bloc_test + mocktail </code></pre> <p><code>AGENTS.md</code> is the project-wide context file that Codex reads on every task. Keep it brief: five to fifteen lines covering the most important project facts. Detailed conventions belong in skills, not in <code>AGENTS.md</code>, because skills load progressively while <code>AGENTS.md</code> always loads.</p> <h3 id="heading-plugin-installation-note-for-codex">Plugin Installation Note for Codex</h3> <p>Codex plugins currently can't bundle rules files automatically. This means installing the Flutter plugin from <code>flutter/agent-plugins</code> installs the skills but doesn't automatically create the <code>AGENTS.md</code> file.</p> <p>Create this file manually after running the plugin installation. The official Flutter documentation provides a template for the recommended <code>AGENTS.md</code> content for Flutter projects.</p> <h2 id="heading-using-skills-with-cursor">Using Skills with Cursor</h2> <p>Cursor is an AI-first code editor built on VS Code. It integrates agent capabilities directly into the editing experience and supports skills through a combination of its rules system and the universal <code>.agents/skills/</code> directory.</p> <h3 id="heading-installing-skills-for-cursor">Installing Skills for Cursor</h3> <pre><code class="language-bash">npx skills add flutter/agent-plugins --skill '*' --agent cursor --yes npx skills add dart-lang/skills --skill '*' --agent cursor --yes </code></pre> <p>Cursor reads skills from <code>.agents/skills/</code> as part of its agent context. The <code>--agent cursor</code> flag ensures skills are placed correctly for Cursor's discovery mechanism.</p> <h3 id="heading-cursor-rules-integration">Cursor Rules Integration</h3> <p>Cursor uses <code>.cursorrules</code> (or the newer <code>.cursor/rules/</code> directory in recent versions) for project-wide instructions, analogous to Claude Code's <code>CLAUDE.md</code>:</p> <pre><code class="language-markdown"># .cursor/rules/flutter.mdc --- description: Flutter project rules applied to all Dart files globs: ["**/*.dart", "pubspec.yaml"] alwaysApply: true --- This is a Flutter project using flutter_bloc, go_router, and freezed. All state management uses the Bloc pattern. Feature-first folder structure with clean architecture. See installed skills in .agents/skills/ for detailed conventions. </code></pre> <p><code>globs: ["**/*.dart"]</code> applies this rule only when Dart files are being edited, which prevents the Flutter rules from loading during Markdown editing or YAML configuration. <code>alwaysApply: true</code> ensures the rule is always in context when matching files are open.</p> <p>The reference to the skills directory at the bottom is intentional: it tells the agent to look at the skills for implementation details rather than making the rules file exhaustively long.</p> <h3 id="heading-using-composer-and-chat-in-cursor-with-skills">Using Composer and Chat in Cursor with Skills</h3> <p>In Cursor's Composer (the multi-file editing agent), skills load automatically when you describe a task. In Cursor Chat (the inline assistant), you may need to be more explicit:</p> <pre><code class="language-plaintext">@flutter-file-organization Create a new PostCard widget extracted from the post list screen </code></pre> <p>The <code>@</code> prefix in Cursor chat can reference installed skills by name in some configurations. In others, simply describing the task in enough detail is sufficient for the agent to load the relevant skill automatically.</p> <h2 id="heading-using-skills-with-other-agents">Using Skills with Other Agents</h2> <h3 id="heading-github-copilot-cli">GitHub Copilot CLI</h3> <p>GitHub Copilot CLI supports the universal <code>.agents/skills/</code> directory when run in agent mode (<code>gh copilot explain</code> and <code>gh copilot suggest</code>):</p> <pre><code class="language-bash">npx skills add flutter/agent-plugins --skill '*' --agent copilot --yes </code></pre> <p>Note from the <code>skills</code> CLI documentation that GitHub Copilot isn't auto-detected when using <code>skills get</code> because the <code>.github/</code> directory is commonly used for other purposes. Always use the explicit <code>--agent copilot</code> flag when installing skills for Copilot.</p> <h3 id="heading-gemini-cli">Gemini CLI</h3> <p>Google's Gemini CLI supports the universal skills directory:</p> <pre><code class="language-bash">npx skills add flutter/agent-plugins --skill '*' --agent gemini --yes </code></pre> <h3 id="heading-universal-installation">Universal Installation</h3> <p>If you want a single installation that works for all agents simultaneously:</p> <pre><code class="language-bash">npx skills add flutter/agent-plugins --skill '*' --agent universal --yes npx skills add dart-lang/skills --skill '*' --agent universal --yes </code></pre> <p>The <code>universal</code> agent target places skills in <code>.agents/skills/</code>, which all compliant agents discover automatically. This is the recommended default for teams that use multiple agents or want to be agent-agnostic.</p> <h3 id="heading-verifying-agent-discovery">Verifying Agent Discovery</h3> <p>Regardless of which agent you use, you can verify skill discovery with a natural language question to the agent:</p> <pre><code class="language-plaintext">Summarize the capabilities of the skills you have available for this project. </code></pre> <p>A correctly configured agent responds with a list of installed skills and their descriptions, confirming that discovery is working. If the agent says it has no skills or can't find any, check that:</p> <ol> <li><p>The <code>.agents/skills/</code> directory exists at the project root</p> </li> <li><p>The directory contains <code>.md</code> files with valid YAML frontmatter</p> </li> <li><p>The agent supports the universal skills specification</p> </li> </ol> <h2 id="heading-the-official-flutter-skills-a-deep-dive">The Official Flutter Skills: A Deep Dive</h2> <p>The official Flutter skills repository (<code>flutter/agent-plugins</code>) contains a set of skills that represent the Flutter team's best thinking on common development patterns. Understanding what each skill covers helps you decide which to install, which to customize, and which to supplement with your own skills.</p> <h3 id="heading-flutter-responsive-layout">flutter-responsive-layout</h3> <p>This skill teaches the agent how to build layouts that adapt correctly across mobile, tablet, and desktop breakpoints. It covers <code>AdaptiveScaffold</code>, <code>LayoutBuilder</code>, <code>MediaQuery</code>, <code>Breakpoints</code>, and the patterns the Flutter Adaptive Framework recommends for handling different screen sizes.</p> <p>Without this skill, agents build layouts that look fine on a single device size and break on others. With it, agents produce layouts that are responsive from the first line of code, using the correct Flutter-specific tools rather than hardcoded pixel thresholds.</p> <h3 id="heading-flutter-declarative-routing">flutter-declarative-routing</h3> <p>This skill teaches GoRouter setup, route definition patterns, nested navigation, redirect logic for authentication, deep linking configuration, and the correct way to pass typed parameters between routes.</p> <p>Without this skill, agents often use <code>Navigator.push</code> even in codebases that carefully use GoRouter everywhere. They also commonly get deep linking wrong and struggle with the typed parameter extraction pattern GoRouter requires.</p> <h3 id="heading-flutter-json-serialization">flutter-json-serialization</h3> <p>This skill teaches the <code>json_serializable</code> and <code>freezed</code> workflow: adding annotations, running <code>build_runner</code>, creating <code>fromJson</code>/<code>toJson</code> methods, handling nullable fields, and using <code>@JsonKey</code> for field name mapping.</p> <p>Without this skill, agents manually write serialization code or use <code>Map&lt;String, dynamic&gt;</code> throughout the data layer, producing fragile code that breaks silently when field names change.</p> <h3 id="heading-flutter-add-widget-test">flutter-add-widget-test</h3> <p>This skill teaches <code>testWidgets</code>, <code>WidgetTester</code>, pump strategies (<code>pump</code>, <code>pumpAndSettle</code>, <code>pumpWidget</code>), widget finders (<code>find.text</code>, <code>find.byType</code>, <code>find.byKey</code>), gesture simulation, and how to wrap widgets in minimal but sufficient test infrastructure.</p> <h3 id="heading-flutter-add-integration-test">flutter-add-integration-test</h3> <p>This skill teaches how to set up and run end-to-end integration tests on devices, web browsers, or Firebase Test Lab. It covers test setup, the <code>IntegrationTestWidgetsFlutterBinding</code>, app startup sequencing, and interacting with a fully running app in test.</p> <h3 id="heading-flutter-bloc">flutter-bloc</h3> <p>This skill teaches the complete Bloc workflow: defining events, states, and the Bloc class, providing the Bloc with <code>BlocProvider</code>, consuming it with <code>BlocBuilder</code>, <code>BlocListener</code>, and <code>BlocConsumer</code>, and testing with <code>bloc_test</code>.</p> <h3 id="heading-flutter-apply-architecture-best-practices">flutter-apply-architecture-best-practices</h3> <p>This skill enforces Clean Architecture (Data, Domain, Presentation) with the BLoC pattern as the official Flutter team recommends it. It defines layer boundaries, dependency rules, and the repository pattern.</p> <h3 id="heading-flutter-add-widget-preview">flutter-add-widget-preview</h3> <p>This skill teaches the Widget Previewer system introduced in Flutter 3.47, including the <code>@Preview</code> annotation, how to set up preview infrastructure, and how to write useful previews for complex widgets.</p> <h2 id="heading-the-official-dart-skills-a-deep-dive">The Official Dart Skills: A Deep Dive</h2> <p>The Dart team's official skills repository (<code>dart-lang/skills</code>) covers the Dart language itself rather than Flutter's widget system. These skills apply to any Dart code: Flutter app logic, Dart CLI tools, Dart backend services, and Dart packages.</p> <h3 id="heading-dart-add-unit-test">dart-add-unit-test</h3> <p>This is the most fundamental Dart skill and the one with the highest immediate impact. It teaches the agent how to write proper unit tests for any Dart class, including:</p> <ul> <li><p>Setting up the <code>test/</code> directory mirroring the <code>lib/</code> structure</p> </li> <li><p>Writing <code>group</code> and <code>test</code> blocks with descriptive names</p> </li> <li><p>Using <code>setUp</code> and <code>tearDown</code> for test lifecycle management</p> </li> <li><p>Using <code>expect</code> with the right matchers</p> </li> <li><p>Mocking dependencies with <code>mocktail</code></p> </li> <li><p>Testing async code with <code>expectLater</code> and stream matchers</p> </li> </ul> <p>Without this skill, agents produce tests that test the wrong things, use incorrect assertion patterns, and structure test files in ways that don't mirror the source tree. With it, agents produce tests that follow the <code>package:test</code> conventions correctly from the first run.</p> <pre><code class="language-markdown"># What dart-add-unit-test teaches the agent ## Test file placement test/features/profile/data/profile_repository_test.dart mirrors lib/features/profile/data/profile_repository.dart ## Test naming ``` group('ProfileRepository', () { group('getProfile', () { test('returns ProfileLoaded when API call succeeds', () async { // ... }); test('returns NetworkFailure when connection fails', () async { // ... }); }); }); ``` ## Async testing ``` await expectLater( repository.getProfile('user123'), completion(isA&lt;Right&lt;AppFailure, UserProfile&gt;&gt;()), ); ``` </code></pre> <h3 id="heading-dart-run-static-analysis">dart-run-static-analysis</h3> <p>This skill teaches the agent how to work with Dart's static analysis infrastructure: configuring <code>analysis_options.yaml</code>, running <code>dart analyze</code>, applying <code>dart fix --apply</code>, understanding lint rules, suppressing false positives correctly, and enforcing strict type checks.</p> <pre><code class="language-markdown"># What dart-run-static-analysis covers ## analysis_options.yaml configuration include: package:flutter_lints/flutter.yaml analyzer: language: strict-casts: true strict-inference: true strict-raw-types: true exclude: - '**/*.g.dart' - '**/*.freezed.dart' linter: rules: avoid_print: true prefer_final_fields: true require_trailing_commas: true ## Correct suppression (when a lint is a false positive) // ignore: avoid_print &lt;- line-level, for one occurrence // ignore_for_file: type=lint &lt;- file-level, for generated files </code></pre> <p>Understanding how to configure <code>analysis_options.yaml</code> correctly is one of those tasks where agents frequently make mistakes without guidance: they enable the wrong rules, forget to exclude generated files, or suppress diagnostics too broadly. This skill makes those configurations correct from the start.</p> <h3 id="heading-dart-tooling">dart-tooling</h3> <p>This skill teaches how to resolve package version conflicts in <code>pubspec.yaml</code>, use dependency overrides correctly, understand the difference between direct and transitive dependencies, and read <code>pubspec.lock</code> to diagnose version resolution issues.</p> <p>Package dependency management is an area where agents frequently hallucinate package versions or suggest <code>dependency_overrides</code> in ways that mask real conflicts. This skill corrects those behaviors.</p> <h3 id="heading-dart-use-pattern-matching">dart-use-pattern-matching</h3> <p>This skill is one of the highest-value Dart skills because Dart 3's sealed classes and pattern matching represent a genuinely new coding paradigm that agents trained before Dart 3's release don't use consistently. It teaches:</p> <ul> <li><p>Switch expressions on sealed classes with exhaustiveness</p> </li> <li><p>Destructuring patterns in switch cases</p> </li> <li><p>Guard clauses with <code>when</code></p> </li> <li><p>Record patterns</p> </li> <li><p>List and map patterns</p> </li> <li><p>The correct use of <code>_</code> (wildcard) in patterns</p> </li> </ul> <pre><code class="language-dart">// What the agent learns to write with dart-use-pattern-matching // Before: traditional switch on enum (old pattern) switch (state) { case AppState.loading: return CircularProgressIndicator(); case AppState.loaded: return ContentWidget(data: data); default: return ErrorWidget(); } // After: switch expression with pattern matching (idiomatic Dart 3) return switch (state) { AppStateLoading() =&gt; const CircularProgressIndicator(), AppStateLoaded(:final data) =&gt; ContentWidget(data: data), AppStateError(:final message) =&gt; ErrorWidget(message: message), }; </code></pre> <p>The destructuring pattern <code>AppStateLoaded(:final data)</code> is pure Dart 3 and extremely clean, but agents without this skill rarely produce it because it wasn't in the training data for older agent versions.</p> <h3 id="heading-dart-collect-coverage">dart-collect-coverage</h3> <p>This skill teaches test coverage collection, LCOV report generation, HTML report generation, and how to filter out generated code (<code>*.g.dart</code>, <code>*.freezed.dart</code>) from coverage reports so the numbers reflect real coverage rather than being inflated by generated code that can't be meaningfully tested.</p> <h3 id="heading-dart-generate-test-mocks">dart-generate-test-mocks</h3> <p>This skill teaches the <code>mockito</code> and <code>build_runner</code> workflow for generating type-safe mocks from interfaces and abstract classes. It covers adding the annotations, running <code>dart run build_runner build</code>, and using the generated mocks in tests.</p> <h3 id="heading-dart-fix-runtime-errors">dart-fix-runtime-errors</h3> <p>This is a procedural skill: it teaches the agent to use the LSP (Language Server Protocol) to fetch the current stack trace, locate the failing line, apply a fix, and verify resolution using hot reload. This is the correct workflow for fixing runtime errors in a live Flutter app rather than guessing at the cause.</p> <h3 id="heading-dart-genkit">dart-genkit</h3> <p>This skill teaches how to build AI-powered workflows and agents using the Genkit Dart SDK. It's specifically relevant for Flutter developers building AI features, covering flow definition, tool calling, model selection, and streaming.</p> <h3 id="heading-dart-migrate-to-checks-package">dart-migrate-to-checks-package</h3> <p>This skill teaches how to migrate from the older <code>package:matcher</code> assertion style to the newer <code>package:checks</code> style, which produces better error messages and is more composable.</p> <pre><code class="language-dart">// Old style (package:matcher) expect(result, isA&lt;Right&lt;AppFailure, UserProfile&gt;&gt;()); expect(result.getOrElse(() =&gt; null)?.name, equals('Ade')); // New style (package:checks) check(result).isA&lt;Right&lt;AppFailure, UserProfile&gt;&gt;(); check(result.getOrElse(() =&gt; null)?.name).equals('Ade'); </code></pre> <h3 id="heading-dart-memory">dart-memory</h3> <p>This skill teaches how to prevent memory leaks and reduce garbage collection pressure in Flutter and Dart apps, covering <code>StreamController</code> disposal, <code>AnimationController</code> disposal, closure capture patterns that prevent garbage collection, and how to use DevTools to identify memory issues.</p> <h3 id="heading-dart-build-cli-app">dart-build-cli-app</h3> <p>For Flutter developers who also write Dart CLI tools, backend scripts, or deployment automation in Dart, this skill covers entrypoint structure, argument parsing with <code>package:args</code>, exit codes, subprocess handling, and cross-platform script patterns.</p> <h3 id="heading-dart-logic-patterns">dart-logic-patterns</h3> <p>This skill covers algorithms, data structures, and Dart-specific patterns for organizing business logic: using <code>Iterable</code> methods correctly, choosing between <code>List</code>, <code>Set</code>, and <code>Map</code> for different use cases, implementing efficient search and sort, and using Dart's collection literals productively.</p> <h2 id="heading-the-flutter-file-organization-skill-a-complete-walkthrough">The flutter-file-organization Skill: A Complete Walkthrough</h2> <p>The file organization skill is the most universally applicable Flutter skill and an excellent teaching example for how skills should be structured. Reading it carefully reveals the principles behind every effective skill.</p> <pre><code class="language-markdown">--- name: flutter-file-organization description: Organize and split Flutter/Dart files while preserving StatefulWidget and State relationships. Use when creating, refactoring, splitting, or reorganizing Dart files and classes. --- # Flutter File Organization When creating, splitting, refactoring, or reorganizing Flutter/Dart files, follow these rules. ## Core Rules 1. Inspect the existing file before modifying it. 2. Identify all classes, enums, extensions, mixins, typedefs, and top-level declarations. 3. Identify relationships and dependencies between declarations before splitting them. 4. Keep each independent primary class in its own file. 5. Treat tightly coupled declarations as a single implementation unit and keep them together. 6. Never separate a `StatefulWidget` from its corresponding `State&lt;T&gt;` class. 7. Update all imports and references after moving declarations. 8. Do not introduce unnecessary private helper classes or methods. 9. Preserve existing application behavior. File organization must not change functionality. 10. Run `dart format` on modified Dart files. 11. Run the project's analyzer and relevant tests. </code></pre> <p>Rule 1 ("Inspect the existing file before modifying it") prevents one of the most common and costly agent mistakes: making assumptions about file contents without reading them.</p> <p>An agent that skips inspection may duplicate declarations, break dependencies, or introduce naming conflicts with things that already exist. Making inspection an explicit first rule ensures the agent always starts from a complete picture of the current state.</p> <p>Rules 2 and 3 ("Identify all classes" and "Identify relationships") are mandatory pre-flight checks. Before the agent touches a single byte of a file, it must map everything that exists and how the pieces depend on each other.</p> <p>This is the equivalent of "measure twice, cut once" applied to code refactoring, and it prevents the most frustrating class of bug: refactors that break things that were working.</p> <p>Rule 6 ("Never separate a StatefulWidget from its corresponding State class") encodes Flutter-specific compilation knowledge. A developer who knows Dart deeply but doesn't know Flutter could reasonably split a file by moving every class to its own file. They would hit a compile error because <code>_ProfilePageState</code> references the <code>ProfilePage</code> widget through <code>widget</code>, which has a type that <code>State&lt;T&gt;</code> establishes at the class level. The two classes form a single compilation unit that can't be separated. This rule prevents a compile error that no amount of general Dart knowledge would avoid.</p> <p>Rules 10 and 11 ("Run dart format" and "Run the project's analyzer") close the task-completion loop. Without these rules, an agent declares success after generating files, leaving formatting inconsistencies and possible analyzer warnings for you to discover later. With them, the agent runs both tools before reporting completion, catching issues immediately.</p> <pre><code class="language-markdown">## Widget Extraction Do not create private `_build...()` methods as a way of extracting substantial widget UI. For example, do not do this: ``` Widget _buildUserCard() { return Container( ... ); } ``` Instead separate this into a class that is public and place it inside the widgets folder or the components folder. </code></pre> <p>The Widget Extraction section does four things that every good skill rule should do: states the rule clearly, explains the prohibited pattern precisely (not just vaguely), shows a concrete code example of what not to do so there's no ambiguity, and tells the agent what to do instead.</p> <p>The <code>_build...()</code> pattern is very common in training data (tutorials often use it for simplicity), which means saying "avoid it" without a concrete example risks not overriding the learned behavior.</p> <p>Showing the exact code pattern to avoid and contrasting it with the alternative makes the instruction maximally clear.</p> <pre><code class="language-markdown">## Component Extraction Do not place large amounts of UI inside a single widget. Extract logical sections into reusable components whenever appropriate. Examples include: - Header sections - Statistics cards - Filter bars - Search bars - Lists - Table rows - Buttons - Empty states - Loading views - Form sections - Dialog content Favor small, reusable widgets over large build methods. </code></pre> <p>The example list in the Component Extraction section is drawn from real experience. These are the actual UI sections that accumulate inside screen widgets in production Flutter apps. An agent reading this list will recognize these patterns in the code it examines and know to extract them.</p> <p>Without the list, "extract logical sections" is too vague for reliable behavior: the agent needs to know concretely what counts as a "logical section."</p> <pre><code class="language-markdown">## Code Comments Do not write code comments. This rule applies everywhere and to every layer. </code></pre> <p>The code comments rule is brief because it's absolute. The phrase "applies everywhere and to every layer" is deliberate. Without this scope qualifier, an agent might interpret the rule as applying only to the current file organization task and revert to adding comments in other files it creates or modifies. The explicit scope removes ambiguity and makes the rule's intent clear across all contexts.</p> <h2 id="heading-writing-your-own-skills-the-complete-guide">Writing Your Own Skills: The Complete Guide</h2> <p>The official skills are your foundation. But your most valuable skills are often the ones you write yourself, encoding the specific patterns, mistakes, and standards of your own projects.</p> <h3 id="heading-the-right-mindset-for-writing-skills">The Right Mindset for Writing Skills</h3> <p>Writing a skill is not the same as writing documentation for humans. Documentation for humans relies on shared context, implicit understanding, and the ability to ask questions. Skills for agents must be explicit, precise, and assume no knowledge beyond what the skill file contains.</p> <p>The best skills come from real experience with your codebase. Keep a running list of every time you manually fix AI-generated code. Every fix is a skill rule. When you explain a convention to a new team member, that explanation is skill content. When you catch the same mistake in code review three times in a row, that mistake needs a skill.</p> <p>Ask yourself before writing any rule: "Would an agent that doesn't know my codebase know to do this?" If the answer is no, the rule belongs in a skill.</p> <h3 id="heading-the-description-the-most-important-twenty-words">The Description: The Most Important Twenty Words</h3> <p>The description field is the gatekeeper. Write it last, after the skill body is complete, so it accurately describes what the skill actually covers. A good description passes this test: if an agent reads only the description, it knows whether this skill is relevant for a given task.</p> <pre><code class="language-yaml"># Poor: too vague, no trigger phrases description: How to handle state in Flutter apps. # Better: specific, multiple trigger phrases, clear scope description: Implement state management using flutter_bloc in Flutter applications. Use when adding state management to screens, creating new features that have loading or error states, fetching data from APIs, handling user interactions that change UI state, or implementing BlocProvider, BlocBuilder, BlocListener, or BlocConsumer. Applies when you see references to bloc, cubit, state, event, or stream in a task. </code></pre> <p>The second description is better for several specific reasons. It lists specific trigger scenarios ("creating new features that have loading or error states") that are more likely to match actual task descriptions than the vague "handle state." It includes the API surface of the relevant package (<code>BlocProvider</code>, <code>BlocBuilder</code>) which are likely to appear in task descriptions. And it lists the conceptual keywords (<code>bloc</code>, <code>cubit</code>, <code>state</code>, and <code>event</code>) that serve as signals.</p> <h3 id="heading-writing-rules-that-change-agent-behavior">Writing Rules That Change Agent Behavior</h3> <p>Not all rules are equal. Rules that tell an agent to do something it was already doing provide no value. Rules that change what the agent does are the valuable ones. To write rules that change behavior, start from observation: what did the agent actually produce that was wrong, and what rule would have prevented that?</p> <pre><code class="language-markdown">## Rules That Work vs Rules That Do Not DO NOT WORK (agent was already trying to do these): - Write clean, readable code. - Follow Flutter best practices. - Use appropriate state management. - Keep the codebase maintainable. WORK (these change specific agent behavior): - Extract any widget build section exceeding 30 lines into a separate class in widgets/. - Never call setState inside a widget that has a corresponding BlocBuilder. - Name Bloc events as past-tense verbs: ProfileLoadRequested, not LoadProfile. - Place all Bloc files (bloc, event, state) in a bloc/ subdirectory inside the feature. - The state class uses sealed keyword: sealed class ProfileState {}. - Provide super.key in every widget constructor: const MyWidget({super.key}). - Check mounted before calling setState in any async method. </code></pre> <p>Notice that working rules contain specific numbers (30 lines), specific folder names (widgets/, bloc/), specific naming patterns with examples, and specific code patterns. Vague rules like "write clean code" describe something the agent already tries to do by default. Specific rules like "name Bloc events as past-tense verbs with concrete examples" change actual output.</p> <h3 id="heading-the-counterexample-pattern">The Counterexample Pattern</h3> <p>For rules that address patterns that are common in training data, showing the wrong pattern alongside the right one is significantly more effective than describing the rule in text alone. The agent has seen the wrong pattern thousands of times in training. A text rule may not be strong enough to override that learned behavior. A visual contrast makes the intention unmistakable.</p> <pre><code class="language-markdown">## Error State Naming Do not name error states with the word "Error" alone at the end. Do not do this: ``` final class ProfileError extends ProfileState { const ProfileError(); } </code></pre> <p>Include the error context:</p> <pre><code class="language-dart">final class ProfileLoadFailure extends ProfileState { const ProfileLoadFailure({required this.message}); final String message; } </code></pre> <p>Including the action name (<code>Load</code>) makes the error state specific to the operation that failed. This is important when a single Bloc handles multiple operations that can fail independently. <code>ProfileLoadFailure</code> and <code>ProfileUpdateFailure</code> can coexist meaningfully. <code>ProfileError</code> and <code>ProfileError2</code> can't.</p> <p>The explanation after the counterexample ("Including the action name...") connects the rule to the reason, which helps the agent apply the rule correctly in edge cases rather than just following the letter of the rule.</p> <h2 id="heading-essential-flutter-skills-every-team-should-have">Essential Flutter Skills Every Team Should Have</h2> <p>Based on the most common areas where AI agents produce incorrect Flutter output, here are the essential skills every Flutter team should write and maintain. Each is presented in full, ready to be adapted to your specific conventions.</p> <h3 id="heading-the-bloc-state-management-skill">The Bloc State Management Skill</h3> <pre><code class="language-plaintext">--- name: flutter-bloc-state-management description: Implement state management using flutter_bloc. Use when creating new features, adding state to screens, fetching data from APIs, handling user interactions that produce loading or error states, using BlocProvider, BlocBuilder, BlocListener, BlocConsumer, adding a Cubit, or any task involving state transitions in Flutter. --- # Flutter Bloc State Management This project uses flutter_bloc for all state management. Do not use setState, ChangeNotifier, Provider, or Riverpod unless explicitly instructed. ## File Structure Every feature that requires state management has three Bloc files in a bloc/ subdirectory: </code></pre> <pre><code class="language-plaintext">lib/ features/ profile/ bloc/ profile_bloc.dart &lt;- Bloc class and handler methods profile_event.dart &lt;- All events as sealed class hierarchy profile_state.dart &lt;- All states as sealed class hierarchy screens/ profile_screen.dart widgets/ profile_card.dart profile.dart &lt;- barrel export </code></pre> <h4 id="heading-sealed-classes">Sealed Classes</h4> <p>Events and states use Dart's sealed class system for exhaustive handling:</p> <pre><code class="language-dart">// profile_event.dart sealed class ProfileEvent {} final class ProfileLoadRequested extends ProfileEvent { const ProfileLoadRequested({required this.userId}); final String userId; } final class ProfileUsernameUpdated extends ProfileEvent { const ProfileUsernameUpdated({required this.newUsername}); final String newUsername; } </code></pre> <pre><code class="language-dart">// profile_state.dart sealed class ProfileState {} final class ProfileInitial extends ProfileState {} final class ProfileLoading extends ProfileState {} final class ProfileLoaded extends ProfileState { const ProfileLoaded({required this.profile}); final UserProfile profile; } final class ProfileLoadFailure extends ProfileState { const ProfileLoadFailure({required this.message}); final String message; } </code></pre> <p><code>sealed class</code> makes the hierarchy exhaustive: Dart's compiler can verify that every possible state is handled in a switch statement. <code>final class</code> on concrete implementations prevents unintended subclassing. Every state and event is <code>final</code> and <code>sealed</code>.</p> <h4 id="heading-naming-conventions">Naming Conventions</h4> <p>The Bloc class should use the feature name followed by <code>Bloc</code>, such as <code>ProfileBloc</code>, <code>AuthBloc</code>, or <code>CartBloc</code>. Events should use a past-tense verb phrase followed by the feature name and the <code>Event</code> suffix, such as <code>ProfileLoadRequested</code> or <code>AuthLoginAttempted</code>. States should use the feature name followed by a descriptive noun or adjective, such as <code>ProfileInitial</code>, <code>ProfileLoading</code>, <code>ProfileLoaded</code>, or <code>ProfileLoadFailure</code>.</p> <p>Don't name events as commands (not <code>LoadProfile</code>, but <code>ProfileLoadRequested</code>). Don't name error states simply as <code>ProfileError</code>. Include the operation: <code>ProfileLoadFailure</code>, <code>ProfileUpdateFailure</code>.</p> <h4 id="heading-the-bloc-class">The Bloc Class</h4> <pre><code class="language-dart">// profile_bloc.dart class ProfileBloc extends Bloc&lt;ProfileEvent, ProfileState&gt; { final ProfileRepository _repository; ProfileBloc({required ProfileRepository repository}) : _repository = repository, super(ProfileInitial()) { on&lt;ProfileLoadRequested&gt;(_onProfileLoadRequested); on&lt;ProfileUsernameUpdated&gt;(_onProfileUsernameUpdated); } Future&lt;void&gt; _onProfileLoadRequested( ProfileLoadRequested event, Emitter&lt;ProfileState&gt; emit, ) async { emit(ProfileLoading()); final result = await _repository.getProfile(event.userId); result.fold( (failure) =&gt; emit(ProfileLoadFailure(message: _mapFailure(failure))), (profile) =&gt; emit(ProfileLoaded(profile: profile)), ); } String _mapFailure(AppFailure failure) =&gt; switch (failure) { NetworkFailure(:final message) =&gt; message, ServerFailure(:final message) =&gt; message, NotFoundFailure() =&gt; 'Profile not found', UnauthorizedFailure() =&gt; 'Please sign in again', _ =&gt; 'An unexpected error occurred', }; } </code></pre> <p>Each event handler is a private method named <code>_on</code> + EventClassName. The pattern is consistent across all Blocs. Every handler emits a loading state before the async operation and emits either a success or failure state after. No handler returns data directly. All communication is through emitted states.</p> <h4 id="heading-widget-integration">Widget Integration</h4> <pre><code class="language-dart">class ProfileScreen extends StatelessWidget { const ProfileScreen({super.key, required this.userId}); final String userId; @override Widget build(BuildContext context) { return BlocProvider( create: (context) =&gt; ProfileBloc( repository: context.read&lt;ProfileRepository&gt;(), )..add(ProfileLoadRequested(userId: userId)), child: BlocConsumer&lt;ProfileBloc, ProfileState&gt;( listener: (context, state) { if (state is ProfileLoadFailure) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.message)), ); } }, builder: (context, state) =&gt; switch (state) { ProfileInitial() =&gt; const SizedBox.shrink(), ProfileLoading() =&gt; const Center(child: CircularProgressIndicator()), ProfileLoaded(:final profile) =&gt; ProfileContent(profile: profile), ProfileLoadFailure(:final message) =&gt; ProfileErrorView(message: message), }, ), ); } } </code></pre> <p><code>BlocConsumer</code> combines listener (side effects) and builder (UI). The switch expression on sealed states is exhaustive: the compiler enforces that every state has a corresponding UI.</p> <h4 id="heading-prohibited-patterns">Prohibited Patterns</h4> <p>Don't use <code>setState</code> in any widget that has a corresponding Bloc. Don't call <code>context.read&lt;SomeBloc&gt;().add(event)</code> from inside <code>initState</code> without deferring with <code>addPostFrameCallback</code>. Don't access <code>BuildContext</code> after an <code>await</code> without checking <code>mounted</code>. Don't create a Bloc inside a <code>StatelessWidget.build</code> method (it is recreated on every rebuild).</p> <h3 id="heading-the-feature-architecture-skill">The Feature Architecture Skill</h3> <pre><code class="language-plaintext">--- name: flutter-feature-architecture description: Structure Flutter features using clean architecture with repository, service, and presentation layers. Use when creating new features, adding screens, implementing data fetching, organizing existing code, deciding where a new file belongs, or any task that involves folder structure, layer boundaries, or the project's directory organization. --- # Flutter Feature Architecture This project uses feature-first folder structure with clean architecture layers. </code></pre> <h4 id="heading-top-level-structure">Top-Level Structure</h4> <pre><code class="language-plaintext">lib/ core/ constants/ &lt;- app-wide constants, not feature-specific errors/ &lt;- AppFailure sealed class hierarchy extensions/ &lt;- Dart extension methods theme/ &lt;- theme extensions, color tokens, typography utils/ &lt;- pure utility functions features/ auth/ profile/ home/ settings/ shared/ widgets/ &lt;- widgets used in 3+ features models/ &lt;- models shared between features services/ &lt;- services used by multiple features app.dart &lt;- MaterialApp setup main.dart &lt;- entry point </code></pre> <h4 id="heading-feature-folder-structure">Feature Folder Structure</h4> <p>Every feature follows this internal structure:</p> <pre><code class="language-plaintext">features/ profile/ bloc/ profile_bloc.dart profile_event.dart profile_state.dart data/ profile_repository.dart &lt;- interface profile_repository_impl.dart &lt;- implementation profile_remote_data_source.dart profile_local_data_source.dart domain/ profile_model.dart &lt;- freezed domain model screens/ profile_screen.dart edit_profile_screen.dart widgets/ profile_card.dart profile_header.dart profile_stats_row.dart profile.dart &lt;- barrel export </code></pre> <h4 id="heading-layer-dependency-rules">Layer Dependency Rules</h4> <p>The presentation layer (screens and widgets) depends only on Bloc and domain models. The Bloc depends only on the repository interface (not the implementation). The repository implementation depends on data sources. Data sources depend on external packages (Firebase, HTTP, SharedPreferences).</p> <p>Never import across layers in the wrong direction. The data layer never imports from the presentation layer. The domain layer imports from nothing in the project.</p> <h4 id="heading-the-barrel-export-file">The Barrel Export File</h4> <p>Every feature has a barrel file that exports only the public API of the feature:</p> <pre><code class="language-dart">// features/profile/profile.dart export 'domain/profile_model.dart'; export 'screens/profile_screen.dart'; export 'screens/edit_profile_screen.dart'; export 'bloc/profile_bloc.dart'; export 'bloc/profile_event.dart'; export 'bloc/profile_state.dart'; </code></pre> <p>Internal implementation files (data sources, repository implementation) aren't exported. Consuming code imports <code>package:myapp/features/profile/profile.dart</code>, never deep paths.</p> <h4 id="heading-the-core-folder-rule">The Core Folder Rule</h4> <p>A file belongs in core/ only if it's used by three or more features. If used by only one or two features, it belongs inside those features' folders. Don't preemptively move things to core/ based on where they might be used in the future.</p> <h3 id="heading-the-error-handling-skill">The Error Handling Skill</h3> <pre><code class="language-markdown">--- name: flutter-error-handling description: Implement error handling using typed AppFailure classes and Either return types. Use when handling errors from API calls, repository methods, Bloc error states, catching exceptions in data sources, showing error UI, implementing try-catch, or any task that involves failure, exception, error state, or error message handling. --- # Flutter Error Handling This project uses a typed failure system. Raw exceptions do not cross layer boundaries. </code></pre> <h4 id="heading-the-appfailure-hierarchy">The AppFailure Hierarchy</h4> <pre><code class="language-dart">// core/errors/app_failure.dart sealed class AppFailure { const AppFailure(); } final class NetworkFailure extends AppFailure { const NetworkFailure({required this.message}); final String message; } final class ServerFailure extends AppFailure { const ServerFailure({required this.statusCode, required this.message}); final int statusCode; final String message; } final class CacheFailure extends AppFailure { const CacheFailure({required this.message}); final String message; } final class NotFoundFailure extends AppFailure { const NotFoundFailure(); } final class UnauthorizedFailure extends AppFailure { const UnauthorizedFailure(); } final class ValidationFailure extends AppFailure { const ValidationFailure({required this.field, required this.message}); final String field; final String message; } </code></pre> <p><code>sealed class AppFailure</code> makes the hierarchy exhaustive. New failure types are added as <code>final class</code> subclasses. The compiler enforces that switch statements on <code>AppFailure</code> handle every possible subtype.</p> <h4 id="heading-repository-return-types">Repository Return Types</h4> <p>Repository methods return <code>Either&lt;AppFailure, T&gt;</code> from the <code>fpdart</code> package:</p> <pre><code class="language-dart">abstract class ProfileRepository { Future&lt;Either&lt;AppFailure, UserProfile&gt;&gt; getProfile(String userId); Future&lt;Either&lt;AppFailure, Unit&gt;&gt; updateUsername(String userId, String username); } </code></pre> <p>Returning <code>Either</code> makes failure possible-but-explicit at the type level. Consumers of the repository can't accidentally ignore the possibility of failure because the return type forces them to handle both branches.</p> <h4 id="heading-data-source-exception-handling">Data Source Exception Handling</h4> <p>Data sources are the only layer that uses try-catch. They catch raw exceptions and convert them to AppFailure objects:</p> <pre><code class="language-dart">class ProfileRemoteDataSource { Future&lt;Either&lt;AppFailure, UserProfileDto&gt;&gt; getProfile(String userId) async { try { final doc = await _firestore.collection('users').doc(userId).get(); if (!doc.exists) return left(const NotFoundFailure()); return right(UserProfileDto.fromJson(doc.data()!)); } on FirebaseException catch (e) { return switch (e.code) { 'permission-denied' =&gt; left(const UnauthorizedFailure()), 'unavailable' =&gt; left(NetworkFailure(message: e.message ?? 'Network error')), _ =&gt; left(ServerFailure(statusCode: 0, message: e.message ?? 'Server error')), }; } catch (e) { return left(NetworkFailure(message: e.toString())); } } } </code></pre> <h4 id="heading-prohibited-patterns">Prohibited Patterns</h4> <p>Don't use <code>try-catch</code> in Blocs, repositories, or presentation layer code. Don't throw exceptions from repository methods. Don't use <code>String</code> as an error message type in state classes. Use the typed failure. Don't pass raw exception messages to the UI. Map failures to user-friendly messages in the Bloc.</p> <h3 id="heading-the-theming-skill">The Theming Skill</h3> <pre><code class="language-markdown">--- name: flutter-theming description: Apply colors, typography, spacing, and visual styling using the project's theme extension system. Use whenever writing code that involves colors, text styles, padding, margin, border radius, shadows, or any visual appearance of UI components. Apply when you see requests involving styling, colors, fonts, spacing, or visual design. --- # Flutter Theming This project uses theme extensions for all visual styling. Hardcoded visual values are not permitted anywhere in the codebase. </code></pre> <h4 id="heading-color-access">Color Access</h4> <pre><code class="language-dart">// Do not do this color: const Color(0xFF6750A4) color: Colors.deepPurple backgroundColor: Theme.of(context).colorScheme.primary // Do this color: context.appColors.primary backgroundColor: context.appColors.surface </code></pre> <p><code>context.appColors</code> is an extension on <code>BuildContext</code> defined in <code>core/theme/app_colors_extension.dart</code>. It provides typed access to the full color palette with names that communicate intent.</p> <p>Available colors: use the semantic colors provided through <code>context.appColors</code>.</p> <p>For <strong>branding and surfaces</strong>, use <code>context.appColors.primary</code> for the main brand color, <code>context.appColors.secondary</code> for secondary accents, <code>context.appColors.surface</code> for card and container backgrounds, and <code>context.appColors.background</code> for screen backgrounds.</p> <p>For <strong>states</strong>, use <code>context.appColors.error</code> for error states and <code>context.appColors.success</code> for success states.</p> <p>For <strong>text</strong>, use <code>context.appColors.textPrimary</code> for primary readable text, <code>context.appColors.textSecondary</code> for captions, labels, and secondary information, and <code>context.appColors.textDisabled</code> for disabled controls and text.</p> <h4 id="heading-spacing">Spacing</h4> <pre><code class="language-dart">// Do not do this padding: const EdgeInsets.all(16) margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 8) // Do this padding: const EdgeInsets.all(AppSpacing.md) margin: const EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: AppSpacing.sm, ) </code></pre> <p><code>AppSpacing</code> is defined in <code>core/constants/app_spacing.dart</code> and provides the following spacing values:</p> <p><strong>xs:</strong> 4 · <strong>sm:</strong> 8 · <strong>md:</strong> 16 · <strong>lg:</strong> 24 · <strong>xl:</strong> 32 · <strong>xxl:</strong> 48</p> <h4 id="heading-typography">Typography</h4> <pre><code class="language-dart">// Do not do this style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600) // Do this style: context.appTypography.bodyMedium style: context.appTypography.headlineLarge.copyWith( color: context.appColors.textPrimary, ) </code></pre> <p><code>context.appTypography</code> is an extension on <code>BuildContext</code> providing the full type scale.</p> <h4 id="heading-border-radius">Border Radius</h4> <pre><code class="language-dart">// Do not do this borderRadius: BorderRadius.circular(8) // Do this borderRadius: BorderRadius.circular(AppRadius.sm) </code></pre> <p><code>AppRadius</code> constants: <code>xs</code> (4), <code>sm</code> (8), <code>md</code> (12), <code>lg</code> (16), <code>xl</code> (24), <code>round</code> (999).</p> <h3 id="heading-the-navigation-skill">The Navigation Skill</h3> <pre><code class="language-markdown">--- name: flutter-navigation description: Implement navigation using GoRouter. Use when adding routes, navigating between screens, implementing deep links, setting up route guards or redirects, handling authentication-gated routes, working with nested navigation or shell routes, or any task involving navigation, routing, back button, browser URL, or deep link. --- # Flutter Navigation This project uses GoRouter for all navigation. Do not use Navigator.push, Navigator.pushNamed, Navigator.pop (only via GoRouter), or any Navigator API that bypasses GoRouter. </code></pre> <h4 id="heading-route-constants">Route Constants</h4> <p>All route paths are constants in <code>core/router/routes.dart</code>:</p> <pre><code class="language-dart">abstract class Routes { static const splash = '/'; static const login = '/auth/login'; static const register = '/auth/register'; static const home = '/home'; static const profile = '/home/profile/:userId'; static const editProfile = '/home/profile/:userId/edit'; static const settings = '/settings'; } </code></pre> <p>Never use string literals for navigation. Always use <code>Routes.home</code>, not <code>'/home'</code>.</p> <h4 id="heading-navigation-methods">Navigation Methods</h4> <pre><code class="language-dart">// Replace the current location (no back button to previous) context.go(Routes.home); // Push on top (back button returns to previous location) context.push(Routes.profile.replaceAll(':userId', userId)); // Pop (go back) context.pop(); // Pop with a result context.pop(result); </code></pre> <p>Never use <code>Navigator.of(context).push(...)</code>. It bypasses GoRouter and breaks deep links.</p> <h4 id="heading-router-definition">Router Definition</h4> <p>All routes are defined in <code>core/router/app_router.dart</code>:</p> <pre><code class="language-dart">final router = GoRouter( initialLocation: Routes.splash, redirect: _redirectLogic, routes: [ GoRoute( path: Routes.home, pageBuilder: (context, state) =&gt; NoTransitionPage( child: const HomeScreen(), ), ), GoRoute( path: Routes.profile, builder: (context, state) { final userId = state.pathParameters['userId']!; return ProfileScreen(userId: userId); }, ), ], ); </code></pre> <h4 id="heading-typed-parameters">Typed Parameters</h4> <p>Extract path parameters from <code>state.pathParameters</code>, and query parameters from <code>state.uri.queryParameters</code>. Never parse the path string manually.</p> <h2 id="heading-essential-dart-skills-every-developer-should-write">Essential Dart Skills Every Developer Should Write</h2> <p>Beyond Flutter-specific skills, pure Dart development benefits enormously from team-level skills. These apply to any Dart code: business logic, data processing, testing, or CLI tools.</p> <h3 id="heading-the-dart-model-and-freezed-skill">The Dart Model and Freezed Skill</h3> <pre><code class="language-markdown">--- name: dart-models-freezed description: Create immutable data models using the freezed package with json_serializable for serialization. Use when creating new data models, DTOs, request or response objects, value objects, or any Dart class that represents structured data. Applies when working with JSON parsing, API response mapping, or defining data structures. --- # Dart Models with Freezed All data models use the freezed package for immutability and code generation. </code></pre> <h4 id="heading-model-definition">Model Definition</h4> <pre><code class="language-dart">import 'package:freezed_annotation/freezed_annotation.dart'; part 'user_profile.freezed.dart'; part 'user_profile.g.dart'; @freezed class UserProfile with _$UserProfile { const factory UserProfile({ required String id, required String name, required String email, String? avatarUrl, @Default(false) bool isVerified, required DateTime createdAt, }) = _UserProfile; factory UserProfile.fromJson(Map&lt;String, dynamic&gt; json) =&gt; _$UserProfileFromJson(json); } </code></pre> <p><code>@freezed</code> triggers code generation that produces an immutable class with a named constructor, <code>copyWith</code> for creating modified copies, <code>==</code> and <code>hashCode</code> based on all fields, <code>toString</code> for debugging, and <code>fromJson</code>/<code>toJson</code> via <code>json_serializable</code>.</p> <p>The <code>part</code> directives are mandatory and must match the filename. <code>user_profile.dart</code> generates <code>user_profile.freezed.dart</code> and <code>user_profile.g.dart</code>.</p> <h4 id="heading-field-rules">Field Rules</h4> <p>Use <code>required</code> for fields that must always be present. Use <code>String?</code> (nullable) for optional fields. Use <code>@Default(value)</code> for fields with a sensible default that avoids nullability. And use <code>@JsonKey(name: 'field_name')</code> when the JSON field name differs from the Dart field name.</p> <h4 id="heading-after-adding-or-modifying-a-model">After Adding or Modifying a Model</h4> <p>Always run:</p> <pre><code class="language-bash">dart run build_runner build --delete-conflicting-outputs </code></pre> <p>Never manually edit <code>.freezed.dart</code> or <code>.g.dart</code> files. They're generated and will be overwritten on the next build.</p> <h4 id="heading-dtos-vs-domain-models">DTOs vs Domain Models</h4> <p>Data Transfer Objects (DTOs) live in <code>data/</code> and map directly to API shapes. Domain models live in <code>domain/</code> and represent the app's internal data model.</p> <p>A DTO may have fields like <code>created_at</code> (snake_case from API). The domain model has <code>createdAt</code> (camelCase). The repository maps from DTO to domain model.</p> <h3 id="heading-the-dart-pattern-matching-skill">The Dart Pattern Matching Skill</h3> <pre><code class="language-markdown">--- name: dart-pattern-matching-idiomatic description: Use Dart 3 pattern matching, switch expressions, and sealed class hierarchies for exhaustive control flow. Use when working with sealed classes, enums, discriminated unions, conditional logic on types, or any switch statement that could be a switch expression. Applies when refactoring if-else chains, handling multiple subtypes, or implementing business logic that branches on type. --- # Dart Pattern Matching Use Dart 3 pattern matching for all control flow that involves type discrimination, sealed class hierarchies, or structural decomposition of data. </code></pre> <h4 id="heading-switch-expressions-over-switch-statements">Switch Expressions Over Switch Statements</h4> <pre><code class="language-dart">// Do not do this (switch statement is an imperative flow) switch (state) { case ProfileLoading(): return const CircularProgressIndicator(); case ProfileLoaded(): return ProfileContent(profile: state.profile); case ProfileLoadFailure(): return ErrorView(message: state.message); default: return const SizedBox.shrink(); } // Do this (switch expression is a value, works in build methods) return switch (state) { ProfileInitial() =&gt; const SizedBox.shrink(), ProfileLoading() =&gt; const CircularProgressIndicator(), ProfileLoaded(:final profile) =&gt; ProfileContent(profile: profile), ProfileLoadFailure(:final message) =&gt; ErrorView(message: message), }; </code></pre> <p>Switch expressions are values, not statements. They work naturally as the argument to <code>return</code> or as the value of a variable. Sealed class hierarchies make them exhaustive: if you add a new state, the compiler tells you every switch expression that needs to handle it.</p> <h4 id="heading-destructuring-in-patterns">Destructuring in Patterns</h4> <pre><code class="language-dart">// Access fields directly in the pattern case ProfileLoaded(:final profile) =&gt; ProfileContent(profile: profile), // Equivalent to: case ProfileLoaded() =&gt; ProfileContent(profile: state.profile), </code></pre> <p>The <code>:final field</code> syntax inside a pattern binds the field's value directly in the case branch. This eliminates the need to access <code>state.profile</code> separately and makes the code more concise.</p> <h4 id="heading-guard-clauses">Guard Clauses</h4> <pre><code class="language-dart">return switch (state) { ProfileLoaded(:final profile) when profile.isVerified =&gt; VerifiedProfileView(profile: profile), ProfileLoaded(:final profile) =&gt; UnverifiedProfileView(profile: profile), _ =&gt; const LoadingView(), }; </code></pre> <p><code>when</code> adds a guard clause to a pattern. The case only matches when both the pattern matches and the guard condition is true. Guards allow fine-grained branching within a single type.</p> <h4 id="heading-record-patterns">Record Patterns</h4> <pre><code class="language-dart">// Matching on records final (name, age) = getUserInfo(); // In switch expressions final description = switch ((user.name, user.isAdmin)) { (final name, true) =&gt; '$name (Admin)', (final name, false) =&gt; name, }; </code></pre> <p>Records are structural tuples. Pattern matching on records extracts the components directly without named accessors.</p> <h4 id="heading-converting-if-else-chains">Converting If-Else Chains</h4> <p>When you see an if-else chain that branches on type or value, convert it to a switch expression:</p> <pre><code class="language-dart">// Do not do this String label; if (priority == Priority.high) { label = 'Urgent'; } else if (priority == Priority.medium) { label = 'Normal'; } else { label = 'Low'; } // Do this final label = switch (priority) { Priority.high =&gt; 'Urgent', Priority.medium =&gt; 'Normal', Priority.low =&gt; 'Low', }; </code></pre> <h3 id="heading-the-dart-testing-conventions-skill">The Dart Testing Conventions Skill</h3> <pre><code class="language-markdown">--- name: dart-testing-conventions description: Write Dart unit tests following package:test conventions with mocktail mocks, descriptive group/test naming, and correct async testing patterns. Use when writing any test file, adding tests to existing files, mocking dependencies, testing async functions, or verifying error handling behavior. --- # Dart Testing Conventions </code></pre> <h4 id="heading-test-file-structure">Test File Structure</h4> <pre><code class="language-dart">import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:myapp/features/profile/data/profile_repository_impl.dart'; import 'package:myapp/core/errors/app_failure.dart'; class MockProfileRemoteDataSource extends Mock implements ProfileRemoteDataSource {} class MockProfileLocalDataSource extends Mock implements ProfileLocalDataSource {} void main() { late MockProfileRemoteDataSource mockRemote; late MockProfileLocalDataSource mockLocal; late ProfileRepositoryImpl repository; setUp(() { mockRemote = MockProfileRemoteDataSource(); mockLocal = MockProfileLocalDataSource(); repository = ProfileRepositoryImpl( remote: mockRemote, local: mockLocal, ); }); group('ProfileRepositoryImpl', () { group('getProfile', () { test( 'returns Right(profile) when remote data source succeeds', () async { when(() =&gt; mockRemote.getProfile(any())) .thenAnswer((_) async =&gt; right(fakeProfileDto)); final result = await repository.getProfile('user123'); expect(result.isRight(), isTrue); expect(result.getOrElse(() =&gt; null)?.id, equals('user123')); }, ); test( 'returns Left(NetworkFailure) when remote throws network error', () async { when(() =&gt; mockRemote.getProfile(any())) .thenAnswer((_) async =&gt; left(NetworkFailure(message: 'No internet'))); final result = await repository.getProfile('user123'); expect(result.isLeft(), isTrue); expect(result.fold((f) =&gt; f, (_) =&gt; null), isA&lt;NetworkFailure&gt;()); }, ); }); }); } </code></pre> <h4 id="heading-test-naming">Test Naming</h4> <p>Use descriptive test names that follow the pattern <strong>"does X when Y"</strong> or <strong>"returns X when Y"</strong>.</p> <p><strong>Examples:</strong> <code>returns Right(profile) when remote data source succeeds</code>, <code>returns Left(NetworkFailure) when connection fails</code>, and <code>calls local data source when remote fails</code>.</p> <p>Avoid using <strong>"test"</strong> or <strong>"should"</strong> in test names. For example, use <code>returns profile when repository call succeeds</code> instead of <code>test that profile is returned correctly</code> or <code>should return profile when called</code>.</p> <h4 id="heading-mock-setup">Mock Setup</h4> <p>Create fresh mocks in <code>setUp</code>, not at the top level of <code>main</code>. This ensures state from one test can't leak into another.</p> <p>Use <code>registerFallbackValue</code> in <code>setUpAll</code> for any custom types passed to <code>any()</code>:</p> <pre><code class="language-dart">setUpAll(() { registerFallbackValue(const ProfileLoadRequested(userId: '')); registerFallbackValue(left&lt;AppFailure, UserProfile&gt;(const NotFoundFailure())); }); </code></pre> <h4 id="heading-async-testing">Async Testing</h4> <pre><code class="language-dart">// For Future results final result = await repository.getProfile('user123'); expect(result.isRight(), isTrue); // For Stream results expectLater( bloc.stream, emitsInOrder([ProfileLoading(), ProfileLoaded(profile: fakeProfile)]), ); </code></pre> <p>Always use <code>await</code> for Futures. Use <code>expectLater</code> with <code>emitsInOrder</code> for Streams. Don't use <code>await Future.delayed(...)</code> in tests. Use <code>pump()</code> for widget tests or mock the async behavior with <code>thenAnswer</code>.</p> <h2 id="heading-skills-for-architecture-and-large-codebases">Skills for Architecture and Large Codebases</h2> <p>As your Flutter project grows, the complexity of architectural decisions increases. These skills are designed for larger codebases where consistent architecture is especially important.</p> <h3 id="heading-the-performance-skill">The Performance Skill</h3> <pre><code class="language-markdown">--- name: flutter-performance description: Apply Flutter performance best practices including const widgets, selective rebuilds, lazy loading, and proper use of keys. Use when optimizing screens, implementing lists, adding animations, working with images, or any task where rendering performance, jank, frame rate, or memory usage is relevant. --- # Flutter Performance </code></pre> <h4 id="heading-const-widgets">Const Widgets</h4> <p>Every widget that can be const must be const. Every constructor that can be const must have a const constructor:</p> <pre><code class="language-dart">// Do not do this class UserAvatar extends StatelessWidget { UserAvatar({super.key, required this.url}); // Missing const final String url; @override Widget build(BuildContext context) { return CircleAvatar( // Missing const where possible backgroundImage: NetworkImage(url), ); } } // Do this class UserAvatar extends StatelessWidget { const UserAvatar({super.key, required this.url}); final String url; @override Widget build(BuildContext context) { return CircleAvatar( backgroundImage: NetworkImage(url), ); } } </code></pre> <h4 id="heading-list-performance">List Performance</h4> <p>Use <code>ListView.builder</code> for lists with unknown or large item counts. Never use <code>ListView</code> with <code>children</code> for lists that could grow beyond 20 items.</p> <pre><code class="language-dart">// Do not do this for variable-length lists ListView( children: items.map((item) =&gt; ItemCard(item: item)).toList(), ) // Do this ListView.builder( itemCount: items.length, itemBuilder: (context, index) =&gt; ItemCard(item: items[index]), ) </code></pre> <h4 id="heading-selective-rebuilds-with-blocselector">Selective Rebuilds with BlocSelector</h4> <p>When only part of a widget tree depends on part of a state, use BlocSelector to rebuild only the dependent widget:</p> <pre><code class="language-dart">// Do not do this (entire subtree rebuilds on any state change) BlocBuilder&lt;CartBloc, CartState&gt;( builder: (context, state) =&gt; CartBadge(count: state is CartLoaded ? state.itemCount : 0), ) // Do this (rebuilds only when item count changes) BlocSelector&lt;CartBloc, CartState, int&gt;( selector: (state) =&gt; state is CartLoaded ? state.itemCount : 0, builder: (context, count) =&gt; CartBadge(count: count), ) </code></pre> <h4 id="heading-image-optimization">Image Optimization</h4> <p>Use <code>cached_network_image</code> for network images. Never use <code>Image.network</code> directly. Use <code>cacheWidth</code> and <code>cacheHeight</code> to resize images at decode time for list items. Use WebP format on Android and HEIC/WebP on iOS for significantly smaller file sizes.</p> <h3 id="heading-the-accessibility-skill">The Accessibility Skill</h3> <pre><code class="language-markdown">--- name: flutter-accessibility description: Implement accessibility features including semantic labels, focus management, contrast requirements, and screen reader support. Use when creating interactive widgets, images, icons, form fields, or any element that needs to be usable by people with disabilities. Apply when working with Semantics, ExcludeSemantics, Focus, or FocusNode. --- # Flutter Accessibility </code></pre> <h4 id="heading-semantic-labels-on-interactive-elements">Semantic Labels on Interactive Elements</h4> <p>Every <code>IconButton</code>, <code>FloatingActionButton</code>, and <code>GestureDetector</code> that performs a meaningful action must have a semantic label:</p> <pre><code class="language-dart">// Do not do this IconButton( onPressed: _onShare, icon: const Icon(Icons.share), ) // Do this IconButton( onPressed: _onShare, icon: const Icon(Icons.share), tooltip: 'Share post', // Used as semantic label on mobile ) </code></pre> <h4 id="heading-images-and-decorative-icons">Images and Decorative Icons</h4> <p>Purely decorative icons and images must be marked as such so screen readers skip them:</p> <pre><code class="language-dart">// Decorative icon (no semantic value) Icon( Icons.star, semanticLabel: '', // Empty label marks it as decorative ) // Informative icon (has semantic value) Icon( Icons.warning, semanticLabel: 'Warning: action cannot be undone', ) </code></pre> <h4 id="heading-form-accessibility">Form Accessibility</h4> <p>All form fields must have labels that screen readers announce. Never rely solely on placeholder text for field identification:</p> <pre><code class="language-dart">TextFormField( decoration: const InputDecoration( labelText: 'Email address', // Screen readers announce this hintText: 'name@example.com', // Only visible when empty ), ) </code></pre> <h4 id="heading-minimum-touch-target-size">Minimum Touch Target Size</h4> <p>All interactive elements must be at least 48x48 dp. If the visual size is smaller, use <code>SizedBox</code> or <code>Padding</code> to expand the hit area:</p> <pre><code class="language-dart">SizedBox( width: 48, height: 48, child: IconButton( iconSize: 20, onPressed: _onClose, icon: const Icon(Icons.close), ), ) </code></pre> <h2 id="heading-advanced-skill-patterns">Advanced Skill Patterns</h2> <h3 id="heading-teaching-tool-usage-as-part-of-task-completion">Teaching Tool Usage as Part of Task Completion</h3> <p>Skills can make specific commands part of the definition of "task complete." This is one of the most powerful patterns because it closes the quality loop automatically:</p> <pre><code class="language-markdown">## Required Verification Steps After any code generation or modification task, always: 1. Run `dart format .` to format all Dart files 2. Run `flutter analyze` to check for analyzer errors and warnings 3. Run `flutter test` to verify no tests are broken by the changes 4. If any of the above produce errors, fix them before reporting the task as complete Do not report a task complete if any of these commands fail. </code></pre> <p>This pattern transforms the skill from a code generation guide into a full quality assurance workflow. The agent doesn't just write code: it validates the code against your quality bar before saying it's finished.</p> <h3 id="heading-conditional-rules-based-on-context">Conditional Rules Based on Context</h3> <p>Some rules apply only in certain circumstances. Express these with conditional phrasing that helps the agent apply them correctly:</p> <pre><code class="language-markdown">## Context-Dependent Rules When a widget initiates a network request: - Disable all interactive elements while the request is in flight - Show a loading indicator appropriate to the UI scope - Handle errors with a user-readable message - Re-enable interactive elements when the request completes (success or failure) When a Bloc handles multiple independent operations: - Create separate error states for each operation (not a single generic Error state) - Name each error state after the operation: ProfileLoadFailure, ProfileUpdateFailure When creating a widget that appears in a ListView: - Always provide a key - Use const constructors wherever possible - Consider using ListView.builder at the list level if the list may exceed 50 items </code></pre> <h3 id="heading-cross-referencing-skills">Cross-Referencing Skills</h3> <p>Complex tasks may require multiple skills working together. Reference related skills explicitly in your skill body so the agent knows to load them:</p> <pre><code class="language-markdown">## Related Skills When this skill's rules result in widget extraction, also apply the flutter-file-organization skill to determine the correct file location. When the extracted component requires state management, apply the flutter-bloc-state-management skill to determine whether it needs its own Bloc. When writing tests for code created using this skill, apply the dart-testing-conventions skill for test naming and structure. </code></pre> <h3 id="heading-skills-that-encode-hard-won-production-lessons">Skills That Encode Hard-Won Production Lessons</h3> <p>Some of the most valuable skill content comes from specific production incidents. Document the lesson from the incident as a skill rule with enough context that anyone (and any agent) understands why it exists:</p> <h4 id="heading-buildcontext-after-async-gaps-learned-from-production">BuildContext After Async Gaps (Learned from Production)</h4> <p>Always check mounted before using BuildContext after any await:</p> <pre><code class="language-dart">Future&lt;void&gt; _onSubmit() async { final result = await _repository.save(formData); // WRONG: context may be stale if widget was disposed during the await ScaffoldMessenger.of(context).showSnackBar(...); // CORRECT: check mounted first if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(...); } </code></pre> <p>This error is silent in development (the widget is usually still mounted by the time the async operation completes) but causes "FlutterError (looking up a deactivated widget's ancestor)" crashes in production where network latency is higher and users navigate away while operations are in flight.</p> <h2 id="heading-package-level-skills-teaching-the-agent-your-libraries">Package-Level Skills: Teaching the Agent Your Libraries</h2> <p>The <code>skills</code> CLI tool (available as a Dart package at <code>pub.dev/packages/skills</code>) enables a powerful pattern: installing skills directly from your project's package dependencies.</p> <pre><code class="language-bash"># Install the Dart skills CLI globally dart pub global activate skills # Install skills from all packages in your project that ship skills skills get </code></pre> <p>When you add a package to your <code>pubspec.yaml</code> and run <code>skills get</code>, the CLI searches each package in your dependency tree for a <code>skills/</code> directory and installs those skills automatically. This means package authors can ship their own usage instructions directly to agent users.</p> <h3 id="heading-why-this-matters">Why This Matters</h3> <p>Before package-level skills, adding a new package to a Flutter project meant the agent knew the package existed (from its training data) but might not know the current API, preferred usage patterns, or common mistakes. This led to agents hallucinating method names, using deprecated APIs, or missing the idiomatic usage pattern the package author intended.</p> <p>With package-level skills, the agent receives authoritative usage instructions directly from the people who wrote the package. When <code>go_router</code> ships a <code>skills/go-router-navigation.md</code> file, every Flutter team that runs <code>skills get</code> after adding GoRouter gets a skill that teaches the agent exactly how GoRouter works, from the GoRouter team.</p> <h3 id="heading-writing-skills-for-your-own-packages">Writing Skills for Your Own Packages</h3> <p>If you maintain internal Dart or Flutter packages that your team uses, shipping skills with them is a high-value investment:</p> <pre><code class="language-plaintext">my_design_system/ lib/ src/ components/ my_design_system.dart skills/ my-design-system-components.md &lt;- teaches agents how to use your components my-design-system-theming.md &lt;- teaches agents your theming system pubspec.yaml README.md </code></pre> <pre><code class="language-markdown">--- name: my-design-system-components description: Use the MyDesignSystem component library for UI elements. Use when creating any UI elements including buttons, cards, form fields, navigation elements, or any visual component. Apply instead of raw Material or Cupertino widgets wherever a design system component exists. --- # MyDesignSystem Component Usage Always use MyDesignSystem components instead of raw Flutter widgets where equivalents exist. ## Available Components DsButton replaces ElevatedButton, TextButton, and OutlinedButton. DsCard replaces Card. DsTextField replaces TextFormField. DsAvatar replaces CircleAvatar. DsChip replaces Chip. DsBottomSheet replaces showModalBottomSheet. ## DsButton Usage </code></pre> <p>Do not do this: <code>ElevatedButton( onPressed: _onSubmit, child: const Text('Submit'), )</code>.</p> <p>Do this: <code>DsButton( label: 'Submit', onPressed: _onSubmit, variant: DsButtonVariant.primary, )</code>.</p> <pre><code class="language-plaintext"> `DsButton.variant` accepts `primary`, `secondary`, `destructive`, and `ghost`. When loading, pass `isLoading: true` to show the button's built-in loading state. </code></pre> <p>When a developer on your team runs <code>skills get</code>, this skill installs automatically alongside any official Flutter or Dart skills, giving the agent complete knowledge of your internal component library.</p> <h2 id="heading-skills-vs-rules-vs-mcp-knowing-the-difference">Skills vs Rules vs MCP: Knowing the Difference</h2> <p>Agent skills exist alongside two other agent customization mechanisms: AI rules files and MCP servers. Understanding the distinct role of each helps you put knowledge in the right place.</p> <h3 id="heading-three-customization-mechanisms">Three Customization Mechanisms</h3> <h4 id="heading-1-ai-rules-always-in-context-project-wide-facts">1. AI rules (always in context, project-wide facts).</h4> <p><code>CLAUDE.md</code>, <code>AGENTS.md</code>, and <code>.cursorrules</code> should contain facts about the project that are always true. These files are loaded for every task and every session.</p> <p>They're best used for information such as the project name and package identifier, Flutter and Dart SDK versions, core packages like <code>flutter_bloc</code> and <code>go_router</code>, minimum platform versions such as Android API 24 and iOS 15, and the project's architecture style such as feature-first or clean architecture. Detailed how-to instructions shouldn't be placed here because those belong in skills.</p> <h4 id="heading-2-skills-agentsskillsmd-loaded-progressively">2. Skills (<code>.agents/skills/*.md</code>, loaded progressively).</h4> <p>Skills should contain instructions for how to perform a specific category of work. They're loaded only when the agent detects that they are relevant to the current task.</p> <p>They're best used for instructions such as how to organize Flutter files, how to implement BLoC state management, how to write tests, how to handle errors, and other task-specific patterns that aren't always relevant. Project-wide facts shouldn't be placed in skills because those belong in the project rules.</p> <h4 id="heading-3-mcp-servers-extend-the-agents-capabilities-with-tools">3. MCP servers (extend the agent's capabilities with tools).</h4> <p>MCP servers are configured through the agent-specific MCP configuration and are used to extend the agent's capabilities by providing access to tools and external data. Their tools are available throughout the session.</p> <p>They're best used for tasks such as looking up Flutter documentation through a Dart MCP server, retrieving package information from <code>pub.dev</code>, running Flutter commands in the project, reading logs from a connected device, and searching for code across the repository. Instructions, conventions, and project-specific rules shouldn't be placed in MCP servers because those belong in the rules and skills.</p> <p>A useful heuristic: if the information would be in a README, it probably belongs in a rules file or skill. If the information requires a network call or executing a program, it belongs in an MCP server. If the information is only relevant for a specific type of task, it belongs in a skill rather than a rules file.</p> <p>Another heuristic: context budget. Rules files are always in context, so they consume context budget on every task regardless of relevance. Keep rules files short (under 50 lines) and factual. Skills amortize their context cost because they are only loaded when relevant. MCP servers have their own cost model based on tool calls.</p> <h2 id="heading-organizing-skills-in-a-team">Organizing Skills in a Team</h2> <h3 id="heading-skills-as-shared-team-knowledge">Skills as Shared Team Knowledge</h3> <p>The <code>.agents/skills/</code> directory must be committed to your Git repository. When you commit a skill, every developer on the team gets it on their next <code>git pull</code>. When a new developer joins, they clone the repo and immediately have the accumulated skill knowledge the team has built. When someone writes a skill from a production incident, that lesson is preserved in the repository alongside the code it protects.</p> <p>This makes skills a living institutional knowledge system: the skill file is simultaneously the instruction for the AI agent and the documentation of the standard itself. Unlike a wiki page or a Confluence article, a skill is read by the tooling that actually generates code, not just by developers who may or may not remember to apply it.</p> <h3 id="heading-skill-review-process">Skill Review Process</h3> <p>Changes to skill files should go through the same pull request review process as code changes. A skill that encodes a wrong convention or expresses a rule too vaguely can produce incorrect output across the entire team's agent usage until it's corrected.</p> <p>Here's a skill review checklist, to check before merging a skill change:</p> <ul> <li><p>The description correctly and completely describes when this skill applies.</p> </li> <li><p>Every rule is specific enough to change agent behavior and isn't vague guidance.</p> </li> <li><p>Counterexamples are provided for patterns that are common in training data.</p> </li> <li><p>Code examples compile correctly in isolation.</p> </li> <li><p>The skill doesn't duplicate content in another skill.</p> </li> <li><p>The skill was tested by asking the agent to perform the relevant task and verifying that the output follows the skill's rules.</p> </li> <li><p>The skill has been reviewed by at least one other team member who would use it in their daily work.</p> </li> </ul> <h3 id="heading-keeping-skills-current">Keeping Skills Current</h3> <p>Skills become outdated when your team's conventions change: when you migrate from one navigation library to another, adopt a new testing framework, update your design system, or refactor your error handling approach. An outdated skill is worse than no skill because it actively steers the agent toward patterns you no longer use.</p> <p>Treat dependency upgrades as skill review triggers. When you upgrade <code>go_router</code> to a new major version, review the navigation skill to ensure it reflects the current API. When you adopt a new pattern from a team retrospective, update the relevant skill in the same PR.</p> <h3 id="heading-skill-discoverability-within-your-team">Skill Discoverability Within Your Team</h3> <p>As your skill library grows, developers need to be able to find the right skill for their task. Use consistent naming conventions and consider maintaining a brief skills index:</p> <pre><code class="language-markdown"># .agents/skills/README.md (not a skill, just an index) ## Flutter Skills flutter-feature-architecture -- Feature folder structure and layer rules flutter-bloc-state-management -- Bloc events, states, and widget integration flutter-file-organization -- File splitting, extraction, and naming flutter-error-handling -- Typed failures and Either return types flutter-navigation -- GoRouter routes, navigation methods, deep links flutter-theming -- Design tokens, color extensions, spacing constants flutter-testing -- Widget tests, Bloc tests, and test naming flutter-accessibility -- Semantic labels, focus, and touch targets flutter-performance -- Const widgets, selective rebuilds, list optimization ## Dart Skills dart-models-freezed -- Freezed models, json_serializable, DTOs dart-testing-conventions -- package:test conventions, mocktail, async testing dart-pattern-matching-idiomatic -- Switch expressions, sealed classes, destructuring dart-run-static-analysis -- analysis_options.yaml, dart analyze, dart fix </code></pre> <p>This index isn't read by agents (it's a <code>README.md</code>, not a skill file). It's for developers who are new to the project and want to know what skills exist before asking the agent to perform tasks.</p> <h2 id="heading-best-practices-for-writing-skills">Best Practices for Writing Skills</h2> <h3 id="heading-start-from-real-mistakes-not-ideal-patterns">Start from Real Mistakes, Not Ideal Patterns</h3> <p>The most effective skills come from observing AI-generated code that was wrong in a specific, reproducible way. The mistake is evidence that the agent's default behavior needs correction for your project. Every time you manually fix AI output, that fix is a skill rule.</p> <p>Ideal-pattern skills ("here is how Bloc should work in theory") are less effective than mistake-correction skills ("the agent always produces X but we need Y, so the rule is Z"). The mistake tells you where the training data diverges from your conventions. The rule corrects it.</p> <h3 id="heading-test-skills-before-committing">Test Skills Before Committing</h3> <p>After writing a skill, test it by asking your agent to perform the task the skill covers. Ask the agent to create a new screen with Bloc state management, or split a large file, or write unit tests for a repository. Then verify that the output follows every rule in your skill.</p> <p>Rules that aren't being followed need to be either more explicit, given a counterexample, or combined with a more specific description that helps the agent recognize when to load the skill.</p> <h3 id="heading-one-skill-per-domain-of-expertise">One Skill per Domain of Expertise</h3> <p>Resist the temptation to write one large skill that covers everything. A skill per domain (file organization, state management, testing, theming, navigation, error handling) is easier to maintain, loads progressively (so each skill is only in context when relevant), and is easier to share with other teams or publish as a community resource.</p> <h3 id="heading-write-the-description-with-trigger-word-richness">Write the Description with Trigger-Word Richness</h3> <p>The description is the only part of a skill that is always read. Pack it with the specific trigger words and phrases that indicate the skill is relevant:</p> <pre><code class="language-yaml"># Trigger-poor description description: How to set up navigation in Flutter. # Trigger-rich description description: Implement navigation using GoRouter in Flutter apps. Use when adding routes, navigating between screens, setting up deep links, handling authentication redirects, configuring nested navigation, working with ShellRoutes, or any task involving Navigator, route, path, deep link, URL, back button, or go_router package. </code></pre> <p>The trigger-rich description will match a much wider range of task descriptions, ensuring the skill loads when it is relevant rather than only on exact phrase matches.</p> <h2 id="heading-common-mistakes-when-writing-skills">Common Mistakes When Writing Skills</h2> <h3 id="heading-rules-that-are-too-vague-to-change-behavior">Rules That Are Too Vague to Change Behavior</h3> <pre><code class="language-markdown"># These change nothing: the agent was already trying to do these - Write clean, maintainable code. - Follow Flutter best practices. - Use the appropriate state management solution. - Organize files logically. # These change specific behavior: the agent was doing something different - Place every extracted widget class in the widgets/ subdirectory of its feature folder. - Name BlocEvent subclasses as past-tense verb phrases: ProfileLoadRequested, not LoadProfile. - Never use Navigator.push; use context.go() or context.push() from GoRouter. - Mark every widget constructor parameter with required unless it has a default value. </code></pre> <p>Vague rules describe aspirations. Specific rules describe concrete, verifiable behaviors. Every rule in a skill should answer the question: "What would an agent do differently after reading this rule compared to before?"</p> <h3 id="heading-missing-the-counterexample-for-high-frequency-wrong-patterns">Missing the Counterexample for High-Frequency Wrong Patterns</h3> <p>Some wrong patterns appear millions of times in training data. An agent that has learned <code>_buildHeaderSection()</code> as a valid Flutter pattern from thousands of examples may not abandon it based on a text rule alone.</p> <p>Show the exact code the agent would produce and contrast it with the code you want. This is effective because the agent recognizes the specific code pattern, and the contrast communicates the rule at the code level, not just the text level.</p> <h3 id="heading-descriptions-that-dont-trigger-on-the-right-tasks">Descriptions That Don't Trigger on the Right Tasks</h3> <p>A skill about Bloc state management that has a description saying "implement state management" won't load when someone asks "add a loading state to the checkout screen." The description needs to include "loading state" as a trigger phrase.</p> <p>Test your descriptions by thinking about the variety of ways someone would describe tasks that need this skill, and ensure the description includes trigger phrases from all of those ways.</p> <h3 id="heading-not-committing-skills-to-version-control">Not Committing Skills to Version Control</h3> <p>Skills left on a single developer's machine are personal notes, not team knowledge. Committed skills are institutional knowledge that new hires get from day one, that agent users across the team benefit from without separate setup, and that can be reviewed, improved, and maintained like code. Always commit <code>.agents/skills/</code> to Git.</p> <h3 id="heading-writing-skills-that-are-too-prescriptive">Writing Skills That Are Too Prescriptive</h3> <p>A skill should encode conventions, not dictate every possible implementation decision. If your skill specifies the exact pixel dimensions of a widget, the exact color of a specific loading indicator, or the exact parameter order of a constructor, you're over-specifying in ways that prevent the agent from making reasonable decisions in novel situations.</p> <p>Skills should capture the structural and architectural patterns that are genuinely inconsistent without guidance. Implementation details that have many equally valid choices shouldn't be in skills.</p> <h2 id="heading-conclusion">Conclusion</h2> <p>The shift to agentic development in Flutter isn't about replacing developers. It's about multiplying what developers can accomplish.</p> <p>An AI agent with strong skills can draft a complete, architecture-correct feature implementation that follows your team's exact conventions in minutes. A senior developer reviews it, adjusts, and ships. The skill is what bridges the gap between the agent's general knowledge and your team's specific standards.</p> <p>What makes skills genuinely powerful is that they're the only part of the AI development workflow that contains knowledge the model wasn't trained on. The model has learned from millions of lines of public Flutter and Dart code. But it has never seen your codebase. It has never made a mistake in your project and been corrected. It has never attended your team's architecture discussions or retrospectives. It doesn't know that your team tried one pattern, found it painful, and deliberately chose a different one. Your skills are the container for all of that knowledge.</p> <p>The official Flutter skills from <code>github.com/flutter/agent-plugins</code> and the official Dart skills from <code>github.com/dart-lang/skills</code> give you a production-quality starting point that covers the most common Flutter and Dart development patterns. The <code>skills</code> CLI tool makes installing them as simple as a single npm command. The package-level skills system means your dependencies can ship their own usage instructions and update them as the package evolves.</p> <p>But the skills you write yourself, drawn from your own production incidents, your own code review feedback, and your own architectural decisions, are the ones with the highest leverage. They encode knowledge that's irreplaceable because it can't be found in any public repository.</p> <p>A rule like "never separate a StatefulWidget from its State class" comes from understanding Flutter's compilation model at a level that most training data does not communicate. A rule like "use sealed class hierarchies with final concrete classes for all Bloc events and states" comes from understanding both Dart 3's type system and the real-world benefits of exhaustive switching. A rule like "check mounted before using BuildContext after any await" comes from seeing the specific crash that happens in production when this rule is violated.</p> <p>These rules, drawn from your experience, documented as skills, and committed to your repository, transform your AI agent from a generalist Flutter developer into a developer who knows your project. That transformation is worth every minute spent writing the skills.</p> <h2 id="heading-references">References</h2> <p><strong>Agent skills for Flutter and Dart (Flutter Documentation):</strong> Comprehensive guide to agent skills including the progressive disclosure model, official repositories, and universal installation commands. <a href="https://docs.flutter.dev/ai/agent-skills">https://docs.flutter.dev/ai/agent-skills</a></p> <p><strong>Get Started with AI in Flutter (Flutter Documentation):</strong> Step-by-step setup guide for Claude Code, Antigravity, Codex, Cursor, and other agents including the official Flutter plugin installation instructions for each tool. <a href="https://docs.flutter.dev/ai/get-started">https://docs.flutter.dev/ai/get-started</a></p> <p><strong>Flutter Agent Plugins Repository (GitHub):</strong> The official repository of Flutter agent skills maintained by the Flutter team, covering responsive layouts, GoRouter navigation, JSON serialization, widget testing, integration testing, BLoC patterns, and more. <a href="https://github.com/flutter/agent-plugins">https://github.com/flutter/agent-plugins</a></p> <p><strong>Dart Skills Repository (GitHub):</strong> The official repository of Dart agent skills maintained by the Dart team, covering unit testing, static analysis, package tooling, pattern matching, CLI apps, native assets, and more. <a href="https://github.com/dart-lang/skills">https://github.com/dart-lang/skills</a></p> <p><strong>Flutter AI Rules Documentation (Flutter Documentation):</strong> Documentation for project-wide AI rules files (CLAUDE.md, AGENTS.md, .cursorrules) and how they complement skills. <a href="https://docs.flutter.dev/ai/ai-rules">https://docs.flutter.dev/ai/ai-rules</a></p> <p><strong>The Agent Skills Specification:</strong> The specification site that defines the universal SKILL.md format, directory conventions, and agent compatibility requirements. The source of truth for the skills standard. <a href="https://agentskills.io">https://agentskills.io</a></p> <p><strong>skills Dart Package (pub.dev):</strong> The Dart CLI tool for installing agent skills from project dependencies. Enables package authors to ship skills alongside their packages and teams to install them automatically. <a href="https://pub.dev/packages/skills">https://pub.dev/packages/skills</a></p> <p><strong>skills CLI (npm):</strong> The npm-distributed CLI for installing agent skills from GitHub repositories. Used for the canonical <code>npx skills add flutter/agent-plugins</code> installation command. <a href="https://www.npmjs.com/package/skills">https://www.npmjs.com/package/skills</a></p> <p><strong>skills-registry Serverpod:</strong> A collection of agent skills for popular Dart and Flutter packages that do not yet ship their own skills, including Riverpod, flutter-shadcn-ui, and others. Maintained by the Serverpod team. <a href="https://github.com/serverpod/skills-registry">https://github.com/serverpod/skills-registry</a></p> <p><strong>dhruvanbhalara/skills Premium Flutter Skills Documentation:</strong> An extensive documentation project covering the full list of available Flutter agent skills with detailed descriptions of what each skill covers and teaches. <a href="https://github.com/dhruvanbhalara/skills">https://github.com/dhruvanbhalara/skills</a></p>

How to apply

  1. 1 Read the full details above and confirm you meet the eligibility criteria.
  2. 2 Prepare your documents — an updated CV, and any cover letter, proposal or certificates required.
  3. 3 Click Apply on official site to complete your application on Atuoha Anthony’s official page.
  4. 4 Submit as early as possible — many close once filled.
Apply on official site

Sourced from freecodecamp. Always verify details on the official website. Opportunihub never charges you to apply.

Frequently asked questions

How do I apply for How to Use Skills in Agentic Flutter Development: A Handbook for Devs?

Review the full details and eligibility on this page, prepare your documents, then use the “Apply on official site” button to complete your application on Atuoha Anthony’s official page.

Is this opportunity remote or location-based?

This opportunity is remote-friendly and open to applicants who can work from anywhere.

Is How to Use Skills in Agentic Flutter Development: A Handbook for Devs free to apply for?

Opportunihub lists this Course for free. Legitimate Courses do not ask for payment to apply — never pay a fee to submit an application.