glosings0n / codelabs / Agentic Coding for Flutter: Mastering Dart MCP Server, AGENTS.md & Custom Skills
40 min left
Step 1 of 8 | Context, Learning Goals & System Requirements Last updated:

Eliminating the AI Verification Tax with Agentic Engineering

1. Context & The "Why": Escaping the Vibe-Coding Verification Tax

Over 79% of Flutter and Dart developers now use AI coding assistants daily. Yet across production mobile teams, a painful paradox has emerged: while raw code generation is faster than ever, engineers spend hours fixing hallucinated pub.dev APIs, unbounded RenderFlex layout overflows, leaked BuildContext references across async gaps, and broken layer boundaries where database DTOs leak directly into UI widgets.

This hidden productivity drain is known as The Verification Tax. It happens when developers rely on unconstrained Vibe-Coding (prompting an LLM in a vacuum without compiler grounding or architectural contracts) instead of Agentic Engineering.

Core Thesis of Agentic Engineering

An AI coding agent is only as reliable as the tools it can invoke, the architectural constitution it must obey, and the deterministic verification loop that gates its output.

Engineering Dimension Unconstrained Vibe-Coding Deterministic Agentic Engineering
Compiler & Runtime Awareness Blind text prediction; guesses method signatures Live AST & VM introspection via dart mcp-server
Architectural Boundaries Imports data/network classes directly inside Widgets Governed by a root AGENTS.md constitution
Context Window Hygiene Bloated prompts that degrade model reasoning Modular on-demand SKILL.md procedural files
Domain Guardrails Manual human code review after every prompt Custom project MCP tools built with genkit_mcp
Definition of Done "It looks plausible in the chat window" flutter analyze --fatal-infos & flutter test pass

2. Learning Goals & What You Will Build

By the end of this hands-on codelab, you will design and verify a complete Agentic Flutter Engineering Workspace:

  1. Wire the Official Dart & Flutter MCP Server (dart mcp-server): Connect your IDE or CLI coding agent to the Dart analyzer, pub package manager, live Widget Inspector, and stateful Hot Reload over the Model Context Protocol.
  2. Engineer a Zero-Drift AGENTS.md Constitution: Write a concise, high-signal project constitution enforcing strict Domain/Application/Presentation separation, immutable entities, explicit Result<T> error handling, and design token invariants.
  3. Build On-Demand Procedural Skills (SKILL.md): Package complex, multi-step feature scaffolding rules into .agents/skills/flutter-clean-feature/SKILL.md so agents load procedural instructions only when needed.
  4. Implement a Governed Flutter Telemetry Feature: Build a 100% self-contained Flutter application using flutter_bloc and Dart 3 sealed classes that passes strict static analysis with zero warnings.
  5. Build a Custom Architecture-Guard MCP Server in Dart (genkit_mcp): Write a custom MCP server in Dart that exposes a live checkLayerImports tool and a project://design-tokens resource to any MCP-compatible agent.
  6. Enforce Automated Verification Loops: Write deterministic widget tests and cross-platform pre-commit verification scripts that prevent broken agent code from ever landing in your branch.

3. Prerequisites & System Requirements

  • Dart SDK: >=3.8.0 (includes the built-in dart mcp-server command).
  • Flutter SDK: >=3.29.0 (stable channel recommended).
  • Git: >=2.40.0 for workspace state inspection.
  • MCP-Compatible Agent Host: Google Antigravity, Gemini CLI, Cursor, VS Code with GitHub Copilot Agent mode, or Claude Code.

Run the pre-flight verification commands below in your terminal to confirm your local SDK toolchain is ready:

1. Contexte & Pourquoi : Échapper à la Taxe de Vérification du Vibe-Coding

Plus de 79 % des développeurs Flutter et Dart utilisent aujourd'hui des assistants IA au quotidien. Pourtant, au sein des équipes mobiles en production, un paradoxe coûteux est apparu : bien que la génération brute de code soit plus rapide que jamais, les ingénieurs passent des heures à corriger des APIs pub.dev hallucinées, des erreurs de rendu RenderFlex non bornées, des fuites de BuildContext après des appels asynchrones, et des violations d'architecture où les DTOs de base de données fuient directement dans les widgets UI.

