Implementation
static const Map<String, String> skills = {
'copilot-instructions.md': r'''
# Flutter Base Project – GitHub Copilot Instructions
You are a senior Dart/Flutter engineer working on this project.
Adhere to all conventions established in the codebase when generating, correcting, or refactoring code.
---
## Project at a Glance
| Concern | Solution |
| -------------------- | -------------------------------------------------------------------- |
| State management | `flutter_bloc` (Bloc + Cubit) |
| State immutability | `Equatable` — all states and events extend it |
| Dependency injection | Constructor injection in Blocs; repos use `DioUtil()` singleton |
| Navigation | `NavigationService` singleton + `CustomRouter.generateRoute` |
| Networking | `DioUtil` (Dio wrapper) only — never use the `http` package |
| Secure storage | `SecureStorageHelper` for tokens; `SharedPreferenceHelper` for flags |
| Routing constants | `RouteConstants` (not inline strings or `static const id`) |
| String constants | `AppStrings` |
| Icon/asset constants | `AppIcons` |
| Colour constants | `AppColors` |
| Dimension constants | `AppDimensions` |
| Result type | `Result<T>` / `Success<T>` / `Failure` from `core/types/` |
| Barrel import | `import '../../../../core/router/export.dart';` |
---
## Dart / Flutter General Rules
### Language
- Write all code and documentation in **English**.
- Always declare explicit types for variables, parameters, and return values.
Avoid `dynamic` or `var` unless unavoidable.
- Use `final` for everything that does not reassign.
- Prefer `const` constructors and widget instantiations wherever possible.
### Naming
| Entity | Convention | Example |
| ------------------------------- | -------------------------------------- | ----------------------- |
| Classes | PascalCase | `HomeBloc` |
| Variables / functions / methods | camelCase | `fetchUserProfile()` |
| Files and directories | snake_case | `home_bloc.dart` |
| Constants (compile-time) | SCREAMING_SNAKE_CASE in constant class | `AppStrings.APP_NAME` |
| Boolean variables | Verb prefix | `isLoading`, `hasError` |
- Start every method/function name with a descriptive **verb**.
- Use complete words; abbreviate only established terms (`API`, `URL`, `OTP`, `FCM`).
### Functions & Methods
- Keep functions **< 20 statements**; extract helpers when a function grows beyond that.
- Use **early returns** instead of deeply nested `if/else` blocks.
- Prefer **named parameters** with `required` for functions that accept multiple arguments.
### Classes
- Follow SOLID principles; one responsibility per class.
- Keep classes **< 200 lines** with **< 10 public methods**.
- Prefer **composition** over inheritance.
---
## Feature-First Architecture
Every feature lives under `lib/features/<feature_name>/` and follows this structure:
```
features/
└── feature_name/
├── bloc/ # Bloc + Event + State (split into separate part files)
├── model/ # Request/response DTOs (fromJson / toJson)
├── repo/ # Repository — wraps DioUtil calls
├── view/ # Page widgets (one per route)
└── widget/ # Small, reusable widgets scoped to this feature
```
Shared code lives in `lib/core/`:
```
core/
├── components/ # Reusable UI widgets (buttons, dialogs…)
├── constants/ # AppStrings, AppColors, AppIcons, AppDimensions, enums
├── data/
│ ├── network/ # DioUtil, ApiConfig, ApiResponse, SecureStorageHelper
│ └── shared_preferences/
├── models/ # Cross-feature domain models (e.g. UserModel)
├── router/ # CustomRouter, RouteConstants, export.dart (barrel)
├── services/ # NavigationService
├── theme/ # AppTheme
├── types/ # Result<T>, Success<T>, Failure<T>
└── utils/ # DebugUtils, ApplicationUtils, AppBlocObserver, mixins
```
> **Important:** always import via the barrel file:
>
> ```dart
> import '../../../../core/router/export.dart';
> ```
---
## Bloc Pattern
### File Layout
Each Bloc is split into three `part` files:
```
feature/bloc/
├── feature_bloc.dart ← Bloc class
├── feature_event.dart ← Events (part of 'feature_bloc.dart')
└── feature_state.dart ← State (part of 'feature_bloc.dart')
```
### State
- Base state extends `Equatable`.
- Use an **enum** for status: at minimum `initial`, `loading`, `success`, `error`.
- Provide a full **`copyWith`** method.
- Override `List<Object?> get props` — include every field.
- `@immutable final class` for the state.
- Do **not** store passwords, tokens, or credentials in state.
### Events
- `@immutable final class` for every event.
- Events extend the sealed base `extends <Feature>Event`.
- Name events `<Verb><Noun>Event`: `SubmitLoginEvent`, `LoadHomeDataEvent`.
- `props` must **never** include passwords or credentials.
### Bloc Class
- Constructor-inject repo with a nullable fallback:
`FeatureBloc({FeatureRepo? repo}) : _repo = repo ?? FeatureRepo()`.
- Register handlers: `on<EventType>(_onEventName)`.
- Handler signature: `Future<void> _onEventName(Event event, Emitter<State> emit) async`.
- Always emit `loading` **first** inside an async handler.
- Always emit a terminal state (`success` or `error`) in **every** code path including `catch`.
- Log errors: `DebugUtils.showPrint('FeatureBloc._onX error: $e')`.
- **Never** perform navigation or show dialogs inside a Bloc.
---
## Repository Pattern
- Constructor-inject `DioUtil` with a nullable fallback:
`FeatureRepo({DioUtil? dioUtil}) : _dioUtil = dioUtil ?? DioUtil()`.
- **Reads** → return `Future<T?>` (call `showSnackBar` on failure, return `null`).
- **Writes** → return `Future<Result<T>>` (`Success<T>` / `Failure`).
- All endpoint paths must come from `ApiConfig` constants.
- Wrap every network call in `try/catch`; log with `DebugUtils.showPrint`.
- Never `rethrow` unless the caller is designed to handle it.
---
## Models (DTOs)
- `@immutable`, all fields `final`.
- `factory fromJson(Map<String, dynamic>)` + `toJson()`.
- Null-safe defaults: `?? ''`, `?? 0`, `?? false`, `?? []`.
- Never declare a field as `dynamic` — cast JSON values explicitly.
- Request models in `feature/model/` named `<Noun>RequestModel`.
- Response models in `feature/model/` named `<Noun>ResponseModel`.
---
## Navigation
- Use `NavigationService` for all programmatic navigation.
- Use `RouteConstants` for all route name strings — never inline strings.
- Pass typed argument objects, not raw `Map`s.
---
## UI / Widgets
- All widgets must use `const` constructors where possible.
- Use `flutter_screenutil` (`16.w`, `20.h`, `14.sp`) for all dimensions.
**Never** use `MediaQuery` for sizing; **never** use raw doubles.
- Use `AppColors` for every colour — never hardcode hex/RGB.
Never use `.withOpacity()` — encode alpha into the constant.
- Use `AppStrings` for every user-facing string.
- Use `AppIcons` for every asset path.
- Use `AppDimensions` for all spacing/size constants.
- All `TextStyle`s must use named `FontStyles` getters from
`lib/core/constants/fonts/font_styles.dart` — no inline `TextStyle(...)`.
- Use `TextWidget` for all text display — no raw `Text(...)` in screen widgets.
- Use `CustomButton` for primary actions.
- Use `BlocBuilder` for state-driven UI; `BlocListener` for side-effects.
- Use `BlocConsumer` only when both building and listening are needed.
- Never use `print()` — use `DebugUtils.showPrint()`.
- Feedback to users: `showSnackBar(message, SnackType.success/failed)`.
- Extract logical sub-trees into `StatelessWidget` classes in `widget/`.
- Always pass a `key` to list items.
---
## Error Handling & Result Type
Use `Result<T>` / `Success<T>` / `Failure` from `core/types/result.dart` for any write
operation. Switch on the result in the Bloc handler:
```dart
switch (result) {
case Success(:final data):
emit(state.copyWith(status: FeatureStatus.success, data: data));
case Failure(:final message):
emit(state.copyWith(status: FeatureStatus.error, message: message));
}
```
---
## Security
### Secrets & Keys
- **Never** hardcode API keys, tokens, or secrets in Dart source.
- Inject all secrets at build time via `--dart-define=KEY=value`; read with
`String.fromEnvironment('KEY')`.
- **`AppConfig.environment`** is set at build time — never change it at runtime.
### Local Storage
- Use `SecureStorageHelper` for **all** security-sensitive values: auth token,
refresh token, FCM token.
- Use `SharedPreferenceHelper` only for non-sensitive app state (theme, onboarding).
- **Never** store a password or credential in `SharedPreferences` or Bloc state.
### Bloc State
- `props` must never include passwords, tokens, or payment secrets.
- Clear any credential field immediately after the handler that consumed it:
`emit(state.copyWith(password: ''))`.
### WebView
- Validate every URL loaded against `ApiConfig` domain constants before loading.
- Reject URLs whose host is not in the allowlist.
### Network
- All API calls must be HTTPS. Never disable certificate validation.
- URL selection is done via `AppConfig.environment` + `ApiConfig.baseUrl` only.
---
## Reliability
- Every repository method must terminate at a `DioUtil` call — no self-recursion.
- Every `async` function calling the network must have a `try/catch`.
- Emit `ApiMsgStrings.somethingWentWrong` to the UI — never expose raw exception messages.
- Always emit `loading` first; always emit a terminal state in every code path.
### Linting & Formatting
- All code must conform exactly to the project's `analysis_options.yaml` (e.g., `flutter_lints` or `very_good_analysis`).
- Never generate code that requires `// ignore:` comments.
- Always append trailing commas `,` to Flutter widget properties to ensure proper `dart format` structure.
### Observability
- `DebugUtils.showPrint` is for debug strings only. For `catch(e, stackTrace)` blocks, pass the exception and stack trace to your designated crash reporter service facade.
### Cubit vs Bloc
- Default to `Cubit` for straightforward state changes. Only use `Bloc` when you specifically need `Event` transformers (e.g., debounce, throttle, droppable).
### UI & E2E Testing
- **Maestro (E2E Flows)**: Use Maestro instead of Flutter integration tests for testing full user journeys, especially those involving native OS permission dialogs (Camera, Biometrics, Vault).
- **Semantics**: Every intractable UI component must be wrapped in `Semantics(identifier: 'specific_name')` to ensure Maestro can reliably tap it during E2E flows. Do not rely on fixed X/Y coordinates.
- **Widget Tests**: Create isolated Widget Tests strictly for custom UI components mapping to Mock Blocs. Mock network images or animations during tests to prevent flaky failures.
---
---
## App Store & Play Store Compliance
To ensure the app passes Apple App Store and Google Play Store reviews, strictly adhere to the following when generating code:
### Permissions & Privacy
- **Just In Time Requesting**: Never request permissions (Camera, Location, Contacts, etc.) at app launch. Request them only when the user initiates a feature that requires them.
- **Rationale Strings**: Ensure that `Info.plist` and `AndroidManifest.xml` have clear, user-friendly rationale strings explaining *why* the permission is needed.
- **Account Deletion**: If generating user profile settings, always include an option to **Delete Account** natively within the app.
### UI / UX & Platform Behaviors
- **Safe Areas**: Always wrap top-level scaffold contents or floating elements in a `SafeArea` to avoid overlaps with the iOS notch, dynamic island, or Android system bars.
- **Back Navigation**: Ensure proper `WillPopScope` or `PopScope` implementations for Android hardware back buttons so users are not trapped on a screen.
- **Accessibility**: Use `Semantics` widgets for custom controls. Ensure text scales correctly with the system text size without overflowing (use `TextOverflow.ellipsis` or scalable flexible layouts).
- **Dark Mode**: Never hardcode colors that would break in Dark Mode. Always use `Theme.of(context).colorScheme` or the predefined `AppColors`.
### Authentication & Payments
- **Sign in with Apple**: If adding any third-party SSO (Google, Facebook), you *must* concurrently implement Sign in with Apple for iOS.
- **Digital Goods**: If generating features that unlock digital content or subscriptions, do not use external payment gateways (like Stripe); use native In-App Purchases (IAP).
## CI/CD & Git Hooks (Pre-Commit)
To enforce code formatting, linting, and reliability before code reaches the repository, a strict pre-commit hook is active.
- **Formatting**: Generated code and tests must output cleanly via `dart format . --set-exit-if-changed`.
- **Linting**: Code must pass `flutter analyze` without any warnings.
- **Tests**: Code must not break existing test cases and must pass `flutter test`.
Never generate code that attempts to circumvent or ignore these rules, as the native git hooks will block all non-compliant commits.
---
## Maintainability
### Single Source of Truth
| Class | File | Purpose |
| ---------------- | ---------------------------------------- | --------------------------- |
| `AppStrings` | `core/res/strings/app_strings.dart` | All user-facing strings |
| `AppColors` | `core/res/colors/colors.dart` | All colour values |
| `AppIcons` | `core/res/drawables/icons.dart` | SVG/PNG asset paths |
| `AppDimensions` | `core/res/size/size_config.dart` | Spacing/size constants |
| `RouteConstants` | `core/routes/app_router.dart` | Navigation route strings |
| `ApiConfig` | `core/data/network/api_config.dart` | API base URLs and endpoints |
| `ApiMsgStrings` | `core/res/strings/api_msg_strings.dart` | API error/success messages |
### Dependency Direction
- Features must not import each other. Shared models and services live in `core/`.
- Blocs must not import views or widgets.
- Repos must not import Blocs or views.
- `core/` must not import from `features/`.
### Code Size Limits
- Files: **< 300 lines**.
- Functions / methods: **< 20 statements**.
- Classes: **< 200 lines**, **< 10 public methods**.
### Environment Switching
Control via `--dart-define=ENV=dev|uat|prod` at build time.
Never merge code where `baseUrl` is hardcoded to a dev or UAT value.
---
## Coverage
- Every public Bloc event handler must have a corresponding `blocTest`.
- Minimum per handler: (1) happy path, (2) null/empty response, (3) exception thrown.
- Use `flutter_test` + `bloc_test` + `mocktail`. Mock repos via constructor injection.
- Never use real network calls in unit tests.
- Follow **Arrange–Act–Assert**; prefix test variables `input`, `mock`, `actual`, `expected`.
---
## Automation – Prompt Files
Use the prompt files in `.github/skills/` as the entry point for structured work:
| Task | Prompt file |
| -------------------------------------------- | ------------------------------------ |
| **Full e2e (Figma + AC + API → prod-ready)** | `create-feature-e2e.prompt.md` |
| Maestro E2E Test Flows | `create-maestro-flow.prompt.md` |
| Deep Link / Routing Architecture | `create-deep-link.prompt.md` |
| New feature (full stack, no Figma) | `create-feature.prompt.md` |
| Screen from Figma only | `create-screen-from-figma.prompt.md` |
| Bloc only | `create-bloc.prompt.md` |
| Repository only | `create-repo.prompt.md` |
| Model DTOs from JSON | `create-model.prompt.md` |
| Tests for a Bloc | `write-tests.prompt.md` |
| Security / quality audit | `security-review.prompt.md` |
| App Store / Play Store compliance check | `app-store-compliance.prompt.md` |
''',
'skills/create-maestro-flow.prompt.md': r'''
---
agent: agent
description: Generate comprehensive, modular Maestro E2E test flows (YAML) for testing critical user journeys, strictly adhering to Flutter Semantics and native OS interaction rules.
argument-hint: "Flow name, target feature, and step-by-step user journey description"
---
# Create Maestro E2E Flow
Flow Name: `${input:flowName}` | Feature: `${input:targetFeature}`
Journey:
```
${input:journeyDescription}
```
## File Locations
All Maestro E2E flows belong in the root-level `.maestro/` directory.
```
.maestro/
├── common/ (For reusable sub-flows like login.yaml)
└── flows/
└── ${input:flowName}.yaml
```
---
## Step 1 – The Boilerplate
Every Maestro YAML file must start with the `appId` declaration and basic setup, heavily relying on environment variables so tests can run on both Android and iOS dynamically.
```yaml
appId: ${APP_ID}
env:
# Define default variables if they aren't passed by the CLI
EMAIL: "test@example.com"
---
# ── Setup / Initialization ──
- clearState: true
- launchApp:
clearState: true
```
---
## Step 2 – Reusable Flow Stitching (`runFlow`)
If the journey requires the user to be logged in, **never** manually rewrite the login steps. Always use `- runFlow` to stitch common modules together.
```yaml
# ── Pre-requisites ──
- runFlow:
file: ../common/login_flow.yaml
env:
USER: ${EMAIL}
```
---
## Step 3 – The Journey Steps (The "Flutter" Rules)
Translate the `${input:journeyDescription}` into Maestro commands.
### CRITICAL Flutter Rules:
1. **Never use brittle X/Y coordinates**: Do not use `- tapOn: point: 100,200`.
2. **Prefer Semantics over raw text**: Flutter paints text onto a canvas. Maestro often struggles to read custom fonts. ONLY use `- tapOn: "Text"` if it is standard. Otherwise, rely on Flutter `Semantics` labels: `- tapOn: id: "btn_submit_document"`.
3. **If targeting an ID**, you **MUST** ensure the corresponding Flutter widget actually has a semantic identifier. (e.g. `Semantics(identifier: 'btn_submit_document', child: ...)`).
4. **Native OS Dialogs**: FormFill Vault extensively uses OS permissions (Camera, Storage, Biometrics). If the journey triggers a permission, blindly assume the native OS dialog appears and accept it.
```yaml
# ── The Action ──
- tapOn: id: "btn_add_document"
# ── Handle OS Permissions (Android/iOS independent) ──
- tapOn: "Allow.*" # Regex matches "Allow", "Allow Access", "While using the app", etc.
# ── Dynamic Input ──
- tapOn: id: "input_document_title"
- inputText: "Aadhaar Card"
- hideKeyboard
# ── Scrolling & Asserting ──
- scrollUntilVisible:
element: id: "btn_save_vault"
direction: DOWN
- tapOn: id: "btn_save_vault"
# ── Validate Success ──
- assertVisible: "Document saved successfully"
```
---
## Step 4 – Assertions and Teardown
Every E2E Flow must end with an incontrovertible visual assertion that the goal was achieved, followed by an optional teardown.
```yaml
# ── Conclusion ──
- extendedWaitUntil:
visible: id: "vault_list_item_aadhaar"
timeout: 5000 # Wait up to 5 seconds for the DB to save and UI to refresh
- assertVisible: "Aadhaar Card"
```
---
## Output Validation & Rules
Before generating the final YAML, ensure:
1. All `id:` targets use snake_case strings.
2. You explicitly output a reminder to the developer: *"Please ensure `Semantics(identifier: '...')` was added to your Flutter Widgets, otherwise Maestro cannot tap these IDs."*
3. You include the exact terminal command to run the flow: e.g., `maestro test .maestro/flows/${input:flowName}.yaml -e APP_ID=com.example.formfill`
''',
'skills/app-store-compliance.prompt.md': r'''
# App Store & Play Store Compliance Review
Review the provided code or feature implementation to ensure it strictly complies with both the **Apple App Store Review Guidelines** and the **Google Play Store Policies**. If any violations are found, point them out explicitly and suggest refactors.
## Core Reference Documentation
- **Apple App Store Review Guidelines**: [developer.apple.com/app-store/review/guidelines/](https://developer.apple.com/app-store/review/guidelines/)
- **Apple Human Interface Guidelines (HIG)**: [developer.apple.com/design/human-interface-guidelines](https://developer.apple.com/design/human-interface-guidelines/)
- **Google Play Developer Policy Center**: [play.google.com/about/developer-content-policy](https://play.google.com/about/developer-content-policy/)
- **Material Design Guidelines**: [m3.material.io](https://m3.material.io/)
---
## 1. Permissions & Privacy
*Ref: Apple Guidelines 5.1.1 (Data Collection and Storage) | Google Play User Data Policy*
- **Just-In-Time Requesting**: Verify that permissions (Camera, Location, Contacts, Photo Library, Microphone, etc.) are NOT requested at app launch. They must only be requested when the user explicitly triggers a feature that requires them.
- **Rationale Strings**: Ensure that `Info.plist` and `AndroidManifest.xml` have clear, user-friendly strings explaining exactly *why* the permission is needed (e.g., `NSCameraUsageDescription`, `NSPhotoLibraryUsageDescription`).
- **Account Deletion**: If the code scaffolded involves user profiles or account settings, verify that a clear, native option to **Delete Account** is included. *Ref: Apple Guideline 5.1.1(v) | Google Play Data Deletion requirement.*
## 2. UI / UX & Platform Behaviors
*Ref: Apple Guidelines 4.0 (Design) | Google Play Core App Quality*
- **Safe Areas**: Verify that top-level screens or floating elements are wrapped in a `SafeArea` widget to prevent overlapping with the iOS notch, dynamic island, or Android system bars.
- **Hardware Back Navigation**: Check that Android hardware back button behavior is respected (using `WillPopScope` or the newer `PopScope` API) so users do not get trapped.
- **Accessibility (a11y)**: Check that custom controls use `Semantics` widgets appropriately. Verify text scaling is supported without overflowing (e.g., using flexible layouts, wrapping, or `TextOverflow.ellipsis`).
- **Dark Mode Support**: Ensure colors are not hardcoded in ways that break when the system swaps to Dark Mode. Colors should come from `Theme.of(context).colorScheme` or the predefined `AppColors` palette.
## 3. Monetization & Authentication
*Ref: Apple Guidelines 3.1.1 (In-App Purchase) & 4.8 (Sign in with Apple) | Google Play Payments Policy*
- **Sign in with Apple**: If the code adds third-party single sign-on (SSO) like Google or Facebook, ensure that **Sign in with Apple** is also implemented. Apple will reject the app otherwise.
- **Native Payments (IAP)**: If the feature unlocks premium digital content, subscriptions, or features, ensure that native In-App Purchases (IAP) are used rather than third-party gateways (like Stripe or PayPal) which violate both platform's core policies for digital goods.
## 4. Content & Data Handling
*Ref: Apple Guidelines 1.2 (User-Generated Content) | Google Play Spam and Minimum Functionality*
- **User-Generated Content (UGC)**: If the app displays UGC, verify there is functionality to report abusive content and block users.
- **External Links**: If the app links to external websites, ensure these links do not bypass native purchasing or hide prohibited content.
- **Secure Storage**: Ensure sensitive data (passwords, tokens) is never logged in plaintext and is only saved using `SecureStorageHelper` rather than basic `SharedPreferences`.
---
**Instructions to AI:**
1. Read through the candidate code.
2. Cross-reference the implementation against the 4 categories and the provided reference links above.
3. List any potential Compliance Risks.
4. Output the corrected, store-compliant code.
''',
'skills/create-feature.prompt.md': r'''
---
agent: agent
description: Scaffold a complete feature folder (bloc/model/repo/view/widget) from acceptance criteria following project architecture standards.
argument-hint: "Feature name in snake_case, PascalCase variant, parent flow folder, and acceptance criteria"
---
# Create Feature
Feature: `${input:featureName}` | PascalCase: `${input:featurePascalName}` | Flow: `${input:parentFolder}`
## Acceptance Criteria
```
${input:acceptanceCriteria}
```
> **Before writing any code**, parse the acceptance criteria and derive:
>
> | Concern | How to derive from AC |
> | ------------------------- | ------------------------------------------------------------------------------------------ |
> | **Events** | One event per user action or system trigger (`SubmitBookingEvent`, `LoadProfileDataEvent`) |
> | **State status variants** | One enum value per observable UI state beyond `initial/loading/success/error` |
> | **API calls** | One repo method per data exchange described |
> | **Edge cases** | Every "when X fails / empty / unauthorised" clause → error state + snackbar |
> | **Navigation** | Each "navigates to …" clause → `NavigationService` call in `BlocListener` |
> | **Validation** | Each "must / cannot / required" clause → guard before API call |
**Read before writing any string/colour/icon/style/route:**
[app_strings.dart](../../lib/core/res/strings/app_strings.dart) ·
[colors.dart](../../lib/core/res/colors/colors.dart) ·
[icons.dart](../../lib/core/res/drawables/icons.dart) ·
[font_style.dart](../../lib/core/res/fonts/font_style.dart) ·
[size_config.dart](../../lib/core/res/size/size_config.dart) ·
[app_router.dart](../../lib/core/routes/app_router.dart) ·
[api_config.dart](../../lib/core/data/network/api_config.dart)
## Folder structure
```
lib/features/${input:parentFolder}/${input:featureName}/
├── bloc/ ${input:featureName}_{bloc,event,state}.dart
├── model/ ${input:featureName}_{request,response}_model.dart (if API needed)
├── repo/ ${input:featureName}_repo.dart
├── view/ ${input:featureName}_view.dart
└── widget/
```
## Per-file rules
**Bloc** — barrel import only; `part` both sibling files; constructor-inject repo
(`?? ${input:featurePascalName}Repo()`); `on<E>(_handler)` per event; emit `loading` (with `clearMessage: true`) →
`try`/`catch (e, stackTrace)` → emit `success`/`error`; log with `DebugUtils.showPrint` passing `stackTrace`; no navigation or UI.
**Event** — `part of` bloc; `@immutable sealed class ${input:featurePascalName}Event extends Equatable`;
each event `@immutable final class <Verb><Noun>Event`; `props` for all non-sensitive fields.
**State** — `part of` bloc; `enum ${input:featurePascalName}Status { initial, loading, success, error }`;
`@immutable final class` state extends `Equatable`; all `final` fields; full `copyWith` with `clearMessage` flag; `props`.
**Repo** — barrel import; constructor-inject `DioUtil ?? DioUtil()`; ALL operations (reads & writes) → `Future<Result<T>>`
(strictly decoupled from UI — no snackbars); all paths from `ApiConfig`;
`try`/`catch (e, stackTrace)` + inner JSON parsing `try`/`catch` + `DebugUtils.showPrint`.
**Models** — `@immutable`, all `final`; `fromJson` factory + `toJson`; null-safe defaults;
never declare a field as `dynamic`.
**View** — wrap scaffold body in `SafeArea` + `SingleChildScrollView`; `BlocProvider` at root fires initial event if needed; `BlocListener` for
side-effects (navigation, snackbars); `BlocBuilder` for UI; every string → `AppStrings`,
colour → `AppColors`, text → `TextWidget`, button → `CustomButton` (wrap custom tap targets in `Semantics(button: true)`), dims → `AppDimensions`.
- ScreenUtil; extract sub-trees to `widget/`.
**Route** — add `static const` to `RouteConstants`; add `case` in `CustomRouter.generateRoute`.
**Exports** — add cross-feature exports to `export.dart`.
**AC coverage check** — after generating all files, verify every AC clause is handled.
**Git Hooks Compliance** — unconditionally pass `flutter analyze`; NEVER output deprecated widgets (e.g. `WillPopScope`).
No `TODO` comments — implement minimally but completely.
''',
'skills/create-screen-from-figma.prompt.md': r'''
---
agent: agent
description: Generate a Flutter screen and widgets from a Figma node, mapping Figma design tokens to project constants (AppColors, FontStyles, AppDimensions, AppIcons, AppStrings).
argument-hint: "Figma file key, Figma node ID, feature name, output path"
---
# Create Screen from Figma
Feature: `${input:featureName}` | PascalCase: `${input:featurePascalName}`
Figma file key: `${input:figmaFileKey}`
Figma node ID: `${input:figmaNodeId}`
---
## Step 1 – Fetch Figma data
Call Figma MCP `get_figma_data`:
- `fileKey`: `${input:figmaFileKey}`
- `nodeId`: `${input:figmaNodeId}`
- Retrieve the full node tree including children, styles, and component variants.
Parse the response and extract every design token present:
| Token type | Figma value | Notes |
| ------------------------ | ---------------- | ------------------------------------ |
| fill colour | `#RRGGBB` / rgba | map to `AppColors.*` |
| stroke colour | `#RRGGBB` / rgba | map to `AppColors.*` |
| font family | string | must match `FontFamily.*` |
| font size | number | map to nearest `FontStyles.*` getter |
| font weight | number | map to nearest `FontStyles.*` getter |
| line height | number/% | map to nearest `FontStyles.*` getter |
| corner radius | number | map to `AppDimensions.radius*` |
| horizontal padding | number | map to `AppDimensions.*` |
| vertical padding/spacing | number | map to `AppDimensions.*` |
| icon/image node | nodeId | schedule for download |
| visible text | string | map to `AppStrings.*` |
---
## Step 2 – Build Design Token Map & download assets
For each token extracted in Step 1, produce:
| Token type | Figma value | Project constant | Action |
| ---------- | -------------- | ------------------------ | ---------------------- |
| colour | `#2196F3` | `AppColors.primary` | use |
| colour | `#FF0000` | — | **add** to `AppColors` |
| text | "Welcome back" | `AppStrings.welcomeBack` | use / **add** |
| icon | node `1234:56` | `AppIcons.someIcon` | **download** + add |
**Rules:**
- A token already present in a project constant → **use** it.
- A token not yet in project constants → **add** it to the relevant constants file, following naming conventions (see project `copilot-instructions.md`). Never hardcode values in widget files.
- Colours: never use `.withOpacity()` — encode alpha into the constant hex value.
- For icon/image nodes: call Figma MCP `download_figma_images` with those node IDs and save to `assets/icons/` or `assets/images/`. Register new asset paths under `flutter: assets:` in `pubspec.yaml` and add a constant to `AppIcons`.
---
## Step 3 – Apply constants
Update the following files as needed (only add what is new — never modify existing constants):
- `lib/core/res/colors/colors.dart` — new `AppColors.*` entries
- `lib/core/res/strings/app_strings.dart` — new `AppStrings.*` entries
- `lib/core/res/drawables/icons.dart` — new `AppIcons.*` entries
- `lib/core/res/fonts/font_style.dart` — new `FontStyles.*` getter (only if a genuinely new style is needed; prefer composing `.copyWith()` on existing getters in widget code)
- `lib/core/res/size/size_config.dart` — new `AppDimensions.*` getter (only if spacing value has no existing equivalent)
- `pubspec.yaml` — new asset entries
---
## Step 4 – Generate the view
Create `lib/features/${input:parentFolder}/${input:featureName}/view/${input:featureName}_view.dart`.
**Layout rules (derived from Figma node tree):**
- **SafeArea & Scrolling**: Wrap the outermost screen scaffold body in a `SafeArea` to respect iPhone Notches and Android system bars. Assume ALL screens need to scroll on smaller devices; wrap the primary `Column` in a `SingleChildScrollView` to prevent pixel overflows.
- **Input Intelligence**: If a Figma component visually represents an input area (a border box with placeholder text), intelligently replace it with a native `TextFormField` rather than generating a static painted `Container`.
- Replicate the Figma layout hierarchy: frames → `Column`/`Row`/`Stack`; auto-layout direction → `Column`/`Row`; auto-layout gap → `SizedBox(height: ...)` / `SizedBox(width: ...)`.
- **Text Overflow**: Wrap text-heavy `Row` children in `Expanded` or use `TextOverflow.ellipsis` to gracefully handle large system font scalings.
- All sizes use ScreenUtil: widths → `.w`, heights → `.h`, font sizes → `.sp`, radii → direct `AppDimensions.*` getter value.
- All colours → `AppColors.*`. All text → `TextWidget(text: ..., style: FontStyles.*)`. All buttons → `CustomButton(...)`.
- Complex or repeated sub-trees (≥ 3 widgets) → extract to `widget/` (Step 5).
- Strictly follow all _View_ rules from `create-feature.prompt.md`.
**BLoC wiring:**
- Wrap screen root in `BlocProvider(create: (_) => FeatureBloc())`.
- State-driven UI → `BlocBuilder<FeatureBloc, FeatureState>`.
- Side effects (navigation, snackbars) → `BlocListener<FeatureBloc, FeatureState>`.
- Both → `BlocConsumer`.
- Dispatch events matching the Feature Design Table from `create-feature.prompt.md`.
---
## Step 5 – Extract widgets
For each identified sub-tree:
1. Create `lib/features/${input:parentFolder}/${input:featureName}/widget/<widget_name>_widget.dart`.
2. Widget is `StatelessWidget` wherever possible; `StatefulWidget` only for locally animated or form-field sub-trees.
3. **Accessibility & Maestro**: Every extracted button, custom interactive container, or touch target MUST be wrapped in a `Semantics(identifier: 'specific_btn_name', button: true)` widget to comply with screen readers AND allow Maestro E2E tests to locate them. It must also guarantee a tap target of at least 48x48.
4. Accept only the minimal typed parameters needed — no passing of entire state objects.
5. **Performance Purity**: Aggressively apply `const` keywords to every generic widget to ensure 60fps scrolling.
6. Pass a `key` parameter.
---
## Final UI Check
- **Git Hooks Compliance**: The final generated code must unconditionally pass `flutter analyze` and `dart format`.
- **No Deprecations**: NEVER output deprecated layout widgets (e.g., use `PopScope` over `WillPopScope`).
- No `TODO` comments in generated files — implement completely or leave the slot empty with a `// placeholder` comment explaining what goes there.
''',
'skills/create-repo.prompt.md': r'''
---
agent: agent
description: Create a Repository class for a feature following the project's DioUtil + Result<T> conventions.
argument-hint: "Feature name in snake_case, PascalCase variant, and list of API methods needed with HTTP verb and endpoint"
---
# Create Repository
Feature: `${input:featureName}` | PascalCase: `${input:featurePascalName}`
API methods needed:
```
${input:apiMethods}
```
## File location
```
lib/features/<parentFolder>/${input:featureName}/repo/${input:featureName}_repo.dart
```
## Template
```dart
import '../../../../core/router/export.dart';
class ${input:featurePascalName}Repo {
${input:featurePascalName}Repo({DioUtil? dioUtil}) : _dioUtil = dioUtil ?? DioUtil();
final DioUtil _dioUtil;
// WRITE operation (POST/PUT/DELETE) — returns Result<T>
Future<Result<ResponseType>> someWriteMethod({
required RequestModel requestModel,
}) async {
try {
final Map<String, dynamic>? response = await _dioUtil.postApi(
url: ApiConfig.someEndpoint,
body: requestModel.toJson(),
showMessage: false,
);
if (response == null) {
return const Failure(message: ApiMsgStrings.somethingWentWrong);
}
// Wrap parsing in try/catch to gracefully handle JSON mapping crashes
try {
return Success(data: ResponseType.fromJson(response));
} catch (e, stackTrace) {
DebugUtils.showPrint('${input:featurePascalName}Repo.someWriteMethod JSON Parsing error: $e\n$stackTrace');
return const Failure(message: ApiMsgStrings.somethingWentWrong);
}
} catch (e, stackTrace) {
DebugUtils.showPrint('${input:featurePascalName}Repo.someWriteMethod network error: $e\n$stackTrace');
return const Failure(message: ApiMsgStrings.somethingWentWrong);
}
}
// READ operation (GET) — returns Result<T> (DECOUPLED FROM UI)
Future<Result<ResponseType>> someReadMethod() async {
try {
final Map<String, dynamic>? response = await _dioUtil.getApi(
url: ApiConfig.someEndpoint,
);
if (response == null) {
return const Failure(message: ApiMsgStrings.somethingWentWrong);
}
// Wrap parsing in try/catch to gracefully handle JSON mapping crashes
try {
return Success(data: ResponseType.fromJson(response));
} catch (e, stackTrace) {
DebugUtils.showPrint('${input:featurePascalName}Repo.someReadMethod JSON Parsing error: $e\n$stackTrace');
return const Failure(message: ApiMsgStrings.somethingWentWrong);
}
} catch (e, stackTrace) {
DebugUtils.showPrint('${input:featurePascalName}Repo.someReadMethod network error: $e\n$stackTrace');
return const Failure(message: ApiMsgStrings.somethingWentWrong);
}
}
}
```
## Rules
- **Constructor-inject** `DioUtil` with `?? DioUtil()` fallback for testability.
- **Unified Return Type**: ALL operations (READs & WRITEs) must exclusively return `Future<Result<T>>`.
- **API Response Parsing Safety**: Always wrap `ResponseType.fromJson(response)` in an inner `try/catch` to gracefully catch and report `TypeError` or `FormatException`.
- All endpoint paths come from `ApiConfig` constants — never inline URL strings.
- **Catch Blocks & Observability**: Always catch `(e, stackTrace)` so that the full context is logged and can be handled by a crash reporter. Use `DebugUtils.showPrint('ClassName.methodName error: $e\n$stackTrace')`.
- **Never** `rethrow` exceptions into the void. Always convert exceptions to `Failure(message)`.
- **Clean Architecture Ban**: You must **never** import `Bloc`, `event`, `state`, `snackbars`, `dialogs`, or `view` files into the repository. The data layer must remain 100% headless and completely decoupled from UI execution.
- Use barrel import `import '../../../../core/router/export.dart'`.
- Every repo method must terminate at a `DioUtil` call — no self-recursion or retry loops.
''',
'skills/create-feature-e2e.prompt.md': r'''
---
agent: agent
description: End-to-end feature scaffold driven by acceptance criteria, a Figma design, and API request/response models. Orchestrates all sub-prompts in sequence to produce the complete bloc/model/repo/view/widget/test suite.
argument-hint: "Feature name, PascalCase variant, parent flow folder, Figma file key, Figma node ID, acceptance criteria, API request JSON, API response JSON"
---
# End-to-End Feature
Feature: `${input:featureName}` | PascalCase: `${input:featurePascalName}` | Flow: `${input:parentFolder}`
Figma file key: `${input:figmaFileKey}`
Figma node ID: `${input:figmaNodeId}`
## Acceptance Criteria
```
${input:acceptanceCriteria}
```
## API Contract
**Request:**
```json
${input:requestJson}
```
**Response:**
```json
${input:responseJson}
```
---
## How this prompt works
This is an **orchestrator**. Each phase below delegates to a dedicated sub-prompt that owns
the detailed rules for that concern. Read each linked sub-prompt **in full** before executing
its phase — the rules there are the authoritative source of truth. Do not re-interpret or
relax them here.
Work through phases **strictly in order**. Do not start Phase N+1 until Phase N is complete.
**CRITICAL:** To prevent token truncation and maintain code quality, you **MUST** naturally pause at the end of every Phase. Output your progress and explicitly ask the user to type "continue" before proceeding to the next Phase.
---
## Phase 1 – Parse & Plan (no code)
Before touching any file:
1. **Derive the Feature Design Table** from the acceptance criteria using the derivation rules
in [create-feature.prompt.md](./create-feature.prompt.md) (the _Acceptance Criteria_ section).
| # | AC Clause | Event name | State status variant | Repo method | Edge case / guard |
| --- | --------- | ---------- | -------------------- | ----------- | ----------------- |
2. **Fetch the Figma design** — call Figma MCP `get_figma_data`:
- `fileKey`: `${input:figmaFileKey}`
- `nodeId`: `${input:figmaNodeId}`
3. **Build the Design Token Map** following Steps 1–2 in
[create-screen-from-figma.prompt.md](./create-screen-from-figma.prompt.md).
| Token type | Figma value | Project constant | Action (use / add) |
| ---------- | ----------- | ---------------- | ------------------ |
4. **Download assets** via Figma MCP `download_figma_images` for any icon/image nodes
identified in the token map.
5. **Derive the model fields** from `${input:requestJson}` and `${input:responseJson}`.
Present the Feature Design Table, Design Token Map, and model field list. Do not proceed
until all three are complete.
---
## Phase 2 – Constants & Assets
Apply the token-mapping rules from
[create-screen-from-figma.prompt.md](./create-screen-from-figma.prompt.md) (Step 3).
Update only what is new — never modify existing constants:
- `lib/core/constants/colors/colors.dart`
- `lib/core/constants/strings/strings.dart`
- `lib/core/constants/drawable/icons.dart`
- `lib/core/constants/fonts/font_styles.dart`
- `lib/core/constants/app_dimensions.dart`
- `pubspec.yaml` (new asset paths only)
---
## Phase 3 – Models
Follow every rule in [create-model.prompt.md](./create-model.prompt.md).
Context:
- `modelName` → `${input:featurePascalName}`
- `modelType` → derive from API contract (request / response / both)
- `jsonOrFields` → use `${input:requestJson}` and `${input:responseJson}`
- Output path → `lib/features/${input:parentFolder}/${input:featureName}/model/`
**Code Generation Rule:** If using `json_serializable` or `freezed`, ensure the model declares the appropriate `part` file. After providing the code, remind the user to run `dart run build_runner build --delete-conflicting-outputs`.
---
## Phase 4 – Repository
Follow every rule in [create-repo.prompt.md](./create-repo.prompt.md).
Context:
- `featureName` → `${input:featureName}`
- `featurePascalName` → `${input:featurePascalName}`
- `apiMethods` → the _Repo method_ + _Edge case_ columns from the Feature Design Table
- API endpoint constant name → add to `lib/core/data/network/api_config.dart` if not present
---
## Phase 5 – Bloc
Follow every rule in [create-bloc.prompt.md](./create-bloc.prompt.md).
Context:
- `featureName` → `${input:featureName}`
- `featurePascalName` → `${input:featurePascalName}`
- `description` → the full _Event name_ + _State status variant_ + _Edge case_ columns
of the Feature Design Table
The `${input:featurePascalName}Status` enum must include every variant listed in the Feature
Design Table in addition to the baseline `initial, loading, success, error`.
---
## Phase 6 – View & Widgets
Follow the view-generation rules in
[create-screen-from-figma.prompt.md](./create-screen-from-figma.prompt.md) (Steps 4–5)
**combined with** the _View_ bullet from
[create-feature.prompt.md](./create-feature.prompt.md) (the _Per-file rules_ → View section).
Additional wiring from AC:
- Every _"navigates to …"_ clause → `BlocListener` branch calling `NavigationService` with `RouteConstants.*`
- Every UI state clause → `BlocBuilder` / `BlocSelector` branch
- Bloc events dispatched from the UI must match the Feature Design Table exactly
Extract every sub-tree of ≥ 3 widgets or reused in siblings into a `widget/` file per
the rules in [create-screen-from-figma.prompt.md](./create-screen-from-figma.prompt.md) (Step 5).
---
## Phase 7 – Route & Exports
Follow the _Route_ and _Exports_ bullets from
[create-feature.prompt.md](./create-feature.prompt.md) (the _Per-file rules_ section):
- Add `static const String ${input:featureName} = '/${input:featureName}';` to `RouteConstants`
- Add a `case RouteConstants.${input:featureName}:` to `CustomRouter.generateRoute`
- Add cross-feature exports to `lib/core/router/export.dart`
---
## Phase 8 – Tests
Follow every rule in [write-tests.prompt.md](./write-tests.prompt.md).
Context:
- Bloc file → `lib/features/${input:parentFolder}/${input:featureName}/bloc/${input:featureName}_bloc.dart`
- Test file → `test/features/${input:featureName}/bloc/${input:featureName}_bloc_test.dart`
- Every row of the Feature Design Table must map to at least one `blocTest` case
- Minimum 3 tests per handler: happy path, failure/null response, exception thrown
---
## Phase 9 – Security, Quality & Compliance Gate
Run [security-review.prompt.md](./security-review.prompt.md) **AND** [app-store-compliance.prompt.md](./app-store-compliance.prompt.md) scoped to all files generated
or modified in Phases 2–8.
Additionally verify:
- [ ] Every AC clause is covered by at least one test from Phase 8
- [ ] Every Figma token from Phase 1 is accessed through a project constant — no inlined hex/rgba/dp values in widget code
- [ ] Every row of the Feature Design Table is implemented across Bloc, Repo, and View
- [ ] `props` in all events/states with passwords or tokens → those fields are excluded
- [ ] No `http` package usage — only `DioUtil`
- [ ] No `print()` — only `DebugUtils.showPrint()`
- [ ] All sensitive values stored in `SecureStorageHelper`, not `SharedPreferenceHelper`
- [ ] All routes use `RouteConstants.*` — no inline string literals
- [ ] All colours use `AppColors.*` — no hardcoded hex
- [ ] All strings use `AppStrings.*` — no hardcoded user-visible text
- [ ] All dimensions use `AppDimensions.*` + ScreenUtil — no raw doubles
- [ ] File sizes: views < 300 lines, blocs < 200 lines, repos < 200 lines
- [ ] **Git Hooks Check**: Ensure generated code strictly follows `dart format .` and passes `flutter analyze` unconditionally. Never output deprecated widgets.
Do not end until all nine phases are complete and every checklist item passes.
''',
'skills/create-deep-link.prompt.md': r'''
---
agent: agent
description: Generate deep-linking logic, string parsing, and route constants to securely link external URLs natively into the application's BLoC architecture.
argument-hint: "Deep link path structure (e.g., /document/:id), target route, expected parameters, and trigger actions"
---
# Create Deep Link & Route Engine
Deep Link Path: `${input:deepLinkPath}` | Target Feature: `${input:targetFeature}`
Parameters Expected: `${input:parameters}`
## File Locations
Update the following files to register the deep link:
```
lib/core/routes/app_router.dart
```
---
## Step 1 – Strict Route Constants
Update the routing constants string to explicitly define the parameter structure. Never hardcode routing strings across the app.
```dart
// Inside lib/core/routes/app_router.dart (or equivalent constants file)
static const String ${input:targetFeature}Route = '/${input:targetFeature}';
static const String ${input:targetFeature}DeepLink = '/${input:targetFeature}/:id';
```
---
## Step 2 – Strong-Typed Arguments
When passing parameters from a Deep Link to a screen, **never** pass raw `Map<String, dynamic>`. Always scaffold a strongly-typed Arguments class to prevent runtime crashes.
```dart
@immutable
final class ${input:targetFeature}Args extends Equatable {
const ${input:targetFeature}Args({required this.id});
final String id;
// Defensive parsing from deep-link intent strings
factory ${input:targetFeature}Args.fromUri(Uri uri) {
return ${input:targetFeature}Args(
id: uri.pathSegments.last, // Example extraction
);
}
@override
List<Object?> get props => [id];
}
```
---
## Step 3 – The Routing Interceptor
Inside the `onGenerateRoute` or `CustomRouter.generateRoute` method, add the matching case. If the link is malformed, securely redirect the user to an Error or Home page.
```dart
case RouteConstants.${input:targetFeature}Route:
final args = settings.arguments;
// 1. Validate the arguments strictly
if (args is! ${input:targetFeature}Args) {
DebugUtils.showPrint('DeepLink Error: Invalid or missing arguments for ${input:targetFeature}');
return MaterialPageRoute(builder: (_) => const GenericErrorView());
}
// 2. Wrap the destination view in the Feature Bloc and fire the Load event immediately
return MaterialPageRoute(
builder: (_) => BlocProvider(
create: (context) => ${input:targetFeature}Bloc(
repo: ${input:targetFeature}Repo(),
)..add(Load${input:targetFeature}Event(id: args.id)), // Automatically triggers data fetch
child: const ${input:targetFeature}View(),
),
);
```
---
## Rules
- **Strong Typing**: URL parameters are notoriously unreliable. Treat every deep-link parameter as a potentially malicious/malformed string. Always parse securely using `.tryParse` if looking for integers.
- **Fail Gracefully**: If a user clicks a broken or expired deep link, it must **never crash**. Use a fallback route (e.g. `GenericErrorView` or Home) and log the failure using `DebugUtils`.
- **Pre-fire BLoC Events**: When deep-linking directly into a specific feature (like a Document View), standard practice is to inject the ID into the `BlocProvider` and immediately `.add(LoadEvent(id))` so the screen loads seamlessly.
- **`dynamic` Ban**: Never extract routing settings using `as dynamic`. Use strict `if (args is MyArgs)` type-checking.
- **Observability**: Any failed deep link attempts must be logged to Crashlytics via `DebugUtils.showPrint()`.
- **Testing**: Instruct the developer to test this link using `adb shell am start -W -a android.intent.action.VIEW -d "url"` or `xcrun simctl openurl booted "url"`.
''',
'skills/security-review.prompt.md': r'''
---
agent: agent
description: Perform a 6-pillar security and quality audit on a feature or file. Returns a finding table with severity ratings.
argument-hint: "Feature name or file path to audit"
---
# Security & Quality Review
Target: `${input:target}`
## Audit pillars
Evaluate every finding against all six pillars. Assign severity using the colour key below,
then emit a consolidated finding table followed by a prioritised fix list.
### 1 — Security
Check against OWASP Top-10 adapted for mobile/Flutter:
| Check | What to look for |
| ------------------------ | --------------------------------------------------------------------------------- |
| Secrets in source | Hardcoded API keys, tokens, passwords, base URLs in `.dart` files |
| Insecure storage | Auth tokens / credentials in `SharedPreferences` instead of `SecureStorageHelper` |
| Credential in Bloc state | Password or token field in `props` or stored beyond the handler that consumed it |
| Plaintext credentials | Passwords or secrets logged with `print`, `debugPrint`, or `DebugUtils` |
| WebView URL validation | URL loaded without checking against `ApiConfig` domain allowlist |
| HTTP instead of HTTPS | Any `http://` in endpoint constants |
| Certificate validation | `badCertificateCallback` set to `true` or similar bypass |
| Injection surface | Dynamic SQL / shell strings built from user input |
| `dart-define` bypass | `AppConfig.environment` mutated at runtime |
### 2 — Reliability
| Check | What to look for |
| ------------------------ | ---------------------------------------------------------------- |
| Missing `try/catch` | `async` function that calls network without `catch` |
| No terminal state | Handler that can exit without emitting `success` or `error` |
| `loading` not first | Handler emits API call before emitting `loading` |
| Raw exception to UI | `e.toString()` or exception message exposed in state / snackbar |
| Self-recursion in repo | Repo method that calls itself |
| `rethrow` without reason | `rethrow` where caller is not designed to handle |
| Unawaited futures | `async` call without `await` or explicit discard (`unawaited()`) |
| Missing stackTrace | `catch (e)` block that fails to capture and log the `stackTrace` |
| `BuildContext` async gap | Using `context` after an `await` without `if (!context.mounted) return;` |
| Memory Leaks | Missing `dispose()` method for `TextEditingController`, `ScrollController`, or Streams |
### 3 — Maintainability
| Check | What to look for |
| -------------------------- | --------------------------------------------------------------- |
| Hardcoded strings | User-visible strings not in `AppStrings` |
| Hardcoded colours / hex | Colour literals not in `AppColors` |
| Raw `Text(...)` widget | `Text(...)` used instead of `TextWidget` |
| Inline `TextStyle` | `TextStyle(...)` not from `FontStyles` getters |
| `MediaQuery` for sizing | `MediaQuery.of(context).size` used for layout dimensions |
| Raw doubles for dimensions | `SizedBox(height: 16)` etc. not from `AppDimensions`/ScreenUtil |
| Inline route strings | Route name strings not from `RouteConstants` |
| Cross-feature imports | Feature A importing directly from Feature B (not via `core/`) |
| Circular dependency | `core/` importing from `features/` |
| File size | Any file exceeding 300 lines |
| Function size | Any function exceeding 20 statements |
| `print` / `debugPrint` | Raw print calls not using `DebugUtils` |
| `dynamic` field | Model or state fields typed as `dynamic` |
| UI in Data Layer | Repositories returning `Future<T?>` instead of `Result<T>` or importing UI elements like Snackbars |
| Deprecated Widgets | Usage of widgets triggering `flutter analyze` warnings (e.g. `WillPopScope` instead of `PopScope`) |
| Routing in BLoC | `Navigator` or `NavigationService` calls happening inside a `_bloc.dart` instead of UI |
| Missing Semantics | Custom `GestureDetector` buttons lacking `Semantics` wrappers for screen readers |
| Obscured / Tiny Targets | Clickable areas that do not meet 48x48 dp minimum guidelines |
### 4 — Test coverage
| Check | What to look for |
| --------------------- | -------------------------------------------------------------------- |
| Missing bloc test | Public event handler without corresponding `blocTest` |
| Incomplete test cases | Handler tested with fewer than 3 cases (happy / failure / exception) |
| Real network in tests | Unit tests that instantiate real `DioUtil` or call actual endpoints |
| `var` / missing types | Test variables not explicitly typed |
### 5 — Duplication
| Check | What to look for |
| ---------------------- | -------------------------------------------------------------------- |
| Repeated UI snippets | Identical widget sub-trees not extracted to `widget/` |
| Duplicate API logic | Same endpoint called from multiple repos |
| Copy-paste state/event | State or event boilerplate copy-pasted and not DRY'd via shared base |
### 6 — Performance & Architecture Purity
| Check | What to look for |
| ---------------------- | -------------------------------------------------------------------- |
| Missing `const` | Neglecting `const` constructors for UI elements, causing useless widget rebuilds |
| `build()` pollution | Expensive loops, synchronous JSON parsing, or heavy logic inside the `build()` method |
| Main Isolate blocking | Parsing massive JSON strings natively instead of safely using `Isolate.run()` |
| Massive ListViews | Using `Column` or `ListView` instead of `ListView.builder` for unbounded lists |
---
## Output format
### Finding table
| # | File | Line | Pillar | Severity | Finding | Fix |
| --- | ------------------- | ---- | -------- | ----------- | ----------------- | ----------------------- |
| 1 | `path/to/file.dart` | 42 | Security | 🔴 Critical | Hardcoded API key | Move to `--dart-define` |
Severity key:
| Icon | Level | Definition |
| ---- | -------- | ----------------------------------------------------- |
| 🔴 | Critical | Immediate security breach or data loss risk |
| 🟠 | High | Likely failure in production or data exposure |
| 🟡 | Medium | Degrades reliability, maintainability, or testability |
| 🟢 | Low | Style / convention divergence; low impact |
### Fix list (prioritised)
List fixes in descending severity order. For each fix provide:
- **File path** (relative to `lib/`)
- **What to change** (concise instruction or code snippet)
- **Why** (one sentence referencing the relevant rule from the copilot instructions)
''',
'skills/write-tests.prompt.md': r'''
---
agent: agent
description: Write bloc_test unit tests for a Bloc class covering happy path, null/empty response, and exception thrown for each handler.
argument-hint: "Feature name, Bloc class path, and list of event handlers to cover"
---
# Write Bloc Tests
Feature: `${input:featureName}` | Bloc: `${input:featurePascalName}Bloc`
Handlers to cover:
```
${input:handlers}
```
## File location
```
test/features/${input:featureName}/bloc/${input:featureName}_bloc_test.dart
```
## Test template
```dart
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:flutter_base_project/features/${input:featureName}/bloc/${input:featureName}_bloc.dart';
import 'package:flutter_base_project/features/${input:featureName}/repo/${input:featureName}_repo.dart';
import 'package:flutter_base_project/core/types/result.dart';
// ── Mocks & Fakes ──────────────────────────────────────────────────────────
class Mock${input:featurePascalName}Repo extends Mock implements ${input:featurePascalName}Repo {}
class FakeRequestModel extends Fake implements RequestModel {} // Replace with actual request model type
// ── Helpers ────────────────────────────────────────────────────────────────
${input:featurePascalName}Bloc buildBloc(${input:featurePascalName}Repo repo) =>
${input:featurePascalName}Bloc(repo: repo);
// ── Tests ──────────────────────────────────────────────────────────────────
void main() {
late Mock${input:featurePascalName}Repo mockRepo;
setUpAll(() {
// CRITICAL: Prevent mocktail crash when passing custom models through any()
registerFallbackValue(FakeRequestModel());
});
setUp(() {
mockRepo = Mock${input:featurePascalName}Repo();
});
group('${input:featurePascalName}Bloc', () {
group('SubmitXxxEvent', () {
// ── arrange ──
const inputEvent = SubmitXxxEvent(someField: 'value');
blocTest<${input:featurePascalName}Bloc, ${input:featurePascalName}State>(
'emits [loading, success] on happy path',
setUp: () {
when(() => mockRepo.someWriteMethod(
requestModel: any(named: 'requestModel'),
)).thenAnswer((_) async => const Success(data: null));
},
build: () => buildBloc(mockRepo),
act: (bloc) => bloc.add(inputEvent),
expect: () => [
const ${input:featurePascalName}State(status: ${input:featurePascalName}Status.loading, message: ''),
const ${input:featurePascalName}State(status: ${input:featurePascalName}Status.success, message: ''),
],
);
blocTest<${input:featurePascalName}Bloc, ${input:featurePascalName}State>(
'emits [loading, error] when repo returns Failure',
setUp: () {
when(() => mockRepo.someWriteMethod(
requestModel: any(named: 'requestModel'),
)).thenAnswer(
(_) async => const Failure(message: 'Something went wrong.'),
);
},
build: () => buildBloc(mockRepo),
act: (bloc) => bloc.add(inputEvent),
expect: () => [
const ${input:featurePascalName}State(status: ${input:featurePascalName}Status.loading, message: ''),
const ${input:featurePascalName}State(
status: ${input:featurePascalName}Status.error,
message: 'Something went wrong.',
),
],
);
blocTest<${input:featurePascalName}Bloc, ${input:featurePascalName}State>(
'emits [loading, error] when repo throws exception',
setUp: () {
when(() => mockRepo.someWriteMethod(
requestModel: any(named: 'requestModel'),
)).thenThrow(Exception('network error'));
},
build: () => buildBloc(mockRepo),
act: (bloc) => bloc.add(inputEvent),
expect: () => [
const ${input:featurePascalName}State(status: ${input:featurePascalName}Status.loading, message: ''),
isA<${input:featurePascalName}State>()
.having((s) => s.status, 'status', ${input:featurePascalName}Status.error),
],
);
});
// Repeat group per handler listed in ${input:handlers}
});
}
```
## Rules
- **Minimum 3 tests per handler:** happy path, failure response, exception thrown.
- Use `bloc_test` `blocTest<B, S>` — never test `.state` after `add` manually.
- Mock repos with `mocktail` via constructor injection: `${input:featurePascalName}Bloc(repo: mockRepo)`.
- **Never** use real network calls in tests.
- Follow **Arrange–Act–Assert**; prefix variables: `inputEvent`, `mockRepo`, `actualState`, `expectedState`.
- Variable names: `input*` for inputs, `mock*` for mocks, `actual*` for captured values, `expected*` for expected values.
- Each `blocTest` description must start with: `'emits [<states>] when <condition>'`.
- Test file: `test/features/${input:featureName}/bloc/${input:featureName}_bloc_test.dart`.
- Register fallback values with `registerFallbackValue` in `setUpAll` if needed by mocktail.
- **Git Hooks Compliance**: The generated test file MUST strictly pass `dart format .` and `flutter analyze` without any custom deprecation warnings.
''',
'skills/create-bloc.prompt.md': r'''
---
agent: agent
description: Create a Bloc class, Event file, and State file for an existing or new feature following project BLoC conventions.
argument-hint: "Feature name in snake_case, PascalCase variant, and description of events/state needed"
---
# Create Bloc
Feature: `${input:featureName}` | PascalCase: `${input:featurePascalName}`
Events / state described:
```
${input:description}
```
## File layout
```
lib/features/<parentFolder>/${input:featureName}/bloc/
├── ${input:featureName}_bloc.dart ← Bloc class (main file)
├── ${input:featureName}_event.dart ← part of bloc
└── ${input:featureName}_state.dart ← part of bloc
```
## Bloc class — `${input:featureName}_bloc.dart`
```dart
import '../../../../core/router/export.dart';
part '${input:featureName}_event.dart';
part '${input:featureName}_state.dart';
class ${input:featurePascalName}Bloc extends Bloc<${input:featurePascalName}Event, ${input:featurePascalName}State> {
${input:featurePascalName}Bloc({${input:featurePascalName}Repo? repo})
: _repo = repo ?? ${input:featurePascalName}Repo(),
super(const ${input:featurePascalName}State()) {
on<SubmitXxxEvent>(_onSubmitXxx);
// add one on<> per event
}
final ${input:featurePascalName}Repo _repo;
Future<void> _onSubmitXxx(
SubmitXxxEvent event,
Emitter<${input:featurePascalName}State> emit,
) async {
emit(state.copyWith(status: ${input:featurePascalName}Status.loading));
try {
final Result<void> result = await _repo.someWriteMethod();
switch (result) {
case Success():
emit(state.copyWith(status: ${input:featurePascalName}Status.success));
case Failure(:final message):
emit(state.copyWith(status: ${input:featurePascalName}Status.error, message: message));
}
} catch (e, stackTrace) {
DebugUtils.showPrint('${input:featurePascalName}Bloc._onSubmitXxx error: $e\n$stackTrace');
// Forward the exception and stackTrace to your crash reporter here
emit(state.copyWith(
status: ${input:featurePascalName}Status.error,
message: ApiMsgStrings.somethingWentWrong,
));
}
}
}
```
## Event file — `${input:featureName}_event.dart`
```dart
part of '${input:featureName}_bloc.dart';
@immutable
sealed class ${input:featurePascalName}Event extends Equatable {
const ${input:featurePascalName}Event();
}
@immutable
final class SubmitXxxEvent extends ${input:featurePascalName}Event {
const SubmitXxxEvent({required this.someField});
final String someField;
@override
List<Object?> get props => [someField]; // never include passwords/tokens
}
```
## State file — `${input:featureName}_state.dart`
```dart
part of '${input:featureName}_bloc.dart';
enum ${input:featurePascalName}Status { initial, loading, success, error }
@immutable
final class ${input:featurePascalName}State extends Equatable {
const ${input:featurePascalName}State({
this.status = ${input:featurePascalName}Status.initial,
this.message = '',
// add domain fields here
});
final ${input:featurePascalName}Status status;
final String message;
${input:featurePascalName}State copyWith({
${input:featurePascalName}Status? status,
String? message,
bool clearMessage = false,
}) =>
${input:featurePascalName}State(
status: status ?? this.status,
message: clearMessage ? '' : (message ?? this.message),
);
@override
List<Object?> get props => [status, message];
}
```
## Rules
- Emit `loading` first in every async handler. Ensure you reset/clear previous error messages when doing so using the `clearMessage` flag.
- Always emit a terminal state (`success` or `error`) in **every** code path including `catch`.
- **Catch Blocks & Observability**: Always catch `(e, stackTrace)` so that the full context is logged and can be caught by a crash reporter.
- **Event Transformers**: If the event handles rapid user input (like searching or submitting a form button), explicitly apply `droppable()`, `restartable()`, or `debounceTime()` transformers from `bloc_concurrency`.
- **Form Validation**: If the feature handles many form fields, validate inputs inside the state before the API call to avoid unnecessary networking.
- Log errors: `DebugUtils.showPrint('${input:featurePascalName}Bloc.<handler> error: $e')`.
- **Never** perform navigation or show dialogs inside the Bloc.
- `props` must **never** include passwords, tokens, or credentials.
- Use barrel import `import '../../../../core/router/export.dart'` — not individual imports.
''',
'skills/create-model.prompt.md': r'''
---
agent: agent
description: Generate Dart model DTOs (request and/or response) from a JSON payload or field description, following project model conventions.
argument-hint: "Model name in PascalCase, request/response/both, raw JSON or field list"
---
# Create Model
Model: `${input:modelName}` | Type: `${input:modelType}` (request / response / both)
JSON or field list:
```json
${input:jsonOrFields}
```
## File location
```
lib/features/<parentFolder>/<featureName>/model/
├── ${input:modelFileName}_request_model.dart (if request or both)
└── ${input:modelFileName}_response_model.dart (if response or both)
```
## Request model template
```dart
import '../../../../core/router/export.dart';
@immutable
final class ${input:modelName}RequestModel extends Equatable {
const ${input:modelName}RequestModel({
required this.field1,
// add all required fields
});
final String field1;
Map<String, dynamic> toJson() => {
'field1': field1,
};
@override
List<Object?> get props => [field1];
}
```
## Response model template
```dart
import '../../../../core/router/export.dart';
@immutable
final class ${input:modelName}ResponseModel extends Equatable {
const ${input:modelName}ResponseModel({
required this.id,
this.createdAt,
// add all fields
});
factory ${input:modelName}ResponseModel.fromJson(Map<String, dynamic> json) =>
${input:modelName}ResponseModel(
// parsed defensively:
id: json['id']?.toString() ?? '',
createdAt: DateTime.tryParse(json['created_at']?.toString() ?? ''),
// map every JSON key
);
final String id;
final DateTime? createdAt;
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt?.toIso8601String(),
};
${input:modelName}ResponseModel copyWith({
String? id,
DateTime? createdAt,
}) =>
${input:modelName}ResponseModel(
id: id ?? this.id,
createdAt: createdAt ?? this.createdAt,
);
@override
List<Object?> get props => [id, createdAt];
}
```
## Rules
- `@immutable` on every model class.
- All fields are `final`.
- Every response model has a `factory fromJson(Map<String, dynamic> json)`.
- Every model has a `toJson()` → `Map<String, dynamic>`.
- Null-safe defaults: `?? ''`, `?? 0`, `?? false`, `?? []`, `?? {}`.
- **Never** declare a field as `dynamic` — cast and parse JSON values defensively to prevent `TypeError` crashes:
- String: `json['key']?.toString() ?? ''`
- int: `int.tryParse(json['key']?.toString() ?? '') ?? 0`
- double: `double.tryParse(json['key']?.toString() ?? '') ?? 0.0`
- bool: `json['key'] is bool ? json['key'] as bool : (json['key']?.toString().toLowerCase() == 'true')`
- DateTime: `DateTime.tryParse(json['key']?.toString() ?? '')`
- List: `(json['key'] as List<dynamic>? ?? []).map((e) => ItemModel.fromJson(e as Map<String, dynamic>)).toList()`
- **Equatable**: All models must extend `Equatable` and implement `get props => [...]` so they trigger reliable UI rebuilds when embedded in a BLoC State.
- Request models live in `<feature>/model/` named `<Noun>RequestModel`.
- Response models live in `<feature>/model/` named `<Noun>ResponseModel`.
- Provide `copyWith` on response models that are stored in Bloc state.
- Use barrel import `import '../../../../core/router/export.dart'` only if needed for shared types.
''',
};