@ds9-solutions/mcp 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +43 -0
  2. package/README.md +53 -0
  3. package/content/agents/code-reviewer.agent.md +52 -22
  4. package/content/agents/db-analyser.agent.md +121 -0
  5. package/content/agents/security-reviewer.agent.md +119 -37
  6. package/content/agents/test-writer.agent.md +84 -35
  7. package/content/skills/dart/SKILL.md +139 -0
  8. package/content/skills/dart/examples/entity.dart +37 -0
  9. package/content/skills/dart/examples/error_handling.dart +36 -0
  10. package/content/skills/dart/examples/order_test.dart +34 -0
  11. package/content/skills/dart/examples/repository.dart +57 -0
  12. package/content/skills/dart/references/checklist.md +12 -0
  13. package/content/skills/dart/references/documentation.md +26 -0
  14. package/content/skills/dart/references/naming.md +27 -0
  15. package/content/skills/dart-frog/SKILL.md +208 -0
  16. package/content/skills/dart-frog/examples/auth_middleware.dart +50 -0
  17. package/content/skills/dart-frog/examples/controller.dart +75 -0
  18. package/content/skills/dart-frog/examples/dto.dart +41 -0
  19. package/content/skills/dart-frog/examples/error_response.dart +35 -0
  20. package/content/skills/dart-frog/examples/exceptions.dart +60 -0
  21. package/content/skills/dart-frog/examples/middleware_composition.dart +34 -0
  22. package/content/skills/dart-frog/examples/model.dart +20 -0
  23. package/content/skills/dart-frog/examples/port.dart +17 -0
  24. package/content/skills/dart-frog/examples/repository.dart +56 -0
  25. package/content/skills/dart-frog/examples/route_collection.dart +25 -0
  26. package/content/skills/dart-frog/examples/route_item.dart +24 -0
  27. package/content/skills/dart-frog/examples/route_test.dart +33 -0
  28. package/content/skills/dart-frog/examples/service.dart +51 -0
  29. package/content/skills/dart-frog/examples/service_test.dart +55 -0
  30. package/content/skills/dart-frog/references/checklist.md +31 -0
  31. package/content/skills/dart-frog/references/error-handling.md +48 -0
  32. package/content/skills/dart-frog/references/fpdart-taskeither.md +53 -0
  33. package/content/skills/dart-frog/references/layers.md +43 -0
  34. package/content/skills/dart-frog/references/middlewares-di.md +45 -0
  35. package/content/skills/dart-frog/references/project-structure.md +67 -0
  36. package/content/skills/dart-frog/references/routing.md +53 -0
  37. package/content/skills/dart-frog/references/testing.md +24 -0
  38. package/content/skills/docker/SKILL.md +3 -3
  39. package/content/skills/driftorm/SKILL.md +249 -0
  40. package/content/skills/flutter/SKILL.md +419 -0
  41. package/content/skills/hex-pattern-dart/SKILL.md +83 -0
  42. package/content/skills/hex-pattern-dart/examples/adapter-in.md +24 -0
  43. package/content/skills/hex-pattern-dart/examples/adapter-out.md +20 -0
  44. package/content/skills/hex-pattern-dart/examples/errors.md +25 -0
  45. package/content/skills/hex-pattern-dart/examples/port.md +11 -0
  46. package/content/skills/hex-pattern-dart/examples/service.md +20 -0
  47. package/content/skills/hex-pattern-dart/examples/structure-tree.md +28 -0
  48. package/content/skills/hex-pattern-dart/references/anti-patterns.md +16 -0
  49. package/content/skills/hex-pattern-dart/references/checklist.md +34 -0
  50. package/content/skills/hex-pattern-dart/references/dependency-injection.md +32 -0
  51. package/content/skills/hex-pattern-dart/references/error-layers.md +27 -0
  52. package/content/skills/hex-pattern-dart/references/layers-and-dependency-rule.md +29 -0
  53. package/content/skills/hex-pattern-dart/references/persistence.md +23 -0
  54. package/content/skills/hex-pattern-dart/references/ports-and-adapters.md +33 -0
  55. package/content/skills/hex-pattern-dart/references/testing.md +30 -0
  56. package/content/skills/mysql/SKILL.md +3 -3
  57. package/content/skills/typescript/SKILL.md +4 -4
  58. package/dist/src/content.js +10 -2
  59. package/dist/src/init.js +1 -0
  60. package/dist/src/search.js +20 -12
  61. package/dist/src/server.js +60 -4
  62. package/dist/src/skill-files.js +125 -0
  63. package/package.json +2 -2
  64. package/content/agents/db-specialist.agent.md +0 -78
  65. package/content/agents/nestjs-api-designer.agent.md +0 -61
  66. package/content/skills/hexagonal/SKILL.md +0 -156
  67. package/content/skills/jest-testing/SKILL.md +0 -88
  68. package/content/skills/nestjs/SKILL.md +0 -100
  69. package/content/skills/typeorm/SKILL.md +0 -119
  70. package/dist/test/mcp.test.js +0 -112