Cette perte de productivité cachée s'appelle la Taxe de Vérification (Verification Tax). Elle survient lorsque les développeurs s'appuient sur du Vibe-Coding non contraint (solliciter un LLM à l'aveugle sans ancrage dans le compilateur ni contrat architectural) au lieu de pratiquer l'Agentic Engineering.

Thèse Centrale de l'Agentic Engineering

Un agent de programmation IA n'est fiable que dans la mesure des outils qu'il peut invoquer, de la constitution architecturale à laquelle il obéit, et de la boucle de vérification déterministe qui valide son code.

Dimension d'Ingénierie Vibe-Coding Non Contraint Agentic Engineering Déterministe
Conscience du Compilateur & VM Prédiction textuelle à l'aveugle ; devine les signatures Introspection AST & VM en direct via dart mcp-server
Frontières Architecturales Importe les classes réseau/données directement dans les Widgets Gouverné par une constitution racine AGENTS.md
Hygiène de la Fenêtre de Contexte Prompts surchargés qui dégradent le raisonnement Fichiers procéduraux modulaires SKILL.md chargés à la demande
Garde-fous Métier Revue manuelle humaine après chaque prompt Outils MCP personnalisés construits avec genkit_mcp
Définition de "Terminé" "Le code a l'air plausible dans le chat" flutter analyze --fatal-infos & flutter test passent à 100 %

2. Objectifs & Ce que vous allez construire

À la fin de ce codelab pratique, vous aurez conçu et vérifié un Environnement Flutter d'Agentic Engineering complet :

  1. Connecter le Serveur MCP Officiel Dart & Flutter (dart mcp-server) : Relier votre IDE ou agent CLI à l'analyseur Dart, au gestionnaire de paquets pub, au Widget Inspector en direct et au Hot Reload via le Model Context Protocol.
  2. Concevoir une Constitution AGENTS.md Zéro-Dérive : Rédiger une constitution de projet concise et déterministe imposant la séparation Domain/Application/Presentation, l'immuabilité des entités, la gestion d'erreurs explicite via Result<T> et le respect des jetons de design.
  3. Créer des Skills Procéduraux à la Demande (SKILL.md) : Encapsuler les règles de génération de fonctionnalités dans .agents/skills/flutter-clean-feature/SKILL.md afin que l'agent ne les charge qu'au moment opportun.
  4. Implémenter une Feature Flutter Gouvernée : Construire une application Flutter de télémétrie 100 % autonome utilisant flutter_bloc et les classes scellées Dart 3, compilant sans aucun avertissement d'analyse statique.
  5. Construire un Serveur MCP d'Architecture en Dart (genkit_mcp) : Écrire un serveur MCP personnalisé en Dart exposant un outil checkLayerImports et une ressource project://design-tokens à tout agent compatible MCP.
  6. Imposer des Boucles de Vérification Automatisées : Écrire des tests de widgets déterministes et des scripts multiplateformes qui empêchent tout code défectueux d'entrer dans votre branche.

3. Prérequis & Configuration Système

  • SDK Dart : >=3.8.0 (inclut nativement la commande dart mcp-server).
  • SDK Flutter : >=3.29.0 (canal stable recommandé).
  • Git : >=2.40.0 pour l'inspection de l'espace de travail.
  • Hôte Agent Compatible MCP : Google Antigravity, Gemini CLI, Cursor, VS Code avec GitHub Copilot Agent, ou Claude Code.

Exécutez les commandes de vérification pré-vol ci-dessous dans votre terminal pour confirmer que votre chaîne d'outils SDK est prête :

Terminal | Pre-Flight SDK & MCP Check (macOS / Linux / Windows PowerShell) SH / PS1
# 1. Verify Flutter & Dart SDK versions (Dart >= 3.8.0 required)
flutter --version
dart --version

# 2. Verify the built-in Dart MCP Server binary responds
dart mcp-server --help

# 3. Create a clean workspace for this codelab
flutter create --empty agentic_telemetry_demo
cd agentic_telemetry_demo
flutter pub add flutter_bloc
Step 2 of 8 | Model Context Protocol Integration

Connecting the Official Dart & Flutter MCP Server

Without tool grounding, an LLM treats Dart like untyped plain text. It frequently invents named parameters that do not exist in your installed version of flutter_bloc or MaterialApp, and it cannot see runtime layout exceptions thrown inside your running emulator.

Starting with Dart 3.8, the Dart SDK ships with a first-party Model Context Protocol server invoked via dart mcp-server. It bridges your coding agent directly to the Dart Analysis Server, the pub package manager, and the Dart Tooling Daemon (DTD) connected to your running Flutter VM.

The 5 Live Superpowers of dart mcp-server

MCP Tool Category Exposed MCP Tool Name What It Replaces in Agent Workflows
Static Analysis analyze_files Eliminates hallucinated imports, type mismatches, and unused variables before you even read the diff.
AST Symbol Lookup signature_help / hover Lets the agent inspect exact constructor parameters and doc comments from your local .pub-cache.
Package Management pub / pub_dev_search Searches pub.dev for current package versions and executes flutter pub add safely.
Runtime Inspection get_widget_tree / get_runtime_errors Reads live RenderFlex overflow trees and active stack traces directly from the running Flutter app.
Stateful Iteration hot_reload / hot_restart Applies edits to the running VM in sub-second cycles and verifies that runtime errors disappear.

1. Register dart mcp-server in Your Agent Host

Create the MCP configuration file in your project root for your preferred agent environment (such as .gemini/settings.json for Gemini CLI / Google Antigravity, or .cursor/mcp.json for Cursor):

Sans ancrage d'outils, un LLM traite le code Dart comme du simple texte non typé. Il invente régulièrement des paramètres nommés qui n'existent pas dans votre version installée de flutter_bloc ou MaterialApp, et il est aveugle aux exceptions de rendu déclenchées dans votre émulateur en cours d'exécution.

Depuis Dart 3.8, le SDK Dart intègre nativement un serveur officiel Model Context Protocol accessible via dart mcp-server. Il connecte directement votre agent de codage au serveur d'analyse Dart, au gestionnaire de paquets pub, ainsi qu'au Dart Tooling Daemon (DTD) relié à la machine virtuelle Flutter active.

Les 5 Super-Pouvoirs en Direct de dart mcp-server

Catégorie d'Outil MCP Outil MCP Exposé Ce qu'il remplace dans le flux de l'agent
Analyse Statique analyze_files Élimine les imports hallucinés, erreurs de typage et variables inutilisées avant même la lecture du diff.
Résolution AST signature_help / hover Permet à l'agent d'inspecter les constructeurs exacts et la documentation depuis votre .pub-cache local.
Gestion de Paquets pub / pub_dev_search Recherche les versions actuelles sur pub.dev et exécute flutter pub add proprement.
Inspection Runtime get_widget_tree / get_runtime_errors Lit l'arbre des widgets et les erreurs RenderFlex directement depuis l'application Flutter active.
Itération Stateful hot_reload / hot_restart Applique les modifications à la VM active en moins d'une seconde et vérifie la disparition des erreurs.

1. Enregistrer dart mcp-server dans votre Hôte Agent

Créez le fichier de configuration MCP à la racine de votre projet pour votre environnement d'agent (par exemple .gemini/settings.json pour Gemini CLI / Google Antigravity, ou .cursor/mcp.json pour Cursor) :

.gemini/settings.json | Workspace MCP Server Configuration JSON
{
  "mcpServers": {
    "dart": {
      "command": "dart",
      "args": [
        "mcp-server",
        "--force-roots-fallback"
      ],
      "env": {}
    }
  }
}
Why Pass --force-roots-fallback?

Some CLI and editor MCP clients do not yet implement the MCP roots/list capability natively. Passing --force-roots-fallback allows the agent to register workspace roots dynamically via tool calls so analyze_files and pub always resolve your project directory accurately.

2. Launching Flutter with the Dart Tooling Daemon (DTD)

When you run your Flutter app during an agentic coding session, pass --print-dtd so your agent (or IDE) can attach to the live VM service and inspect the widget tree or trigger Hot Reloads:

Pourquoi passer l'argument --force-roots-fallback ?

Certains clients MCP en ligne de commande ou éditeurs n'implémentent pas encore nativement la capacité MCP roots/list. L'option --force-roots-fallback permet à l'agent d'enregistrer dynamiquement la racine du projet via un appel d'outil afin que analyze_files et pub ciblent toujours le bon dossier.

2. Lancer Flutter avec le Dart Tooling Daemon (DTD)

Lorsque vous lancez votre application Flutter pendant une session de codage agentique, passez le drapeau --print-dtd pour permettre à votre agent d'inspecter l'arbre de widgets ou de déclencher un Hot Reload :

Terminal | Launch Flutter with DTD URI Exposed for MCP BASH
# Start Flutter on your target device/emulator and print the DTD connection URI
flutter run --print-dtd

# Example output:
# The Dart Tooling Daemon is listening on ws://127.0.0.1:58432/AbCdEfGhIjKl=/
# Your agent passes this URI to the `connect_dart_tooling_daemon` MCP tool!
Step 3 of 8 | Project Governance & System Invariants

Engineering a Zero-Drift AGENTS.md Constitution

dart mcp-server tells the agent whether your code compiles, but it does not tell the agent how your team architects software. Without explicit architectural rules, an agent will happily mix HTTP calls inside StatefulWidget.build(), hardcode hex colors across 40 files, or swallow exceptions with empty catch (_) {} blocks.

AGENTS.md is the open standard for repository-level instructions read automatically at the start of every session by modern coding agents. Think of AGENTS.md as a compiler constitution for agent behavior: it must be concise, unambiguous, and strictly focused on global invariants that apply to every task in the repository.

Anatomy of a High-Signal Flutter AGENTS.md

  • Keep it under 150 lines: Every token in AGENTS.md is injected into every conversation turn. Put universal invariants in AGENTS.md and move task-specific playbooks into modular SKILL.md files (Step 4).
  • Define Strict Layer Dependency Direction: Explicitly forbid lib/domain/ from importing package:flutter/ or data-layer DTOs.
  • Mandate Exhaustive Pattern Matching: Require Dart 3 sealed class hierarchies for states and results so the compiler forces exhaustive switch expressions in widgets.
  • Define the Verification Gate: Instruct the agent that no task is complete until analyze_files (or flutter analyze --fatal-infos) and flutter test return zero issues.

Create the following AGENTS.md file at the root of your agentic_telemetry_demo workspace:

dart mcp-server indique à l'agent si votre code compile, mais ne lui dit pas comment votre équipe architecture ses logiciels. Sans règles architecturales explicites, un agent mélangera volontiers des appels HTTP directement dans StatefulWidget.build(), codera en dur des couleurs hexadécimales dans 40 fichiers, ou masquera les exceptions avec des blocs catch (_) {} vides.

AGENTS.md est le standard ouvert d'instructions au niveau du dépôt, lu automatiquement au début de chaque session par les agents de codage modernes. Considérez AGENTS.md comme une constitution de compilateur pour le comportement de l'agent : elle doit être concise, sans ambiguïté et strictement concentrée sur les invariants globaux qui s'appliquent à chaque tâche du dépôt.

Anatomie d'un AGENTS.md Flutter à Fort Signal

  • Rester sous les 150 lignes : Chaque jeton de AGENTS.md est injecté à chaque tour de conversation. Placez uniquement les invariants universels dans AGENTS.md et déportez les procédures spécifiques dans des fichiers modulaires SKILL.md (Étape 4).
  • Définir la Direction Stricte des Dépendances : Interdire explicitement à lib/domain/ d'importer package:flutter/ ou des DTOs de la couche de données.
  • Imposer le Pattern Matching Exhaustif : Exiger des hiérarchies sealed class Dart 3 pour les états et résultats afin que le compilateur impose des expressions switch exhaustives dans les widgets.
  • Définir la Porte de Vérification : Instruire l'agent qu'aucune tâche n'est terminée tant que analyze_files (ou flutter analyze --fatal-infos) et flutter test ne retournent pas zéro erreur.

Créez le fichier AGENTS.md suivant à la racine de votre projet agentic_telemetry_demo :

AGENTS.md | Root Architectural Constitution for Flutter Agents MARKDOWN
# AGENTS.md | Flutter & Dart Engineering Constitution

These architectural and verification rules are mandatory across all files in this repository.

## 1. Strict Layered Architecture & Dependency Rule
- `lib/domain/`: Pure Dart only. Contains immutable entities, value objects, `sealed class Result<T>`, and `abstract interface class` repository contracts.
  - FORBIDDEN in `lib/domain/`: Never import `package:flutter/...`, `package:flutter_bloc/...`, HTTP clients, or JSON serialization packages.
- `lib/application/`: State management (`Cubit` / `Bloc`) and use-case orchestration.
  - Every state hierarchy MUST be a `sealed class` with `final class` subtypes (`Initial`, `Loading`, `Loaded`, `Error`).
- `lib/main.dart` & `lib/presentation/`: Flutter widgets consuming `Cubit`/`Bloc` states via exhaustive Dart 3 `switch` expressions.
  - Widgets MUST NOT instantiate repositories directly or perform raw I/O inside `build()`.

## 2. Error Handling & Type Safety Invariants
- Never throw raw strings or swallow exceptions with empty `catch (_) {}` blocks.
- All repository methods that can fail MUST return `Future<Result<T>>` (`Success<T>` or `Failure<T>` with `message` and `code`).
- Always use `const` constructors wherever possible and enable `prefer_const_constructors`.

## 3. UI Design Tokens & Accessibility
- Surface Primary: `Color(0xFF070A13)` | Surface Card: `Color(0xFF0F1424)` | Border: `Color(0xFF202B46)`
- Accent Cyan: `Color(0xFF00B0FF)` | Accent Blue: `Color(0xFF02569B)`
- Status Nominal: `Color(0xFF10B981)` | Degraded: `Color(0xFFF59E0B)` | Critical: `Color(0xFFEF4444)`
- Zero gradients: use solid fills and crisp `1px` borders (`Border.all(color: Color(0xFF202B46))`).
- Every interactive `IconButton` or action button MUST include a stable `Key(...)` for deterministic widget testing and a descriptive `tooltip`.

## 4. Mandatory MCP & Verification Workflow
1. Before adding any dependency, query `pub_dev_search` via the Dart MCP server.
2. After creating or modifying any Dart file, run the `analyze_files` MCP tool (or `flutter analyze --fatal-infos`).
3. Never report a task as finished until `flutter analyze --fatal-infos` and `flutter test` pass with 0 warnings and 0 failures.
Step 4 of 8 | Progressive Context Disclosure

Creating On-Demand Procedural Skills (SKILL.md)

A common mistake when adopting AI agents is dumping 3,000 lines of feature templates, database migration guides, release checklists, and widget testing recipes into AGENTS.md. When your root instruction file grows too large, the agent suffers from instruction dilution (attention degradation in the middle of long prompts) and burns unnecessary context tokens on every single chat turn.

The solution is Agent Skills: self-contained folders inside .agents/skills/<skill-name>/SKILL.md. Each skill begins with a compact YAML frontmatter containing only name and description. At startup, the agent reads only the 2-line YAML frontmatter of each skill. When your prompt matches a skill's description (for example, scaffolding a new Clean Architecture feature), the agent dynamically loads the full SKILL.md body into context!

Rule of Thumb: AGENTS.md vs. SKILL.md

Put "What must always be true" (invariants, forbidden imports, verification gates) in AGENTS.md. Put "How to execute a specific workflow step-by-step" (scaffolding a feature, writing FFI bindings, migrating tests) in .agents/skills/<name>/SKILL.md.

Create the flutter-clean-feature Skill

Create the file .agents/skills/flutter-clean-feature/SKILL.md in your workspace:

Une erreur fréquente lors de l'adoption d'agents IA consiste à déverser 3 000 lignes de modèles de fonctionnalités, de guides de migration SQLite, de checklists de publication et de recettes de tests dans AGENTS.md. Lorsque votre fichier d'instructions racine devient trop volumineux, l'agent souffre de dilution d'instructions (dégradation de l'attention au milieu de longs contextes) et consomme inutilement des milliers de jetons à chaque message.

La solution repose sur les Agent Skills : des dossiers modulaires situés dans .agents/skills/<nom-du-skill>/SKILL.md. Chaque skill commence par un en-tête YAML compact contenant uniquement name et description. Au démarrage, l'agent ne lit que ces deux lignes. Lorsque votre demande correspond à la description du skill (par exemple, générer une nouvelle fonctionnalité Clean Architecture), l'agent charge dynamiquement le contenu complet de SKILL.md !

Règle d'Or : AGENTS.md vs SKILL.md

Placez "Ce qui doit toujours être vrai" (invariants, imports interdits, portes de validation) dans AGENTS.md. Placez "Comment exécuter une procédure étape par étape" (créer une feature, générer des bindings FFI, écrire des tests) dans .agents/skills/<nom>/SKILL.md.

Créer le Skill flutter-clean-feature

Créez le fichier .agents/skills/flutter-clean-feature/SKILL.md dans votre espace de travail :

.agents/skills/flutter-clean-feature/SKILL.md | On-Demand Procedural Skill MARKDOWN
---
name: flutter-clean-feature
description: Step-by-step playbook for scaffolding, wiring, and widget-testing a layered Flutter feature with pure Dart domain models, Cubit state management, and exhaustive switch expressions. Use whenever creating or refactoring a Flutter feature.
---