@@ -0,0 +1,249 @@
1
+ ---
2
+ name: driftorm
3
+ description: Convenções e boas práticas do Drift, a biblioteca de banco de dados type-safe para Dart e Flutter (SQLite e Postgres). Use ao criar, editar, revisar ou testar tabelas, consultas, DAOs, migrations ou a classe de banco em projetos que dependem de drift.
4
+ ---
5
+
6
+ # Drift best practices
7
+
8
+ Baseado na documentação oficial: https://drift.simonbinder.eu (repositório simolus3/drift, `drift` 2.35.x).
9
+
10
+ ## Princípio central
11
+
12
+ O Drift gera código a partir do schema que você declara. O schema é a fonte da verdade: tabelas em Dart (ou SQL em arquivos `.drift`), uma classe de banco central e o `build_runner` gerando o resto. Toda mudança de schema é uma migration versionada e testada.
13
+
14
+ ## Setup
15
+
16
+ | Alvo | Dependências | Dev dependencies |
17
+ |---|---|---|
18
+ | Flutter | `drift`, `drift_flutter`, `path_provider` | `drift_dev`, `build_runner` |
19
+ | Dart (SQLite) | `drift`, `sqlite3` | `drift_dev`, `build_runner` |
20
+ | Dart (Postgres) | `drift`, `postgres`, `drift_postgres` | `drift_dev`, `build_runner` |
21
+
22
+ - `dart run build_runner build` gera o código uma vez. Durante o desenvolvimento, use `dart run build_runner watch`.
23
+ - Para Postgres, configure `sql: dialects: [postgres]` no `build.yaml`. Por padrão o gerador só emite SQL de SQLite.
24
+
25
+ ## Regras
26
+
27
+ ### Classe de banco
28
+
29
+ 1. **Uma classe `@DriftDatabase(tables: [...], daos: [...])`** que estende `_$NomeDatabase`, com `part 'arquivo.g.dart';`.
30
+ 2. **O construtor aceita um `QueryExecutor` opcional**: `AppDatabase([QueryExecutor? e]) : super(e ?? _openConnection());`. Isso permite injetar um banco em memória nos testes.
31
+ 3. **Use uma única instância (singleton) por arquivo de banco.** Abrir a mesma classe várias vezes sobre o mesmo arquivo quebra os streams e gera aviso em runtime. Forneça a instância por DI (Riverpod, get_it, provider).
32
+ 4. **Abra a conexão fora da thread principal.** No Flutter, use `driftDatabase(name: ...)`, do `drift_flutter`. Em Dart puro, use `NativeDatabase.createInBackground(File(...))`. Ambos rodam o SQLite em isolate.
33
+ 5. **Feche com `await db.close()`** no fim do ciclo de vida (testes, shutdown do servidor).
34
+
35
+ ### Tabelas
36
+
37
+ 6. **Uma tabela é uma classe que estende `Table`**, com colunas declaradas como `late final campo = tipo()();`. Os parênteses finais são obrigatórios.
38
+ 7. **Colunas são não-nulas por padrão.** Use `.nullable()` só quando `null` tiver significado de domínio.
39
+ 8. **Chave primária:** `integer().autoIncrement()()` para o caso comum. Para outros casos, sobrescreva `Set<Column> get primaryKey => {...}` (só com `=>` e literal de set).
40
+ 9. **Prefira `clientDefault()` a `withDefault()`.** O `clientDefault` é calculado em Dart e não exige migration. O `withDefault` altera o schema.
41
+ 10. **Chaves estrangeiras com `.references(Tabela, #coluna, onDelete: ...)`.** No SQLite elas vêm desligadas, então ative com `PRAGMA foreign_keys = ON` no `beforeOpen`.
42
+ 11. **Garanta integridade no schema:** `.unique()`, `uniqueKeys` para unicidade composta, `.check(expr)` para validação. Prefira `check` a `withLength`, que só valida em Dart.
43
+ 12. **Crie índices com `@TableIndex(name: ..., columns: {#col})`** para colunas muito filtradas. Chave primária, `unique` e alvo de FK já têm índice.
44
+ 13. **Em projeto novo, armazene `DateTime` como texto ISO-8601** com `store_date_time_values_as_text: true` no `build.yaml`. O padrão é timestamp Unix em segundos, sem fuso e sem frações.
45
+ 14. **Reaproveite colunas comuns** (`id`, `createdAt`) com mixins de tabela.
46
+ 15. **Enums e tipos próprios** usam `intEnum<T>()`, `textEnum<T>()` ou `TypeConverter`, em vez de conversão manual espalhada pelo código.
47
+ 16. **Rode o gerador de novo** sempre que mudar tabelas, DAOs ou o annotation do banco.
48
+
49
+ ### Consultas e escrita
50
+
51
+ 17. **Insira com `Companion.insert(...)`**, que torna obrigatórios só os campos sem default. Para atualização parcial, use `Companion(campo: Value(x))`.
52
+ 18. **Use a API tipada** (`select`, `into`, `update`, `delete`, joins, manager) antes de SQL cru. Quando precisar de SQL, prefira arquivos `.drift`, que o gerador valida em tempo de build. `customSelect` fica como último recurso e precisa de `readsFrom` para funcionar com `watch`.
53
+ 19. **`get()` para leitura única e `watch()` para UI reativa.** O stream já emite o valor atual ao ser escutado, então não combine `get()` com `watch()`.
54
+ 20. **`getSingle()` lança erro se não houver exatamente uma linha.** Quando a ausência é um caso válido, use `getSingleOrNull()` ou `watchSingleOrNull()`.
55
+
56
+ ### Organização
57
+
58
+ 21. **Separe as consultas por domínio em DAOs:** `@DriftAccessor(tables: [...])` estendendo `DatabaseAccessor<AppDatabase>` com o mixin gerado. Registre os DAOs em `daos:` no `@DriftDatabase`.
59
+ 22. **Não exponha a classe de banco para a UI ou para os handlers.** Exponha DAOs ou repositórios que retornam modelos e streams.
60
+
61
+ ### Transações
62
+
63
+ 23. **Operações que precisam ser atômicas vão em `transaction(() async { ... })`.**
64
+ 24. **Faça `await` em todas as chamadas dentro da transação.** Query sem `await` pode rodar depois do fechamento e causar perda de dados ou crash. Não agende timers que usem o banco dentro da transação.
65
+ 25. **Streams criados fora da transação só são notificados no commit.** Não escute fora da transação um stream criado dentro dela.
66
+
67
+ ### Migrations
68
+
69
+ 26. **Toda mudança de schema exige incrementar o `schemaVersion` e escrever uma migration.** O Drift não altera bancos existentes sozinho.
70
+ 27. **Use o fluxo guiado `make-migrations`.** Registre o banco em `databases:` no `build.yaml` e rode `dart run drift_dev make-migrations` **antes** da primeira mudança, para salvar o schema inicial. Depois, a cada mudança, incremente a versão e rode o comando de novo.
71
+ 28. **Escreva as migrations com `stepByStep(from1To2: ..., from2To3: ...)`**, usando o `schema` recebido e nunca as tabelas atuais da classe. Extraia o `stepByStep` para um getter de extensão em `GeneratedDatabase`, para não referenciar o schema atual por acidente.
72
+ 29. **Rode os testes de migration gerados** (em `test/drift/`) e versione a pasta `drift_schemas/` no Git.
73
+ 30. **Evite consultas dentro de callbacks de migration.** Se precisar, rode-as só depois de o schema chegar ao estado que elas esperam.
74
+ 31. **Pragmas e seed vão no `beforeOpen`**, que roda a cada abertura. Use `details.wasCreated` e `details.hadUpgrade` para condicionar o seed.
75
+
76
+ ### Testes
77
+
78
+ 32. **Teste com `NativeDatabase.memory()`**, abrindo no `setUp` e fechando no `tearDown`. Em widget tests do Flutter, envolva com `DatabaseConnection(..., closeStreamsSynchronously: true)`.
79
+
80
+ ## Exemplos
81
+
82
+ ### Tabelas, banco e conexão (Flutter)
83
+
84
+ ```dart
85
+ // lib/database/database.dart
86
+ import 'package:drift/drift.dart';
87
+ import 'package:drift_flutter/drift_flutter.dart';
88
+ import 'package:path_provider/path_provider.dart';
89
+
90
+ import 'database.steps.dart';
91
+
92
+ part 'database.g.dart';
93
+
94
+ mixin AutoIncrementingPrimaryKey on Table {
95
+ late final id = integer().autoIncrement()();
96
+ }
97
+
98
+ class Categories extends Table with AutoIncrementingPrimaryKey {
99
+ late final name = text().unique()();
100
+ }
101
+
102
+ @TableIndex(name: 'todo_category', columns: {#category})
103
+ class TodoItems extends Table with AutoIncrementingPrimaryKey {
104
+ // Coluna que se referencia no check precisa de tipo explícito.
105
+ late final Column<String> title =
106
+ text().check(title.length.isBiggerThanValue(0))();
107
+ late final done = boolean().clientDefault(() => false)();
108
+ late final category = integer()
109
+ .nullable()
110
+ .references(Categories, #id, onDelete: KeyAction.setNull)();
111
+ late final createdAt = dateTime().clientDefault(DateTime.now)();
112
+ }
113
+
114
+ @DriftDatabase(tables: [Categories, TodoItems], daos: [TodosDao])
115
+ class AppDatabase extends _$AppDatabase {
116
+ AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
117
+
118
+ @override
119
+ int get schemaVersion => 2;
120
+
121
+ @override
122
+ MigrationStrategy get migration => MigrationStrategy(
123
+ onUpgrade: _schemaUpgrade,
124
+ beforeOpen: (details) async {
125
+ await customStatement('PRAGMA foreign_keys = ON');
126
+ },
127
+ );
128
+
129
+ static QueryExecutor _openConnection() {
130
+ return driftDatabase(
131
+ name: 'app_database',
132
+ native: const DriftNativeOptions(
133
+ databaseDirectory: getApplicationSupportDirectory,
134
+ ),
135
+ );
136
+ }
137
+ }
138
+
139
+ extension Migrations on GeneratedDatabase {
140
+ OnUpgrade get _schemaUpgrade => stepByStep(
141
+ from1To2: (m, schema) async {
142
+ await m.createTable(schema.categories);
143
+ await m.addColumn(schema.todoItems, schema.todoItems.category);
144
+ },
145
+ );
146
+ }
147
+ ```
148
+
149
+ ### DAO com stream e transação
150
+
151
+ ```dart
152
+ // lib/database/todos_dao.dart
153
+ import 'package:drift/drift.dart';
154
+
155
+ import 'database.dart';
156
+
157
+ part 'todos_dao.g.dart';
158
+
159
+ @DriftAccessor(tables: [TodoItems, Categories])
160
+ class TodosDao extends DatabaseAccessor<AppDatabase> with _$TodosDaoMixin {
161
+ TodosDao(super.attachedDatabase);
162
+
163
+ Stream<List<TodoItem>> watchPending() {
164
+ return (select(todoItems)
165
+ ..where((t) => t.done.equals(false))
166
+ ..orderBy([(t) => OrderingTerm.desc(t.createdAt)]))
167
+ .watch();
168
+ }
169
+
170
+ Future<int> add(String title) {
171
+ return into(todoItems).insert(TodoItemsCompanion.insert(title: title));
172
+ }
173
+
174
+ Future<void> deleteCategory(int categoryId) {
175
+ return transaction(() async {
176
+ await (update(todoItems)..where((t) => t.category.equals(categoryId)))
177
+ .write(const TodoItemsCompanion(category: Value(null)));
178
+ await (delete(categories)..where((c) => c.id.equals(categoryId))).go();
179
+ });
180
+ }
181
+ }
182
+ ```
183
+
184
+ ### Conexão Postgres (backend Dart)
185
+
186
+ ```dart
187
+ import 'dart:io';
188
+
189
+ import 'package:drift_postgres/drift_postgres.dart';
190
+ import 'package:postgres/postgres.dart' as pg;
191
+
192
+ QueryExecutor openPostgres() {
193
+ final env = Platform.environment;
194
+ return PgDatabase(
195
+ endpoint: pg.Endpoint(
196
+ host: env['DB_HOST']!,
197
+ database: env['DB_NAME']!,
198
+ username: env['DB_USER'],
199
+ password: env['DB_PASSWORD'],
200
+ ),
201
+ );
202
+ }
203
+ ```
204
+
205
+ ### Teste
206
+
207
+ ```dart
208
+ import 'package:drift/drift.dart';
209
+ import 'package:drift/native.dart';
210
+ import 'package:test/test.dart';
211
+
212
+ import 'package:my_app/database/database.dart';
213
+
214
+ void main() {
215
+ late AppDatabase db;
216
+
217
+ setUp(() {
218
+ db = AppDatabase(
219
+ DatabaseConnection(
220
+ NativeDatabase.memory(),
221
+ closeStreamsSynchronously: true,
222
+ ),
223
+ );
224
+ });
225
+
226
+ tearDown(() => db.close());
227
+
228
+ test('watchPending emite o item recém-criado', () async {
229
+ final stream = db.todosDao.watchPending();
230
+ await db.todosDao.add('Escrever testes');
231
+
232
+ final items = await stream.first;
233
+ expect(items.map((t) => t.title), contains('Escrever testes'));
234
+ });
235
+ }
236
+ ```
237
+
238
+ ## Checklist de revisão
239
+
240
+ - [ ] Mudou tabela, DAO ou annotation? O `build_runner` foi rodado de novo?
241
+ - [ ] Mudou o schema? O `schemaVersion` foi incrementado, o `make-migrations` foi rodado e existe um passo `fromXToY` testado?
242
+ - [ ] A migration usa `schema.*` e não as tabelas atuais da classe?
243
+ - [ ] Existe uma única instância do banco, injetada por DI?
244
+ - [ ] A conexão usa `driftDatabase` ou `createInBackground`?
245
+ - [ ] FKs no SQLite têm `PRAGMA foreign_keys = ON`?
246
+ - [ ] Toda chamada dentro de `transaction` tem `await`?
247
+ - [ ] Defaults usam `clientDefault`, salvo quando o default precisa estar no schema?
248
+ - [ ] `getSingle` é usado só onde a linha certamente existe?
249
+ - [ ] Os testes usam `NativeDatabase.memory()` e fecham o banco no `tearDown`?
@@ -0,0 +1,419 @@
1
+ ---
2
+ name: flutter
3
+ description: Padrões de arquitetura, código e qualidade para apps Flutter com Provider, go_router e fpdart. Use ao criar, editar, revisar ou testar arquivos em lib/ ou test/ de um projeto Flutter, como features, providers, services, models, views, rotas, tema ou testes.
4
+ ---
5
+
6
+ # Flutter best practices
7
+
8
+ Arquitetura em camadas com **Provider + ChangeNotifier**, **go_router** e **fpdart** (`TaskEither`/`Either`), alinhada ao guia oficial de arquitetura do Flutter (MVVM com camada de dados) e ao Effective Dart.
9
+
10
+ ## Stack
11
+
12
+ - **Estado:** `provider` com `ChangeNotifier`. Não misture Riverpod, Bloc ou GetX.
13
+ - **Navegação:** `go_router`.
14
+ - **Erros e async:** `fpdart`. Operações que podem falhar retornam `TaskEither`, nunca `Future` que lança exceção entre camadas.
15
+ - **Configuração:** `flutter_dotenv` (`.env`) ou `--dart-define`. Nunca coloque segredos ou URLs fixas no código.
16
+ - **Lints:** `flutter_lints`.
17
+
18
+ ## Estrutura de pastas
19
+
20
+ ```
21
+ lib/
22
+ ├── main.dart # bootstrap + composição de dependências
23
+ ├── components/ # features
24
+ │ └── <feature>/
25
+ │ ├── models/
26
+ │ ├── providers/ # ChangeNotifier (ViewModel)
27
+ │ ├── services/ # acesso a dados via ports (pode ter dtos/ e mappers/)
28
+ │ ├── views/ # telas
29
+ │ │ └── components/ # widgets exclusivos da feature
30
+ │ └── components/ # subfeatures, com a mesma estrutura
31
+ └── shared/
32
+ ├── components/ # widgets reutilizáveis (buttons, inputs, widgets...)
33
+ ├── constraints/ # AppConstraints (bordas, breakpoints, limites)
34
+ ├── errors/ # Failure, exceções e Log
35
+ ├── infra/ # armazenamento local etc.
36
+ ├── models/
37
+ ├── providers/ # rotas e sessão
38
+ ├── services/ # implementações de infraestrutura
39
+ │ └── ports/ # contratos abstratos (HTTP, storage, push...)
40
+ ├── theme/ # paleta + extensão de ThemeData
41
+ ├── typography/ # AppTypography.of(context)
42
+ └── utils/
43
+ ```
44
+
45
+ 1. **Diretamente em `lib/`** só podem existir `components/`, `shared/`, `main.dart` e arquivos gerados de configuração (por exemplo, `firebase_options.dart`).
46
+ 2. **Na raiz de cada feature** (e de cada subfeature em `components/`), as únicas pastas permitidas são `models`, `providers`, `services`, `views` e `components`.
47
+ 3. **Use `components/` para subfeatures** quando a feature se divide por perfil ou área (por exemplo, `admin/components/reports`).
48
+
49
+ ## Camadas e fluxo de dados
50
+
51
+ ```
52
+ View ──context.read/watch──▶ Provider ──▶ Service ──▶ Port (shared/services/ports)
53
+ ▲ .run() + match/fold TaskEither<Failure, T> TaskEither<Exception, T> ▲
54
+ └──────── AppSnackBars ◀──── Log.print + Failure ◀───────────────────────────────┘
55
+ implementado em shared/services
56
+ ```
57
+
58
+ 4. **Ports** são classes `abstract` em `shared/services/ports/` e retornam `TaskEither<Exception, T>`. As implementações concretas ficam em `shared/services/`.
59
+ 5. **Services de feature** recebem *ports* pelo construtor, nunca a implementação concreta. Eles convertem JSON em model e retornam `TaskEither<Exception, T>`, sem estado, sem `notifyListeners` e sem `BuildContext`.
60
+ 6. **Providers** estendem `ChangeNotifier` e recebem o service pelo construtor. Guardam estado privado exposto por getters e retornam `TaskEither<Failure, T>` (normalmente `Unit`). Em erro, registram com `Log` e devolvem a `Failure` do domínio.
61
+ 7. **Views** usam `context.read<T>()` para ações e `context.watch<T>()` para estado. Executam com `.run()` e tratam o resultado com `.match(...)` ou `.fold(...)`.
62
+ 8. **fpdart em toda fronteira entre camadas:**
63
+ - Em `views → providers`, a view consome com `.run()` **e** `.match`, `.fold`, `.getOrElse`, `.isLeft` ou `.isRight`. Só `mapLeft` não basta, porque ignora o caminho de sucesso.
64
+ - Em `providers → services`, o arquivo importa `package:fpdart/fpdart.dart` e trabalha com `TaskEither` ou `Either`.
65
+ - Em `services → shared/services`, vale a mesma regra.
66
+ 9. **Direção única de dependência.** Uma view nunca importa um service, e um provider nunca importa uma view. A lógica de negócio fica fora dos widgets.
67
+ 10. **Crie uma camada de *use case*** só quando a lógica for complexa ou compartilhada entre providers. Caso contrário, ela fica no provider ou no service.
68
+
69
+ ## Regras de código
70
+
71
+ ### Documentação e estilo
72
+
73
+ 11. **Toda declaração tem `///` logo acima:** classes, construtores, métodos, getters, funções, enums, extensions, mixins, typedefs e campos de classe. Variáveis locais estão isentas. Siga o Effective Dart: primeira frase curta terminada em ponto, campos descritos com substantivo e booleanos começando com "Indica se...".
74
+ 12. **Não use `var`.** Use `final`, `const` ou tipo explícito.
75
+ 13. **Todo arquivo passa por `dart format`.** Código fora do formato não é commitado.
76
+ 14. **`flutter analyze --fatal-infos` precisa passar limpo.** Não use `// ignore` sem justificativa no próprio comentário.
77
+ 15. **Importe pelo caminho do pacote** (`package:<app>/...`). Use alias (`as admin`, `as customer`) quando houver nomes de classe iguais em features diferentes.
78
+ 16. **Ao mudar dependências, versione o `pubspec.lock`**, e no CI use `flutter pub get --enforce-lockfile`.
79
+
80
+ ### Models
81
+
82
+ 17. **Models são classes imutáveis**, com campos `final`, construtor `const` e `factory fromJson(Map<String, dynamic>)` defensivo (`?.toString() ?? ''`, `DateTime.tryParse`). Nada de `dynamic` vazando para fora do model.
83
+ 18. **Textos de exibição derivados** (rótulo, tradução, ícone) ficam em getters do model ou em `shared/utils`, nunca espalhados pela view.
84
+ 19. **DTOs e mappers** ficam em `services/dtos/` e `services/mappers/` quando o formato da API difere do model de domínio.
85
+
86
+ ### Erros e logs
87
+
88
+ 20. **`Failure` é uma `sealed class`** em `shared/errors/failure.dart`, com uma subclasse por domínio (`AuthFailure`, `LoadingFailure`, `ValidationFailure`, `GeneralFailure`...). Use a do domínio certo. Se faltar uma, crie a subclasse em vez de reaproveitar outra.
89
+ 21. **A mensagem da `Failure` é texto para o usuário**, claro e sem detalhe técnico. O detalhe técnico vai para o log, com `origin` (classe), `method`, `error` e `stack`.
90
+ 22. **Não use `print` nem `debugPrint` soltos.** Centralize em um `Log` com níveis (Debug, Info, Warning, Error, Critical) sobre `dart:developer`. Nunca registre tokens, senhas ou dados pessoais sensíveis.
91
+
92
+ ### Estado e injeção de dependência
93
+
94
+ 23. **A composição é explícita em `main.dart`.** Ela instancia os ports concretos, depois os services e por fim os providers, sempre por construtor. Não use service locator.
95
+ 24. **Escopo dos providers:**
96
+ - Globais (auth, sessão) ficam no `MultiProvider` acima do `MaterialApp.router`.
97
+ - Providers de uma área ficam com `ChangeNotifierProvider.value` no `ShellRoute` dessa área.
98
+ - Providers de tela efêmera são criados no `builder` da `GoRoute`.
99
+ 25. **Carga inicial** acontece em `initState` com `WidgetsBinding.instance.addPostFrameCallback((_) => _load())`.
100
+ 26. **Depois de qualquer `await` na view**, confira `if (!mounted) return;` (ou `context.mounted`) antes de usar o `context`.
101
+ 27. **Paralelize com `Future.wait`** somente chamadas independentes, e trate cada resultado com tipo explícito, sem `as List<T>`.
102
+ 28. **Exponha coleções como somente leitura** (`List.unmodifiable`) e só chame `notifyListeners()` quando o estado mudar de fato.
103
+
104
+ ### Navegação
105
+
106
+ 29. **Paths são constantes em uma classe `RoutesMapping`.** Nunca escreva string de rota solta na view.
107
+ 30. **Toda `GoRoute` tem `name`.** Navegue com `context.go(...)` ou `context.push(...)` usando `RoutesMapping`.
108
+ 31. **Autenticação e acesso** são decididos no `redirect` do `GoRouter`, com `refreshListenable` apontando para o provider de auth. Não faça checagem de login dentro das telas.
109
+ 32. **Áreas com navegação própria** (por perfil, com bottom bar) usam `ShellRoute` com `navigatorKey` dedicado.
110
+ 33. **Push notifications e deep links** passam por um único método do gerenciador de rotas.
111
+
112
+ ### UI, tema e acessibilidade
113
+
114
+ 34. **Cores vêm de uma `enum` de paleta** (com getter `color`) **e de uma extensão de `ThemeData`** (`Theme.of(context).primary`). Evite `Colors.*` e hexadecimais na view.
115
+ 35. **Textos usam `AppTypography.of(context).<estilo>(...)`**, que escolhe entre mobile e tablet. Evite `TextStyle(fontSize: ...)` solto.
116
+ 36. **Botões, inputs, modais e snackbars** usam os componentes de `shared/components` ou os estilos expostos pela extensão de tema. Raio de borda, breakpoints e limites ficam em `AppConstraints`.
117
+ 37. **Feedback ao usuário** passa por `AppSnackBars.showSuccess`, `showError` e `showWarning`.
118
+ 38. **Layout responsivo** decide com `AppConstraints.isMobile` ou `isTablet` no nível da tela, e com `LayoutBuilder` dentro dos widgets.
119
+ 39. **Widgets pequenos e `const`:** quebre a tela em widgets dentro de `views/components/` em vez de métodos `_buildX()` gigantes.
120
+ 40. **Acessibilidade:** ícones clicáveis têm `tooltip` ou `Semantics`, e ícones informativos têm `semanticLabel`.
121
+
122
+ ### Testes
123
+
124
+ 41. **Testes ficam em `test/`, espelhando `lib/`**, com sufixo `_test.dart`.
125
+ 42. **Services são testados com um *fake* do port** (uma classe que implementa o contrato). Providers são testados com o service real sobre esse port fake. Não acesse rede nem serviços externos em teste unitário.
126
+ 43. **Widgets são testados com `testWidgets`**, envolvendo a tela em `MaterialApp` e `ChangeNotifierProvider.value`. Use `pump` para mudanças simples e `pumpAndSettle` para animações e operações async.
127
+
128
+ ## Fluxo para criar uma feature
129
+
130
+ - [ ] **1. Pasta.** Crie `lib/components/<feature>/` com `models/`, `services/`, `providers/` e `views/`.
131
+ - [ ] **2. Model.** Crie a classe imutável com `fromJson` defensivo.
132
+ - [ ] **3. Port.** Se precisar de infraestrutura nova, crie o contrato em `shared/services/ports/` e a implementação em `shared/services/`.
133
+ - [ ] **4. Service.** Receba os ports pelo construtor e retorne `TaskEither<Exception, T>`.
134
+ - [ ] **5. Provider.** Estenda `ChangeNotifier`, guarde o estado privado e use `TaskEither.tryCatch`, `Log` e a `Failure` do domínio.
135
+ - [ ] **6. View.** Leia com `context.watch`, aja com `context.read` e consuma com `.run()` + `.match`.
136
+ - [ ] **7. Rota.** Adicione a constante em `RoutesMapping` e a `GoRoute` nomeada.
137
+ - [ ] **8. DI.** Instancie o service e o provider em `main.dart` e registre no escopo certo.
138
+ - [ ] **9. Testes.** Teste o service com port fake, o provider e o widget principal.
139
+ - [ ] **10. Validação:**
140
+
141
+ ```bash
142
+ dart format lib test
143
+ flutter analyze --fatal-infos
144
+ flutter test
145
+ ```
146
+
147
+ ## Exemplos (feature ilustrativa: tarefas)
148
+
149
+ ### Port
150
+
151
+ ```dart
152
+ // lib/shared/services/ports/app_http_port.dart
153
+ import 'package:fpdart/fpdart.dart';
154
+
155
+ /// Contrato de acesso HTTP à API do app.
156
+ abstract class AppHttpPort {
157
+ /// Executa um GET em [path] e converte a resposta com [fromJson].
158
+ TaskEither<Exception, T> get<T>(String path, T Function(dynamic) fromJson);
159
+ }
160
+ ```
161
+
162
+ ### Model
163
+
164
+ ```dart
165
+ // lib/components/tasks/models/task_model.dart
166
+
167
+ /// Representa uma tarefa do usuário.
168
+ class TaskModel {
169
+ /// Identificador da tarefa.
170
+ final String id;
171
+
172
+ /// Título exibido na lista.
173
+ final String title;
174
+
175
+ /// Data de conclusão, quando houver.
176
+ final DateTime? doneAt;
177
+
178
+ /// Cria uma tarefa imutável.
179
+ const TaskModel({required this.id, required this.title, this.doneAt});
180
+
181
+ /// Constrói a tarefa a partir do JSON da API.
182
+ factory TaskModel.fromJson(Map<String, dynamic> json) {
183
+ return TaskModel(
184
+ id: json['id']?.toString() ?? '',
185
+ title: json['title']?.toString() ?? '',
186
+ doneAt: DateTime.tryParse(json['done_at']?.toString() ?? ''),
187
+ );
188
+ }
189
+
190
+ /// Indica se a tarefa foi concluída.
191
+ bool get isDone => doneAt != null;
192
+ }
193
+ ```
194
+
195
+ ### Service
196
+
197
+ ```dart
198
+ // lib/components/tasks/services/tasks_service.dart
199
+ import 'package:fpdart/fpdart.dart';
200
+ import 'package:my_app/components/tasks/models/task_model.dart';
201
+ import 'package:my_app/shared/services/ports/app_http_port.dart';
202
+
203
+ /// Acessa os dados de tarefas.
204
+ class TasksService {
205
+ /// Porta HTTP para a API.
206
+ final AppHttpPort _httpPort;
207
+
208
+ /// Cria o serviço com a porta HTTP injetada.
209
+ TasksService(this._httpPort);
210
+
211
+ /// Busca as tarefas do usuário autenticado.
212
+ TaskEither<Exception, List<TaskModel>> getTasks() {
213
+ return _httpPort.get<List<TaskModel>>('/tasks', (json) {
214
+ final List<dynamic> list = json is List
215
+ ? json
216
+ : (json['data'] ?? <dynamic>[]) as List<dynamic>;
217
+ return list
218
+ .map((item) => TaskModel.fromJson(item as Map<String, dynamic>))
219
+ .toList();
220
+ });
221
+ }
222
+ }
223
+ ```
224
+
225
+ ### Provider
226
+
227
+ ```dart
228
+ // lib/components/tasks/providers/tasks_provider.dart
229
+ import 'package:flutter/foundation.dart';
230
+ import 'package:fpdart/fpdart.dart';
231
+ import 'package:my_app/components/tasks/models/task_model.dart';
232
+ import 'package:my_app/components/tasks/services/tasks_service.dart';
233
+ import 'package:my_app/shared/errors/failure.dart';
234
+ import 'package:my_app/shared/errors/log.dart';
235
+
236
+ /// Gerencia o estado da tela de tarefas.
237
+ class TasksProvider extends ChangeNotifier {
238
+ /// Serviço de acesso às tarefas.
239
+ final TasksService _service;
240
+
241
+ /// Cria o provider com o serviço injetado.
242
+ TasksProvider(this._service);
243
+
244
+ /// Tarefas carregadas.
245
+ List<TaskModel> _tasks = const [];
246
+
247
+ /// Indica se as tarefas estão sendo carregadas.
248
+ bool _isLoading = false;
249
+
250
+ /// Tarefas carregadas, somente leitura.
251
+ List<TaskModel> get tasks => List.unmodifiable(_tasks);
252
+
253
+ /// Indica se as tarefas estão sendo carregadas.
254
+ bool get isLoading => _isLoading;
255
+
256
+ /// Carrega as tarefas e notifica a tela.
257
+ TaskEither<Failure, Unit> load() {
258
+ return TaskEither.tryCatch(
259
+ () async {
260
+ _setLoading(true);
261
+ try {
262
+ final result = await _service.getTasks().run();
263
+ _tasks = result.getOrElse((error) => throw error);
264
+ return unit;
265
+ } finally {
266
+ _setLoading(false);
267
+ }
268
+ },
269
+ (error, stack) {
270
+ Log.print<Error>(
271
+ message: 'Erro ao carregar tarefas',
272
+ origin: 'TasksProvider',
273
+ method: 'load',
274
+ error: error,
275
+ stack: stack,
276
+ );
277
+ return const LoadingFailure('Não foi possível carregar suas tarefas.');
278
+ },
279
+ );
280
+ }
281
+
282
+ /// Atualiza o indicador de carregamento e notifica os ouvintes.
283
+ void _setLoading(bool value) {
284
+ _isLoading = value;
285
+ notifyListeners();
286
+ }
287
+ }
288
+ ```
289
+
290
+ ### View
291
+
292
+ ```dart
293
+ // lib/components/tasks/views/tasks_view.dart
294
+ import 'package:flutter/material.dart';
295
+ import 'package:my_app/components/tasks/providers/tasks_provider.dart';
296
+ import 'package:my_app/shared/components/widgets/app_snackbars.dart';
297
+ import 'package:my_app/shared/theme/app_theme.dart';
298
+ import 'package:my_app/shared/typography/app_typography.dart';
299
+ import 'package:provider/provider.dart';
300
+
301
+ /// Tela com a lista de tarefas.
302
+ class TasksView extends StatefulWidget {
303
+ /// Cria a tela de tarefas.
304
+ const TasksView({super.key});
305
+
306
+ /// Cria o estado da tela.
307
+ @override
308
+ State<TasksView> createState() => _TasksViewState();
309
+ }
310
+
311
+ /// Estado da tela de tarefas.
312
+ class _TasksViewState extends State<TasksView> {
313
+ /// Agenda a carga inicial após o primeiro frame.
314
+ @override
315
+ void initState() {
316
+ super.initState();
317
+ WidgetsBinding.instance.addPostFrameCallback((_) => _load());
318
+ }
319
+
320
+ /// Carrega as tarefas e mostra erro amigável em caso de falha.
321
+ Future<void> _load() async {
322
+ final result = await context.read<TasksProvider>().load().run();
323
+ if (!mounted) return;
324
+ result.match(
325
+ (failure) => AppSnackBars.showError(context, failure.message),
326
+ (_) {},
327
+ );
328
+ }
329
+
330
+ /// Monta a lista de tarefas.
331
+ @override
332
+ Widget build(BuildContext context) {
333
+ final provider = context.watch<TasksProvider>();
334
+ final typography = AppTypography.of(context);
335
+
336
+ if (provider.isLoading) {
337
+ return Center(
338
+ child: CircularProgressIndicator(color: Theme.of(context).primary),
339
+ );
340
+ }
341
+
342
+ return ListView.separated(
343
+ padding: const EdgeInsets.all(16),
344
+ itemCount: provider.tasks.length,
345
+ separatorBuilder: (_, _) => const SizedBox(height: 8),
346
+ itemBuilder: (context, index) {
347
+ final task = provider.tasks[index];
348
+ return ListTile(
349
+ title: Text(task.title, style: typography.topic()),
350
+ trailing: Icon(
351
+ task.isDone ? Icons.check_circle : Icons.schedule,
352
+ color: Theme.of(context).primary,
353
+ semanticLabel: task.isDone ? 'Concluída' : 'Pendente',
354
+ ),
355
+ );
356
+ },
357
+ );
358
+ }
359
+ }
360
+ ```
361
+
362
+ ### Teste do service com port fake
363
+
364
+ ```dart
365
+ // test/components/tasks/services/tasks_service_test.dart
366
+ import 'package:flutter_test/flutter_test.dart';
367
+ import 'package:fpdart/fpdart.dart';
368
+ import 'package:my_app/components/tasks/services/tasks_service.dart';
369
+ import 'package:my_app/shared/services/ports/app_http_port.dart';
370
+
371
+ /// Porta HTTP falsa que devolve uma resposta fixa.
372
+ class _FakeHttpPort implements AppHttpPort {
373
+ /// Cria a porta com a resposta a devolver.
374
+ _FakeHttpPort(this.response);
375
+
376
+ /// Resposta JSON simulada.
377
+ final Object response;
378
+
379
+ /// Devolve a resposta simulada convertida pelo [fromJson].
380
+ @override
381
+ TaskEither<Exception, T> get<T>(String path, T Function(dynamic) fromJson) =>
382
+ TaskEither.of(fromJson(response));
383
+ }
384
+
385
+ void main() {
386
+ test('converte a lista da API em TaskModel', () async {
387
+ final service = TasksService(
388
+ _FakeHttpPort({
389
+ 'data': [
390
+ {'id': '1', 'title': 'Revisar PR', 'done_at': '2026-05-10'},
391
+ ],
392
+ }),
393
+ );
394
+
395
+ final result = await service.getTasks().run();
396
+
397
+ final tasks = result.getOrElse((error) => throw error);
398
+ expect(tasks.single.title, 'Revisar PR');
399
+ expect(tasks.single.isDone, isTrue);
400
+ });
401
+ }
402
+ ```
403
+
404
+ ## Evite
405
+
406
+ - **`Failure` genérica ou errada:** por exemplo, `AuthFailure` para erro de carregamento.
407
+ - **Estilos fixos na view:** `Colors.white`, `TextStyle(fontSize: 16)`, `Colors.grey.shade300`.
408
+ - **Casts depois de `Future.wait`:** algo como `results[0] as List<T>`.
409
+ - **Import duplicado** do mesmo arquivo com e sem alias.
410
+ - **Declarações sem `///`.**
411
+ - **Consumo de `TaskEither` só com `mapLeft`**, que ignora o caminho de sucesso.
412
+ - **`try/catch` dentro da view** para tratar erro de provider. O erro já chega como `Failure`.
413
+
414
+ ## Referências
415
+
416
+ - Guia de arquitetura do Flutter: https://docs.flutter.dev/app-architecture
417
+ - Testes no Flutter: https://docs.flutter.dev/testing/overview
418
+ - Effective Dart: https://dart.dev/effective-dart
419
+ - Skills oficiais do Flutter: https://github.com/flutter/skills