# Flutter Clean Feature Scaffolding Playbook

Follow these exact steps in order whenever asked to create or update a feature:

## Step 1: Pure Domain Layer (`lib/domain/<feature>.dart`)
1. Define immutable entities with `final` fields, `const` constructors, and `copyWith`.
2. Compute derived domain properties (such as `HealthStatus`) via pure getters on the entity.
3. Define repository contracts as `abstract interface class` returning `Future<Result<T>>`.
4. Verify zero `package:flutter/` imports in this file.

## Step 2: Application State & Cubit (`lib/application/<feature>_cubit.dart`)
1. Declare a `sealed class <Feature>State` with `const` constructor.
2. Create four `final class` subtypes: `Initial`, `Loading`, `Loaded`, and `Error`.
3. In the `Cubit`, emit `Loading`, await the repository's `Result<T>`, and use an exhaustive `switch (result)` with pattern destructuring (`case Success(:final value):`, `case Failure(:final message, :final code):`).

## Step 3: Presentation Layer (`lib/main.dart` or `lib/presentation/`)
1. Inject the `Cubit` via `BlocProvider` and render state via `BlocBuilder`.
2. Use an exhaustive `switch (state)` expression inside `BlocBuilder.builder`.
3. Attach explicit `const Key('...')` identifiers to loading indicators, status badges, and action buttons.

## Step 4: Mandatory Verification
1. Write a `testWidgets` test in `test/<feature>_widget_test.dart` covering both the nominal `Loaded` path and the `Error` recovery path.
2. Call `analyze_files` via the Dart MCP server and run `flutter test`.
Step 5 of 8 | Clean Layered Implementation

Building the Reference Flutter Feature Under Agent Governance

With dart mcp-server active, AGENTS.md at the project root, and .agents/skills/flutter-clean-feature/SKILL.md registered, let us examine the exact 4-file Flutter application produced under these constraints. Every file below is 100% self-contained and verified against flutter analyze --fatal-infos.

1. Project Dependencies (pubspec.yaml)

Ensure your pubspec.yaml declares Dart ^3.8.0 and flutter_bloc:

Avec dart mcp-server actif, AGENTS.md à la racine du projet et .agents/skills/flutter-clean-feature/SKILL.md enregistré, examinons l'application Flutter complète en 4 fichiers produite sous ces contraintes. Chaque fichier ci-dessous est 100 % autonome et vérifié avec flutter analyze --fatal-infos.

1. Dépendances du Projet (pubspec.yaml)

Assurez-vous que votre fichier pubspec.yaml déclare Dart ^3.8.0 et flutter_bloc :

pubspec.yaml | Governed Flutter Workspace Manifest YAML
name: agentic_telemetry_demo
description: Deterministic Flutter feature governed by AGENTS.md, Skills, and Dart MCP.
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: ^3.8.0

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^9.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0

flutter:
  uses-material-design: true

2. Pure Dart Domain Layer (lib/domain/telemetry_snapshot.dart)

Notice how this file obeys Section 1 and Section 2 of AGENTS.md: it has zero Flutter or third-party imports, defines a sealed Result<T> algebraic type, computes HealthStatus deterministically, and exposes an abstract interface class TelemetryRepository:

2. Couche Domaine Pure Dart (lib/domain/telemetry_snapshot.dart)

Observez comment ce fichier respecte les sections 1 et 2 de AGENTS.md : il ne contient aucun import Flutter ou tiers, définit un type algébrique scellé Result<T>, calcule HealthStatus de façon déterministe et expose un contrat abstract interface class TelemetryRepository :

lib/domain/telemetry_snapshot.dart | Pure Domain Entities & Contracts DART
// lib/domain/telemetry_snapshot.dart
// Pure Dart domain layer: zero Flutter UI or platform imports allowed.

sealed class Result<T> {
  const Result();
}

final class Success<T> extends Result<T> {
  final T value;
  const Success(this.value);
}

final class Failure<T> extends Result<T> {
  final String message;
  final String code;
  const Failure({required this.message, required this.code});
}

enum HealthStatus { nominal, degraded, critical }

final class TelemetrySnapshot {
  final String nodeId;
  final int latencyMs;
  final double memoryUsageMb;
  final int activeIsolates;
  final DateTime recordedAt;

  const TelemetrySnapshot({
    required this.nodeId,
    required this.latencyMs,
    required this.memoryUsageMb,
    required this.activeIsolates,
    required this.recordedAt,
  });

  HealthStatus get status {
    if (latencyMs >= 500 || memoryUsageMb >= 512.0) {
      return HealthStatus.critical;
    }
    if (latencyMs >= 200 || memoryUsageMb >= 256.0) {
      return HealthStatus.degraded;
    }
    return HealthStatus.nominal;
  }

  TelemetrySnapshot copyWith({
    String? nodeId,
    int? latencyMs,
    double? memoryUsageMb,
    int? activeIsolates,
    DateTime? recordedAt,
  }) {
    return TelemetrySnapshot(
      nodeId: nodeId ?? this.nodeId,
      latencyMs: latencyMs ?? this.latencyMs,
      memoryUsageMb: memoryUsageMb ?? this.memoryUsageMb,
      activeIsolates: activeIsolates ?? this.activeIsolates,
      recordedAt: recordedAt ?? this.recordedAt,
    );
  }
}

abstract interface class TelemetryRepository {
  Future<Result<TelemetrySnapshot>> fetchLatestSnapshot({
    required String nodeId,
  });
}

3. Application Layer Cubit & Sealed States (lib/application/telemetry_cubit.dart)

Next, create lib/application/telemetry_cubit.dart. It uses Dart 3 pattern matching (case Success(:final value):) to transition cleanly from TelemetryLoading to either TelemetryLoaded or TelemetryError:

3. Couche Application Cubit & États Scellés (lib/application/telemetry_cubit.dart)

Créez ensuite lib/application/telemetry_cubit.dart. Il utilise la déstructuration par motif de Dart 3 (case Success(:final value):) pour passer proprement de TelemetryLoading à TelemetryLoaded ou TelemetryError :

lib/application/telemetry_cubit.dart | Sealed State & Cubit Orchestration DART
// lib/application/telemetry_cubit.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import '../domain/telemetry_snapshot.dart';

sealed class TelemetryState {
  const TelemetryState();
}

final class TelemetryInitial extends TelemetryState {
  const TelemetryInitial();
}

final class TelemetryLoading extends TelemetryState {
  const TelemetryLoading();
}

final class TelemetryLoaded extends TelemetryState {
  final TelemetrySnapshot snapshot;
  const TelemetryLoaded(this.snapshot);
}

final class TelemetryError extends TelemetryState {
  final String message;
  final String code;
  const TelemetryError({required this.message, required this.code});
}

class InMemoryTelemetryRepository implements TelemetryRepository {
  bool simulateFailure;
  final Duration latency;

  InMemoryTelemetryRepository({
    this.simulateFailure = false,
    this.latency = const Duration(milliseconds: 120),
  });

  @override
  Future<Result<TelemetrySnapshot>> fetchLatestSnapshot({
    required String nodeId,
  }) async {
    await Future<void>.delayed(latency);
    if (simulateFailure) {
      return const Failure<TelemetrySnapshot>(
        message: 'Telemetry node unreachable over local socket.',
        code: 'ERR_NODE_TIMEOUT',
      );
    }
    return Success<TelemetrySnapshot>(
      TelemetrySnapshot(
        nodeId: nodeId,
        latencyMs: 42,
        memoryUsageMb: 118.4,
        activeIsolates: 3,
        recordedAt: DateTime.utc(2026, 10, 28, 12, 0),
      ),
    );
  }
}

class TelemetryCubit extends Cubit<TelemetryState> {
  final TelemetryRepository _repository;

  TelemetryCubit(this._repository) : super(const TelemetryInitial());

  Future<void> refreshSnapshot({String nodeId = 'edge-node-kampala-01'}) async {
    emit(const TelemetryLoading());
    final result = await _repository.fetchLatestSnapshot(nodeId: nodeId);
    switch (result) {
      case Success<TelemetrySnapshot>(:final value):
        emit(TelemetryLoaded(value));
      case Failure<TelemetrySnapshot>(:final message, :final code):
        emit(TelemetryError(message: message, code: code));
    }
  }
}

4. Presentation Layer & Exhaustive Switch UI (lib/main.dart)

Finally, create lib/main.dart. Every color token (0xFF070A13, 0xFF0F1424, 0xFF202B46, 0xFF00B0FF) and widget key (toggle_failure_btn, refresh_telemetry_btn, retry_telemetry_btn, health_status_badge) matches the exact contract in AGENTS.md:

4. Couche Présentation & UI par Switch Exhaustif (lib/main.dart)

Enfin, créez lib/main.dart. Chaque jeton de couleur (0xFF070A13, 0xFF0F1424, 0xFF202B46, 0xFF00B0FF) et chaque clé de widget (toggle_failure_btn, refresh_telemetry_btn, retry_telemetry_btn, health_status_badge) respecte strictement le contrat défini dans AGENTS.md :

lib/main.dart | Exhaustive Pattern-Matched Flutter UI DART
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'application/telemetry_cubit.dart';
import 'domain/telemetry_snapshot.dart';

void main() {
  final repository = InMemoryTelemetryRepository();
  runApp(AgenticTelemetryApp(repository: repository));
}

class AgenticTelemetryApp extends StatelessWidget {
  final InMemoryTelemetryRepository repository;

  const AgenticTelemetryApp({super.key, required this.repository});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Agentic Telemetry Monitor',
      debugShowCheckedModeBanner: false,
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: const Color(0xFF070A13),
        colorScheme: const ColorScheme.dark(
          primary: Color(0xFF00B0FF),
          secondary: Color(0xFF02569B),
          surface: Color(0xFF0F1424),
        ),
      ),
      home: BlocProvider<TelemetryCubit>(
        create: (_) => TelemetryCubit(repository)..refreshSnapshot(),
        child: TelemetryDashboardScreen(repository: repository),
      ),
    );
  }
}

class TelemetryDashboardScreen extends StatelessWidget {
  final InMemoryTelemetryRepository repository;

  const TelemetryDashboardScreen({super.key, required this.repository});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: const Color(0xFF0F1424),
        title: const Text('Edge Telemetry Monitor'),
        actions: [
          IconButton(
            key: const Key('toggle_failure_btn'),
            tooltip: 'Toggle simulated socket failure',
            icon: const Icon(Icons.bug_report_outlined),
            onPressed: () {
              repository.simulateFailure = !repository.simulateFailure;
              context.read<TelemetryCubit>().refreshSnapshot();
            },
          ),
          IconButton(
            key: const Key('refresh_telemetry_btn'),
            tooltip: 'Refresh telemetry snapshot',
            icon: const Icon(Icons.refresh),
            onPressed: () => context.read<TelemetryCubit>().refreshSnapshot(),
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: BlocBuilder<TelemetryCubit, TelemetryState>(
          builder: (context, state) {
            return switch (state) {
              TelemetryInitial() => const Center(
                child: Text('Ready to query telemetry node.'),
              ),
              TelemetryLoading() => const Center(
                child: CircularProgressIndicator(
                  key: Key('telemetry_loading_indicator'),
                ),
              ),
              TelemetryLoaded(:final snapshot) => _TelemetryMetricView(
                snapshot: snapshot,
              ),
              TelemetryError(:final message, :final code) => _TelemetryErrorView(
                message: message,
                code: code,
                onRetry: () {
                  repository.simulateFailure = false;
                  context.read<TelemetryCubit>().refreshSnapshot();
                },
              ),
            };
          },
        ),
      ),
    );
  }
}

class _TelemetryMetricView extends StatelessWidget {
  final TelemetrySnapshot snapshot;

  const _TelemetryMetricView({required this.snapshot});

  @override
  Widget build(BuildContext context) {
    final statusColor = switch (snapshot.status) {
      HealthStatus.nominal => const Color(0xFF10B981),
      HealthStatus.degraded => const Color(0xFFF59E0B),
      HealthStatus.critical => const Color(0xFFEF4444),
    };

    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Container(
          padding: const EdgeInsets.all(16),
          decoration: BoxDecoration(
            color: const Color(0xFF0F1424),
            border: Border.all(color: const Color(0xFF202B46)),
            borderRadius: BorderRadius.circular(12),
          ),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                'Node: ${snapshot.nodeId}',
                style: const TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.w600,
                ),
              ),
              Container(
                padding: const EdgeInsets.symmetric(
                  horizontal: 12,
                  vertical: 4,
                ),
                decoration: BoxDecoration(
                  color: statusColor.withValues(alpha: 0.15),
                  border: Border.all(color: statusColor),
                  borderRadius: BorderRadius.circular(999),
                ),
                child: Text(
                  snapshot.status.name.toUpperCase(),
                  key: const Key('health_status_badge'),
                  style: TextStyle(
                    color: statusColor,
                    fontWeight: FontWeight.w700,
                    fontSize: 12,
                  ),
                ),
              ),
            ],
          ),
        ),
        const SizedBox(height: 16),
        Row(
          children: [
            Expanded(
              child: _MetricTile(
                label: 'Latency',
                value: '${snapshot.latencyMs} ms',
              ),
            ),
            const SizedBox(width: 16),
            Expanded(
              child: _MetricTile(
                label: 'Heap RAM',
                value: '${snapshot.memoryUsageMb.toStringAsFixed(1)} MB',
              ),
            ),
            const SizedBox(width: 16),
            Expanded(
              child: _MetricTile(
                label: 'Isolates',
                value: '${snapshot.activeIsolates}',
              ),
            ),
          ],
        ),
      ],
    );
  }
}

class _MetricTile extends StatelessWidget {
  final String label;
  final String value;

  const _MetricTile({required this.label, required this.value});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: const Color(0xFF0F1424),
        border: Border.all(color: const Color(0xFF202B46)),
        borderRadius: BorderRadius.circular(12),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            label,
            style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 13),
          ),
          const SizedBox(height: 8),
          Text(
            value,
            style: const TextStyle(
              color: Color(0xFFF8FAFC),
              fontSize: 20,
              fontWeight: FontWeight.w700,
            ),
          ),
        ],
      ),
    );
  }
}

class _TelemetryErrorView extends StatelessWidget {
  final String message;
  final String code;
  final VoidCallback onRetry;

  const _TelemetryErrorView({
    required this.message,
    required this.code,
    required this.onRetry,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: const Color(0xFF0F1424),
        border: Border.all(color: const Color(0xFFEF4444)),
        borderRadius: BorderRadius.circular(12),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Error [$code]',
            style: const TextStyle(
              color: Color(0xFFEF4444),
              fontWeight: FontWeight.w700,
              fontSize: 16,
            ),
          ),
          const SizedBox(height: 8),
          Text(message),
          const SizedBox(height: 16),
          ElevatedButton(
            key: const Key('retry_telemetry_btn'),
            onPressed: onRetry,
            child: const Text('Retry Connection'),
          ),
        ],
      ),
    );
  }
}
Step 6 of 8 | Custom Tooling with genkit_mcp

Building a Custom Architecture-Guard MCP Server in Dart

What if you want your coding agent to verify custom domain rules automatically, such as scanning lib/domain/ to prove that zero Flutter UI or data-layer imports slipped in, or querying your canonical design system tokens as an MCP resource?

With genkit and genkit_mcp in Dart, you can build and expose a custom Model Context Protocol server in ~60 lines of Dart code using createMcpServer, ai.defineTool, and ai.defineResource!

1. Write the Custom Dart MCP Server (tools/architecture_mcp_server.dart)

This standalone Dart script exposes two MCP primitives over standard I/O (stdio):

  • Tool checkLayerImports: Recursively scans every .dart file in lib/domain and reports any forbidden package:flutter/, package:flutter_bloc/, or /data/ imports.
  • Resource project://design-tokens: Serves the canonical JSON design tokens so any connected agent can read exact hex codes on demand.

Que faire si vous souhaitez que votre agent vérifie automatiquement des règles métier personnalisées, comme scanner lib/domain/ pour prouver qu'aucun import Flutter UI ou couche de données ne s'y est glissé, ou interroger vos jetons de design sous forme de ressource MCP ?

Grâce aux paquets genkit et genkit_mcp en Dart, vous pouvez créer et exposer votre propre serveur Model Context Protocol en une soixantaine de lignes de Dart avec createMcpServer, ai.defineTool et ai.defineResource !

1. Écrire le Serveur MCP Dart Personnalisé (tools/architecture_mcp_server.dart)

Ce script Dart autonome expose deux primitives MCP sur l'entrée/sortie standard (stdio) :

  • Outil checkLayerImports : Parcourt récursivement chaque fichier .dart dans lib/domain et signale tout import interdit vers package:flutter/, package:flutter_bloc/ ou /data/.
  • Ressource project://design-tokens : Sert les jetons de design JSON canoniques afin que tout agent connecté puisse lire les codes hexadécimaux exacts à la demande.
tools/architecture_mcp_server.dart | Custom MCP Server with genkit_mcp DART
// tools/architecture_mcp_server.dart
// Run with: dart run tools/architecture_mcp_server.dart
import 'dart:convert';
import 'dart:io';
import 'package:genkit/genkit.dart';
import 'package:genkit_mcp/genkit_mcp.dart';
import 'package:schemantic/schemantic.dart';

void main() async {
  final ai = Genkit();

  // 1. Expose an MCP Tool that audits lib/domain/ for layer boundary violations
  ai.defineTool(
    name: 'checkLayerImports',
    description:
        'Scans lib/domain/ to verify zero package:flutter, flutter_bloc, or data-layer imports.',
    inputSchema: SchemanticType.map(
      SchemanticType.string(),
      SchemanticType.dynamicSchema(),
    ),
    fn: (input, _) async {
      final domainPath = (input['domainPath'] as String?) ?? 'lib/domain';
      final dir = Directory(domainPath);
      if (!dir.existsSync()) {
        return jsonEncode({
          'status': 'error',
          'message': 'Directory $domainPath does not exist.',
        });
      }

      final violations = <String>[];
      final forbiddenPatterns = [
        'package:flutter/',
        'package:flutter_bloc/',
        '/data/',
        '/presentation/',
      ];

      await for (final entity in dir.list(recursive: true)) {
        if (entity is File && entity.path.endsWith('.dart')) {
          final lines = await entity.readAsLines();
          for (var i = 0; i < lines.length; i++) {
            final line = lines[i].trim();
            if (line.startsWith('import ')) {
              for (final pattern in forbiddenPatterns) {
                if (line.contains(pattern)) {
                  violations.add('${entity.path}:${i + 1} -> $line');
                }
              }
            }
          }
        }
      }

      return jsonEncode({
        'status': violations.isEmpty ? 'passed' : 'failed',
        'checkedDirectory': domainPath,
        'violationCount': violations.length,
        'violations': violations,
      });
    },
  );

  // 2. Expose an MCP Resource serving canonical UI design tokens
  ai.defineResource(
    name: 'design-tokens',
    uri: 'project://design-tokens',
    fn: (_, _) async => ResourceOutput(
      content: [
        TextPart(
          text: jsonEncode({
            'surfacePrimary': '#070A13',
            'surfaceCard': '#0F1424',
            'borderColor': '#202B46',
            'accentCyan': '#00B0FF',
            'accentBlue': '#02569B',
            'statusNominal': '#10B981',
            'statusDegraded': '#F59E0B',
            'statusCritical': '#EF4444',
          }),
        ),
      ],
    ),
  );

  // 3. Start the MCP Server over stdio transport
  final server = createMcpServer(
    ai,
    McpServerOptions(name: 'flutter-arch-guard', version: '1.0.0'),
  );
  await server.start();
}

2. Register Both MCP Servers Together in .gemini/settings.json

Add flutter-arch-guard right alongside the official dart MCP server so your agent can invoke both compiler tools and your custom architectural audit tool in the same session:

2. Enregistrer les Deux Serveurs MCP dans .gemini/settings.json

Ajoutez flutter-arch-guard aux côtés du serveur MCP officiel dart afin que votre agent puisse invoquer à la fois les outils du compilateur et votre outil d'audit architectural personnalisé dans la même session :

.gemini/settings.json | Combined Official + Custom Dart MCP Configuration JSON
{
  "mcpServers": {
    "dart": {
      "command": "dart",
      "args": ["mcp-server", "--force-roots-fallback"]
    },
    "flutter-arch-guard": {
      "command": "dart",
      "args": ["run", "tools/architecture_mcp_server.dart"]
    }
  }
}
Step 7 of 8 | Deterministic Quality Gates

Automated Agent Verification Loops & Deterministic Widget Tests

The defining difference between Vibe-Coding and Agentic Engineering is a closed-loop verification gate. When an agent writes a widget test using the stable Key contracts mandated in AGENTS.md, it can prove in under 2 seconds that the UI renders nominal states, handles socket failures gracefully, and recovers when the user taps Retry.

1. Create the Deterministic Widget Test (test/telemetry_widget_test.dart)

Save the following test file in test/telemetry_widget_test.dart:

La différence fondamentale entre le Vibe-Coding et l'Agentic Engineering réside dans une boucle de vérification fermée. Lorsqu'un agent écrit un test de widget en s'appuyant sur les contrats de clés Key imposés par AGENTS.md, il peut prouver en moins de 2 secondes que l'interface affiche l'état nominal, gère proprement les erreurs de socket et rétablit l'état lorsque l'utilisateur appuie sur Réessayer.

1. Créer le Test de Widget Déterministe (test/telemetry_widget_test.dart)

Enregistrez le fichier de test suivant dans test/telemetry_widget_test.dart :

test/telemetry_widget_test.dart | Full State Transition Widget Test DART
// test/telemetry_widget_test.dart
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:agentic_telemetry_demo/application/telemetry_cubit.dart';
import 'package:agentic_telemetry_demo/main.dart';

void main() {
  testWidgets(
    'renders nominal telemetry metrics and recovers from simulated failure',
    (WidgetTester tester) async {
      final repository = InMemoryTelemetryRepository(
        latency: const Duration(milliseconds: 10),
      );

      await tester.pumpWidget(AgenticTelemetryApp(repository: repository));

      // Initially loading while async repository resolves
      expect(
        find.byKey(const Key('telemetry_loading_indicator')),
        findsOneWidget,
      );

      await tester.pumpAndSettle();

      // Verify nominal loaded state
      expect(find.text('Node: edge-node-kampala-01'), findsOneWidget);
      expect(find.byKey(const Key('health_status_badge')), findsOneWidget);
      expect(find.text('NOMINAL'), findsOneWidget);
      expect(find.text('42 ms'), findsOneWidget);
      expect(find.text('118.4 MB'), findsOneWidget);

      // Trigger simulated failure via the app bar action button
      await tester.tap(find.byKey(const Key('toggle_failure_btn')));
      await tester.pumpAndSettle();

      expect(find.text('Error [ERR_NODE_TIMEOUT]'), findsOneWidget);
      expect(
        find.text('Telemetry node unreachable over local socket.'),
        findsOneWidget,
      );

      // Recover by tapping Retry Connection
      await tester.tap(find.byKey(const Key('retry_telemetry_btn')));
      await tester.pumpAndSettle();

      expect(find.text('NOMINAL'), findsOneWidget);
    },
  );
}

2. Cross-Platform Pre-Commit Verification Gate

Run this three-stage verification gate in your terminal (or wire it into .git/hooks/pre-commit) so neither a human nor an AI agent can commit unformatted, warning-ridden, or failing Flutter code:

2. Porte de Vérification Pré-Commit Multiplateforme

Exécutez cette porte de vérification en trois étapes dans votre terminal (ou intégrez-la dans .git/hooks/pre-commit) afin que ni un humain ni un agent IA ne puisse commiter du code Flutter mal formaté, contenant des avertissements ou cassant les tests :

Terminal | Cross-Platform Verification Gate (Bash & PowerShell) SH / PS1
# macOS / Linux / Git Bash
dart format --output=none --set-exit-if-changed lib test && \
flutter analyze --fatal-infos && \
flutter test --reporter expanded

# Windows PowerShell
dart format --output=none --set-exit-if-changed lib test; if ($LASTEXITCODE -eq 0) { flutter analyze --fatal-infos }; if ($LASTEXITCODE -eq 0) { flutter test --reporter expanded }
Step 8 of 8 | Verification, Takeaways & Official Resources

Verification Checklist, Key Takeaways & Official Resources

1. End-to-End Verification Checklist

Before wrapping up, confirm that your project passes every check below:

  • MCP Server Active: .gemini/settings.json (or your IDE MCP config) registers dart mcp-server --force-roots-fallback.
  • Zero Domain Leakage: lib/domain/telemetry_snapshot.dart has zero package:flutter/ imports.
  • Zero Static Analysis Warnings: flutter analyze --fatal-infos outputs No issues found!.
  • 100% Green Widget Tests: flutter test passes the nominal load, simulated failure, and retry recovery assertions.

2. Key Engineering Takeaways: The 4 Primitives of Agentic Coding

Agentic Primitive File / Command When It Loads Primary Engineering Responsibility
1. Constitution AGENTS.md Every session turn Global invariants, layer dependency rules, design tokens, and definition of done.
2. Procedural Skill .agents/skills/*/SKILL.md On-demand when matched Step-by-step scaffolding, testing, or migration playbooks without bloating context.
3. Live Tooling (MCP) dart mcp-server & genkit_mcp Invoked via tool calls Compiler diagnostics, pub search, DTD Widget Inspector, Hot Reload, and custom audits.
4. Verification Gate flutter analyze + flutter test Before task completion Deterministic proof that generated code compiles cleanly and satisfies behavioral contracts.

3. Official Documentation & Further Resources

About the Author

Authored by Georges Byona (@glosings0n), Software Engineer, Edge-AI Researcher, Founder @ LosingTek & Founder of FlutterFire Summer Camp.

1. Checklist de Vérification Finale

Avant de conclure, vérifiez que votre projet valide chacun des points suivants :

  • Serveur MCP Actif : .gemini/settings.json (ou la configuration MCP de votre IDE) enregistre dart mcp-server --force-roots-fallback.
  • Zéro Fuite dans le Domaine : lib/domain/telemetry_snapshot.dart ne contient aucun import package:flutter/.
  • Zéro Avertissement d'Analyse Statique : flutter analyze --fatal-infos affiche No issues found!.
  • 100 % des Tests de Widgets au Vert : flutter test valide l'état nominal, la panne simulée et la reprise après clic sur Retry.

2. Points Clés à Retenir : Les 4 Primitives de l'Agentic Coding

Primitive Agentique Fichier / Commande Moment de Chargement Responsabilité d'Ingénierie Principale
1. Constitution AGENTS.md À chaque tour de session Invariants globaux, règles de dépendances entre couches, jetons UI et critères de validation.
2. Skill Procédural .agents/skills/*/SKILL.md À la demande selon la tâche Guides étape par étape de création de feature ou de test sans saturer la fenêtre de contexte.
3. Outillage Live (MCP) dart mcp-server & genkit_mcp Invoqué par appel d'outil Diagnostics du compilateur, recherche pub, Widget Inspector DTD, Hot Reload et audits métier.
4. Porte de Vérification flutter analyze + flutter test Avant de clore la tâche Preuve déterministe que le code généré compile proprement et respecte les contrats.

3. Ressources Officielles & Pour Aller Plus Loin

À propos de l'Auteur

Conçu par Georges Byona (@glosings0n), Software Engineer, Edge-AI Researcher, Fondateur @ LosingTek & Fondateur du FlutterFire Summer Camp.