@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,83 @@
1
+ ---
2
+ name: hex-pattern-dart
3
+ description: Padrão hexagonal (ports & adapters) da DS9 para projetos Dart, independente de framework — organização por contexto em domain (models, ports, services) e adapter (controllers, dtos, repository), regra de dependência, camadas de erro fixas (BusinessException, ClientException, MiddlewareException), composição de dependências na borda e resultado tipado com TaskEither. Use ao criar, revisar ou refatorar módulos/features, definir camadas, ports, services, adapters ou repositórios em Dart, ou quando o usuário pedir "arquitetura hexagonal", "ports and adapters" ou "organização de novo contexto".
4
+ ---
5
+
6
+ # Padrão Hexagonal DS9 para Dart (`hex-pattern-dart`)
7
+
8
+ Base arquitetural dos projetos Dart da DS9. Isola a regra de negócio de protocolo, banco e framework, deixando o núcleo testável e substituível. Siga junto com a skill `dart` (nomenclatura, `///`, OOP). Para um framework específico, use também a skill dele (ex.: `dart-frog` aplica este padrão em APIs HTTP). Esta skill não depende de nenhum framework nem de projeto específico. **Escopo:** back-end e pacotes Dart de domínio. Apps Flutter seguem a skill `flutter` (organização própria em components/providers/services); dela aproveite só os princípios (regra de dependência, ports, `TaskEither`).
9
+
10
+ Os blocos de código nos arquivos complementares são **pseudocódigo** ilustrativo: adapte à sintaxe e às versões do projeto.
11
+
12
+ ## 1. Regras inegociáveis
13
+
14
+ 1. **Regra de dependência:** `adapter → domain`. O `domain` nunca importa `adapter`, framework, driver de banco nem tipos de protocolo (request/response).
15
+ 2. **Resultado tipado:** operações que podem falhar retornam `TaskEither<Exception, T>` (`fpdart`), criado com `TaskEither.tryCatch`. Não lance exceção através das camadas. Se `fpdart` não estiver no `pubspec.yaml`, informe que ele é obrigatório para seguir o padrão DS9 e peça autorização para instalar.
16
+ 3. **Composição na borda:** dependências são montadas em um único ponto (composition root) e injetadas por construtor. Nunca instancie service, repositório ou controller dentro da rota/handler.
17
+ 4. **Camadas de erro fixas:** `BusinessException` (domínio), `ClientException` (resposta ao cliente) e `MiddlewareException` (borda). Erro 5xx nunca expõe mensagem interna.
18
+ 5. **Sem regra de negócio no adapter:** controller traduz protocolo ↔ chamada do service; repositório só persiste e mapeia.
19
+ 6. **Projeto existente:** preserve a estrutura atual; aplique o padrão no código novo e no que for pedido, sem refatorar o restante.
20
+
21
+ ## 2. Estrutura por contexto
22
+
23
+ ```text
24
+ lib/
25
+ ├── <contexto>/ # bounded context (ex.: orders)
26
+ │ ├── domain/
27
+ │ │ ├── models/ # entidades e value objects (invariantes)
28
+ │ │ ├── ports/ # contratos que o domínio precisa (saída)
29
+ │ │ └── services/ # casos de uso; dependem só de ports e models
30
+ │ └── adapter/
31
+ │ ├── controllers/ # entrada: traduz protocolo → service → resposta
32
+ │ ├── dtos/ # contratos de entrada/saída + validação
33
+ │ └── repository/ # saída: implementa ports (persistência, clients)
34
+ └── common/ # exceções, tipos e utilitários compartilhados
35
+ ```
36
+
37
+ - Imports **relativos** dentro de `lib/`; o composition root e os testes usam `package:`.
38
+ - Um contexto não importa `domain`/`adapter` de outro. Comunique-se por port ou por tipos em `common/`.
39
+
40
+ ## 3. Responsabilidades
41
+
42
+ | Peça | Faz | Não faz |
43
+ |---|---|---|
44
+ | `model` | Invariantes, comportamento do negócio | I/O, serialização de protocolo |
45
+ | `port` | Declara o contrato (`abstract interface class`) | Implementação |
46
+ | `service` | Orquestra ports, aplica regra e autorização de recurso, retorna `TaskEither` | Conhece HTTP, SQL, DTO |
47
+ | `controller` | Valida DTO, chama service, mapeia erro de negócio → `ClientException` | Regra de negócio |
48
+ | `dto` | Entrada/saída do protocolo, `fromJson`/`toJson` | Ser usado no domínio |
49
+ | `repository` | Implementa port, mapeia modelo ↔ persistência | Regra de negócio |
50
+
51
+ ## 4. Erros por camada
52
+
53
+ - Repositório/service: `TaskEither.tryCatch` converte falha em `BusinessException` (com `domainName`, `statusCode`, mensagem segura, erro original e stack).
54
+ - Controller: converte `BusinessException` em `ClientException` (mensagem segura; 5xx genérico).
55
+ - Borda (middleware/handler): converte `ClientException`/`MiddlewareException` em resposta de protocolo.
56
+
57
+ ## 5. Checklist rápido
58
+
59
+ - [ ] O `domain` não importa nada de `adapter` nem de framework?
60
+ - [ ] Todo retorno que pode falhar é `TaskEither` via `tryCatch`?
61
+ - [ ] Dependências vêm do composition root por construtor?
62
+ - [ ] Os erros usam as 3 camadas e 5xx não vaza detalhe?
63
+ - [ ] Controller sem regra de negócio; repositório sem regra de negócio?
64
+ - [ ] DTO fica fora do `domain`?
65
+ - [ ] Existem testes do service com fakes das ports?
66
+
67
+ ## 6. Arquivos complementares (leitura sob demanda)
68
+
69
+ Use `list_skill_files` e `get_skill_file` com `skill: hex-pattern-dart`.
70
+
71
+ | Dúvida | Arquivo |
72
+ |---|---|
73
+ | Camadas e regra de dependência | `references/layers-and-dependency-rule.md` |
74
+ | Como desenhar ports e adapters | `references/ports-and-adapters.md` |
75
+ | Camadas de erro | `references/error-layers.md` |
76
+ | Injeção e composition root | `references/dependency-injection.md` |
77
+ | Persistência agnóstica e mapper | `references/persistence.md` |
78
+ | Estratégia de testes | `references/testing.md` |
79
+ | Erros comuns | `references/anti-patterns.md` |
80
+ | Checklist completo de review | `references/checklist.md` |
81
+ | Árvore de pastas de exemplo | `examples/structure-tree.md` |
82
+ | Port (`<Papel>Port`), service e adapters (pseudocódigo) | `examples/port.md`, `examples/service.md`, `examples/adapter-in.md`, `examples/adapter-out.md` |
83
+ | Hierarquia de exceções | `examples/errors.md` |
@@ -0,0 +1,24 @@
1
+ # Adapter de entrada — controller (pseudocódigo)
2
+
3
+ ```text
4
+ // adapter/controllers/order_controller.dart
5
+ /// Traduz o protocolo para o service e o resultado para a resposta.
6
+ class OrderController
7
+ final OrderService _service
8
+
9
+ OrderController(this._service)
10
+
11
+ Task<Response> getById(Context context, String id)
12
+ return _service.getById(id, context.requesterId)
13
+ .mapLeft(toClientException) // BusinessException -> ClientException
14
+ .match(
15
+ (client) => Response(status: client.statusCode, body: client.message),
16
+ (order) => Response.ok(OrderResponse.from(order)),
17
+ )
18
+
19
+ toClientException(BusinessException e)
20
+ safe = e.statusCode >= 500 ? "Erro interno" : e.message // 5xx genérico
21
+ return ClientException(statusCode: e.statusCode, message: safe)
22
+ ```
23
+
24
+ Pontos-chave: sem regra de negócio; service recebido por construtor; erro 5xx sem detalhe interno; o log guarda `e.error` e `e.stackTrace`.
@@ -0,0 +1,20 @@
1
+ # Adapter de saída — repository (pseudocódigo)
2
+
3
+ ```text
4
+ // adapter/repository/order_repository.dart
5
+ /// Implementa OrderRepositoryPort sobre a tecnologia de persistência do projeto.
6
+ class OrderRepository implements OrderRepositoryPort
7
+ final DataSource _source // abstração do acesso físico
8
+
9
+ OrderRepository(this._source)
10
+
11
+ TaskEither<BusinessException, Order> findById(String id)
12
+ return TaskEither.tryCatch(
13
+ () async => OrderMapper.toDomain(await _source.read(id)),
14
+ (error, stack) => BusinessException(
15
+ domainName: "orders", statusCode: 500,
16
+ message: "Falha ao consultar pedido", error: error, stackTrace: stack),
17
+ )
18
+ ```
19
+
20
+ Pontos-chave: toda chamada de I/O em `tryCatch`; mapper converte persistência → domínio; a falha vira `BusinessException` com mensagem segura.
@@ -0,0 +1,25 @@
1
+ # Hierarquia de exceções (pseudocódigo)
2
+
3
+ ```text
4
+ // common/exceptions
5
+ /// Falha de regra de negócio ou de infraestrutura vista pelo domínio.
6
+ class BusinessException implements Exception
7
+ String domainName
8
+ int statusCode
9
+ String message // segura para o cliente
10
+ Object? error // causa original (somente log)
11
+ StackTrace? stackTrace
12
+
13
+ /// Falha pronta para ser devolvida ao cliente.
14
+ class ClientException implements Exception
15
+ int statusCode
16
+ String message
17
+
18
+ /// Falha da borda (autenticação, parsing, composição).
19
+ class MiddlewareException implements Exception
20
+ int statusCode
21
+ String message
22
+ StackTrace? stackTrace
23
+ ```
24
+
25
+ Regra: `BusinessException` → (controller) → `ClientException`. A borda converte qualquer uma das duas, ou `MiddlewareException`, em resposta de protocolo.
@@ -0,0 +1,11 @@
1
+ # Port (pseudocódigo)
2
+
3
+ ```text
4
+ // domain/ports/order_repository.dart
5
+ /// Contrato de persistência de pedidos exigido pelo domínio.
6
+ abstract interface class OrderRepositoryPort
7
+ TaskEither<BusinessException, Order> findById(String id)
8
+ TaskEither<BusinessException, Unit> save(Order order)
9
+ ```
10
+
11
+ Pontos-chave: só depende de `Order` e de `BusinessException`; nenhuma menção a banco, SQL ou protocolo.
@@ -0,0 +1,20 @@
1
+ # Service (pseudocódigo)
2
+
3
+ ```text
4
+ // domain/services/order_service.dart
5
+ /// Casos de uso de pedidos.
6
+ class OrderService
7
+ final OrderRepositoryPort _repository // recebido por construtor
8
+
9
+ OrderService(this._repository)
10
+
11
+ TaskEither<BusinessException, Order> getById(String id, String requesterId)
12
+ return _repository.findById(id).flatMap((order) =>
13
+ order.belongsTo(requesterId) // autorização de recurso aqui
14
+ ? TaskEither.right(order)
15
+ : TaskEither.left(BusinessException(
16
+ domainName: "orders", statusCode: 403, message: "Acesso negado"))
17
+ )
18
+ ```
19
+
20
+ Pontos-chave: depende só da port; sem DTO, sem protocolo; regra e autorização no service; retorno `TaskEither`.
@@ -0,0 +1,28 @@
1
+ # Árvore de pastas (exemplo)
2
+
3
+ ```text
4
+ lib/
5
+ ├── common/
6
+ │ ├── exceptions/
7
+ │ │ ├── business_exception.dart
8
+ │ │ ├── client_exception.dart
9
+ │ │ └── middleware_exception.dart
10
+ │ └── error_response.dart
11
+ └── orders/
12
+ ├── domain/
13
+ │ ├── models/order.dart
14
+ │ ├── ports/order_repository.dart
15
+ │ └── services/order_service.dart
16
+ └── adapter/
17
+ ├── controllers/order_controller.dart
18
+ ├── dtos/create_order_dto.dart
19
+ └── repository/
20
+ ├── order_mapper.dart
21
+ └── order_repository.dart
22
+ test/
23
+ └── orders/
24
+ ├── domain/services/order_service_test.dart
25
+ └── adapter/controllers/order_controller_test.dart
26
+ ```
27
+
28
+ Adicione um novo contexto criando a mesma árvore sob `lib/<contexto>/`.
@@ -0,0 +1,16 @@
1
+ # Anti-padrões
2
+
3
+ | Anti-padrão | Por que é problema | Correção |
4
+ |---|---|---|
5
+ | Instanciar controller/service dentro da rota | Impede troca e teste; acopla borda ao concreto | Injetar pelo composition root |
6
+ | Regra de negócio em middleware/controller | Some em testes de domínio; duplica em outros adapters | Mover para o service |
7
+ | Ler segmento de path por posição para autorizar | Frágil e fácil de burlar | Autorizar no service com o identificador do recurso |
8
+ | `error.toString()` na resposta | Vaza detalhe interno ao cliente | Mensagem segura em `ClientException` |
9
+ | `catch (_) {}` genérico | Engole a causa e o stack | `TaskEither.tryCatch` + `BusinessException` |
10
+ | DTO usado no `domain` | Acopla negócio ao protocolo | DTO só em `adapter/dtos` |
11
+ | Model de persistência exposto ao `domain` | Acopla negócio ao banco | Mapper no repositório |
12
+ | Service que acessa banco/HTTP direto | Quebra a regra de dependência | Criar port e adapter |
13
+ | Service "faz tudo" (god service) | Difícil de testar e evoluir | Um service por caso de uso/agregado |
14
+ | Contexto importando `domain` de outro | Acoplamento entre contextos | Port ou tipo em `common/` |
15
+ | Exceção lançada entre camadas | Fluxo de erro invisível no tipo | Retornar `TaskEither` |
16
+ | `null` como "não encontrado" | Ambíguo | `Option`/`left` explícito |
@@ -0,0 +1,34 @@
1
+ # Checklist de review — hexagonal DS9 (Dart)
2
+
3
+ ## Estrutura
4
+ - [ ] Organização por contexto com `domain/{models,ports,services}` e `adapter/{controllers,dtos,repository}`.
5
+ - [ ] Imports relativos dentro de `lib/`.
6
+ - [ ] Nenhum contexto importa `domain`/`adapter` de outro.
7
+
8
+ ## Dependência
9
+ - [ ] `domain` sem import de `adapter`, framework, driver ou tipos de protocolo.
10
+ - [ ] Ports como `abstract interface class` em `domain/ports`.
11
+ - [ ] Repositórios implementam ports e vivem em `adapter/repository`.
12
+
13
+ ## Erros
14
+ - [ ] `TaskEither` + `TaskEither.tryCatch` em toda operação falível.
15
+ - [ ] `BusinessException` → `ClientException` → `MiddlewareException`.
16
+ - [ ] 5xx com mensagem genérica; sem `toString()`/stack no cliente.
17
+ - [ ] Sem `catch (_)` genérico nem exceção lançada entre camadas.
18
+
19
+ ## Composição
20
+ - [ ] Dependências por construtor e montadas no composition root.
21
+ - [ ] Nada instanciado dentro de rota/handler/controller.
22
+ - [ ] `fpdart` presente no `pubspec.yaml` (ou usuário informado).
23
+
24
+ ## Responsabilidades
25
+ - [ ] Controller sem regra de negócio; repositório sem regra de negócio.
26
+ - [ ] Autorização de recurso no service.
27
+ - [ ] DTO fora do `domain`; mapper entre persistência e model.
28
+
29
+ ## Testes
30
+ - [ ] Service testado com fakes das ports (sucesso e cada erro).
31
+ - [ ] `test/` espelha `lib/`.
32
+
33
+ ## Dart
34
+ - [ ] Regras da skill `dart`: nomenclatura, `///` em APIs públicas, OOP, null safety.
@@ -0,0 +1,32 @@
1
+ # Injeção de dependência e composition root
2
+
3
+ ## Princípio
4
+
5
+ Existe **um** lugar onde o grafo de dependências é montado (composition root). O restante do código recebe dependências por construtor e só conhece abstrações (ports).
6
+
7
+ ## Regras
8
+
9
+ 1. Dependências entram por construtor, como `final` e por interface.
10
+ 2. Nunca instancie service, repositório ou controller dentro de rota/handler/controller.
11
+ 3. O composition root escolhe a implementação concreta de cada port.
12
+ 4. Ordem de montagem: infraestrutura → repositórios → services → controllers.
13
+ 5. Reaproveite instâncias (cache/singleton no composition root) quando o recurso for caro (conexão, cliente HTTP).
14
+ 6. Em frameworks com middleware ou provider, use o mecanismo do framework como composition root (ver a skill do framework, ex.: `dart-frog`).
15
+ 7. Em testes, o composition root é substituído por fakes das ports.
16
+
17
+ ## Pseudocódigo
18
+
19
+ ```text
20
+ compose():
21
+ dataSource = ConcreteDataSource(config)
22
+ repository = OrderRepository(dataSource)
23
+ service = OrderService(repository)
24
+ controller = OrderController(service)
25
+ return controller
26
+ ```
27
+
28
+ ## Evite
29
+
30
+ - Localizador global (`ServiceLocator.get()`) dentro do `domain`.
31
+ - Dependência criada com `new`/construtor direto dentro do método que a usa.
32
+ - Ler configuração/ambiente fora do composition root.
@@ -0,0 +1,27 @@
1
+ # Camadas de erro
2
+
3
+ Os nomes das camadas são fixos em todo projeto DS9.
4
+
5
+ | Camada | Tipo | Onde nasce | Campos | Quem consome |
6
+ |---|---|---|---|---|
7
+ | Negócio | `BusinessException` | service / repository | `domainName`, `statusCode`, `message` (segura), `error`, `stackTrace` | controller |
8
+ | Cliente | `ClientException` | controller | `statusCode`, `message` | borda (handler/middleware) |
9
+ | Borda | `MiddlewareException` | middleware / handler | `statusCode`, `message`, `stackTrace?` | resposta final |
10
+
11
+ ## Regras
12
+
13
+ 1. Falhas esperadas viajam como valor: `TaskEither.left(BusinessException(...))`.
14
+ 2. `tryCatch` converte qualquer exceção inesperada em `BusinessException`, preservando `error` e `stackTrace` para log.
15
+ 3. Mensagem para o cliente é sempre segura. Para 5xx use texto genérico; detalhes ficam no log.
16
+ 4. Nunca devolva `error.toString()` nem stack ao cliente.
17
+ 5. Evite `catch (_)` genérico sem converter para a camada correta.
18
+ 6. `domainName` identifica o contexto de origem e ajuda a rastrear logs.
19
+
20
+ ## Fluxo
21
+
22
+ ```text
23
+ repository.tryCatch(falha) ──► BusinessException
24
+ service repassa / enriquece ──► BusinessException
25
+ controller.mapLeft ──────────► ClientException (5xx genérico)
26
+ borda.match ─────────────────► resposta de protocolo
27
+ ```
@@ -0,0 +1,29 @@
1
+ # Camadas e regra de dependência
2
+
3
+ ## Sentido das dependências
4
+
5
+ ```text
6
+ adapter/controllers ─┐
7
+ adapter/dtos ────────┼──► domain/services ──► domain/ports ◄── adapter/repository
8
+ │ │
9
+ └──────────┴──► domain/models
10
+ ```
11
+
12
+ - `domain` conhece apenas `domain` e `common`.
13
+ - `adapter` conhece `domain` e `common`.
14
+ - O `domain` declara a port; o `adapter/repository` a implementa (inversão de dependência).
15
+
16
+ ## O que pertence a cada camada
17
+
18
+ - **models:** entidades, value objects, regras que valem em qualquer protocolo ou banco.
19
+ - **ports:** contratos de saída (persistência, mensageria, serviços externos). Nomeie `<Papel>Port`: `OrderRepositoryPort`, `PaymentGatewayPort`.
20
+ - **services:** um caso de uso por método público. Aplicam regra e autorização de recurso.
21
+ - **controllers/dtos:** tudo que depende de protocolo (HTTP, fila, CLI).
22
+ - **repository:** tudo que depende de infraestrutura (banco, API externa).
23
+
24
+ ## Sinais de violação
25
+
26
+ - `import` de pacote de framework, driver ou `adapter` dentro de `domain/`.
27
+ - DTO ou tipo de request/response aparecendo em assinatura de service.
28
+ - Service que monta SQL, chama HTTP ou lê variável de ambiente.
29
+ - Controller com `if` de regra de negócio.
@@ -0,0 +1,23 @@
1
+ # Persistência agnóstica
2
+
3
+ O padrão não prescreve banco nem ORM. Para a tecnologia do projeto, consulte a skill correspondente via `list_skills`/`recommend_for_task` e preserve o que já existe.
4
+
5
+ ## Contrato
6
+
7
+ - O `domain` declara a port (`OrderRepositoryPort`).
8
+ - O `adapter/repository` implementa a port usando a tecnologia do projeto.
9
+ - Opcionalmente, o repositório delega a um `DataSource` (abstração do acesso físico), facilitando trocar ou simular o banco.
10
+
11
+ ## Mapper
12
+
13
+ - Modelo de persistência (linha/documento) e model do domínio são tipos distintos.
14
+ - Um mapper converte nos dois sentidos e vive no `adapter/repository`.
15
+ - O `domain` nunca importa o tipo de persistência.
16
+
17
+ ## Regras
18
+
19
+ 1. Toda chamada de I/O dentro de `TaskEither.tryCatch`.
20
+ 2. Falha vira `BusinessException` com mensagem segura.
21
+ 3. "Não encontrado" é resultado explícito (`left` com status adequado ou `Option`), nunca `null` implícito.
22
+ 4. Transação: abra no adapter; o service expõe a operação de negócio completa, sem conhecer a transação.
23
+ 5. Migrations seguem a skill de persistência do projeto e não alteram estrutura existente sem pedido.
@@ -0,0 +1,33 @@
1
+ # Ports e adapters
2
+
3
+ ## Ports (saída)
4
+
5
+ - Declare como `abstract interface class` dentro de `domain/ports/`.
6
+ - Expresse a necessidade do domínio, não a tecnologia: `save(order)`, não `insertRow(map)`.
7
+ - Assinaturas usam apenas models e tipos de `common/`.
8
+ - Toda operação que pode falhar retorna `TaskEither<BusinessException, T>`.
9
+
10
+ ```text
11
+ abstract interface class OrderRepositoryPort
12
+ TaskEither<BusinessException, Order> findById(String id)
13
+ TaskEither<BusinessException, Unit> save(Order order)
14
+ ```
15
+
16
+ ## Adapters de entrada (controllers)
17
+
18
+ - Recebem o contexto do protocolo (request) a cada chamada de método.
19
+ - Validam o DTO, chamam o service e mapeiam o resultado para resposta.
20
+ - Mapeiam `BusinessException` → `ClientException`; 5xx com mensagem genérica.
21
+ - Recebem o service por construtor; nunca o criam.
22
+
23
+ ## Adapters de saída (repository)
24
+
25
+ - Implementam a port e escondem o detalhe de persistência.
26
+ - Mapeiam modelo de persistência ↔ model do domínio (mapper dedicado).
27
+ - Envolvem cada chamada externa em `TaskEither.tryCatch` e convertem a falha em `BusinessException`.
28
+
29
+ ## Nomenclatura
30
+
31
+ - Arquivos `snake_case`; classes `UpperCamelCase`.
32
+ - Port: `<Papel>Port` (`OrderRepositoryPort`). Implementação: nome do papel sem sufixo (`OrderRepository`), sem prefixo `I` e sem sufixo `Impl`.
33
+ - DTO: `<Acao><Entidade>Dto` / `<Entidade>Response`.
@@ -0,0 +1,30 @@
1
+ # Testes na arquitetura hexagonal
2
+
3
+ ## Estratégia
4
+
5
+ | Alvo | Como testar | Dublês |
6
+ |---|---|---|
7
+ | `model` | Unitário puro | Nenhum |
8
+ | `service` | Unitário com ports falsas | Fakes/mocks das ports |
9
+ | `controller` | Unitário com service falso e contexto de protocolo simulado | Service falso |
10
+ | `repository` | Integração com a tecnologia real ou em memória | Opcional |
11
+ | Borda (rota/handler) | Teste de contrato: status, corpo e método não suportado | Composition root de teste |
12
+
13
+ ## Regras
14
+
15
+ 1. A árvore de `test/` espelha `lib/`.
16
+ 2. Teste o caminho feliz **e** cada `left` (erro de negócio, erro inesperado, 5xx genérico).
17
+ 3. Verifique que o 5xx não vaza mensagem interna.
18
+ 4. Para `TaskEither`, execute com `.run()` e inspecione o `Either` resultante.
19
+ 5. Prefira fakes simples a mocks com muitas expectativas quando a port for pequena.
20
+ 6. Nenhum teste de `domain` pode precisar de rede, banco ou framework.
21
+ 7. Nome: `<comportamento> quando <condição>`.
22
+
23
+ ## Pseudocódigo
24
+
25
+ ```text
26
+ test "retorna Right quando o pedido existe":
27
+ repo = FakeOrderRepository(with: order)
28
+ result = await OrderService(repo).getById("1").run()
29
+ expect(result.isRight)
30
+ ```
@@ -5,7 +5,7 @@ description: Diretrizes de modelagem, indexação, migrations, performance e seg
5
5
 
6
6
  # MySQL — Diretrizes DS9
7
7
 
8
- Convenções obrigatórias para schema, queries e migrations em MySQL (compatível com MariaDB), usadas em conjunto com a skill `typeorm` para a camada de persistência em projetos NestJS/hexagonal.
8
+ Convenções obrigatórias para schema, queries e migrations em MySQL (compatível com MariaDB), usadas com a camada de persistência do projeto.
9
9
 
10
10
  ## Engine e charset
11
11
 
@@ -52,7 +52,7 @@ deleted_at DATETIME(3) NULL -- se o domínio usa soft delete
52
52
 
53
53
  ## Migrations
54
54
 
55
- - Toda alteração de schema é uma migration versionada (via TypeORM migrations — ver skill `typeorm`), nunca alteração manual direto no banco de produção.
55
+ - Toda alteração de schema é uma migration versionada (via o mecanismo de migrations do projeto), nunca alteração manual direto no banco de produção.
56
56
  - Migrations são **imutáveis** após merge/deploy: para corrigir, crie uma nova migration (nunca edite uma já aplicada em outro ambiente).
57
57
  - Toda migration precisa de `up` e `down` reversível. Se o `down` não for possível com segurança (ex.: `DROP COLUMN`), documente explicitamente no corpo da migration.
58
58
  - Alterações em tabelas grandes (`ADD COLUMN`, `ADD INDEX`) em produção devem considerar lock de tabela: preferir `ALGORITHM=INPLACE, LOCK=NONE` quando suportado, e avaliar janela de manutenção para tabelas muito grandes.
@@ -66,7 +66,7 @@ deleted_at DATETIME(3) NULL -- se o domínio usa soft delete
66
66
 
67
67
  ## Queries
68
68
 
69
- - Nunca concatene valores de usuário diretamente em SQL — sempre use *prepared statements*/query builder parametrizado (via TypeORM). Isso é mandatório contra SQL Injection.
69
+ - Nunca concatene valores de usuário diretamente em SQL — sempre use *prepared statements*/query builder parametrizado (via a biblioteca de acesso a dados do projeto). Isso é mandatório contra SQL Injection.
70
70
  - Evite `SELECT *`; liste as colunas necessárias, especialmente em tabelas largas.
71
71
  - Pagine toda listagem (`LIMIT`/`OFFSET` ou keyset pagination para datasets grandes); nunca retorne uma tabela inteira sem limite.
72
72
  - Prefira keyset pagination (`WHERE id > :lastId ORDER BY id LIMIT :n`) a `OFFSET` alto em tabelas grandes (OFFSET grande é O(n)).
@@ -5,7 +5,7 @@ description: Convenções de TypeScript da DS9 — tipagem estrita, modelagem de
5
5
 
6
6
  # TypeScript — Diretrizes DS9
7
7
 
8
- Convenções de código TypeScript aplicáveis a qualquer projeto da empresa, incluindo os que usam NestJS (skill `nestjs`) e arquitetura hexagonal (skill `hexagonal`).
8
+ Convenções de código TypeScript aplicáveis a qualquer projeto da empresa, incluindo os que seguem arquitetura hexagonal em geral.
9
9
 
10
10
  ## Configuração do compilador
11
11
 
@@ -31,7 +31,7 @@ function handle(payload: OrderPayload) { return payload.items.map((i) => i.id);
31
31
 
32
32
  ## Modelagem de domínio
33
33
 
34
- - Use *value objects* para conceitos com regras próprias (ex.: `Email`, `Money`), em vez de `string`/`number` soltos, quando o projeto já segue esse padrão (ver skill `hexagonal`).
34
+ - Use *value objects* para conceitos com regras próprias (ex.: `Email`, `Money`), em vez de `string`/`number` soltos, quando o projeto já segue esse padrão.
35
35
  - Invariantes de negócio são validadas no construtor/factory da entidade (`Order.create(...)`), lançando erro de domínio em vez de permitir estado inválido existir.
36
36
  - Prefira classes/funções imutáveis (métodos retornam uma nova instância) para entidades de domínio sensíveis a concorrência, salvo quando o projeto adota mutação controlada por padrão.
37
37
 
@@ -44,7 +44,7 @@ function handle(payload: OrderPayload) { return payload.items.map((i) => i.id);
44
44
  ## Imports e organização de módulos
45
45
 
46
46
  - Imports absolutos/com alias (`@app/domain/...`) para cruzar camadas, imports relativos (`./`, `../`) dentro do mesmo módulo/camada — configurar via `paths` no `tsconfig.json`.
47
- - Nunca import circular entre módulos; se ocorrer, é sinal de fronteira de módulo mal definida (revisar com a skill `hexagonal`).
47
+ - Nunca import circular entre módulos; se ocorrer, é sinal de fronteira de módulo mal definida (revisar a organização dos módulos).
48
48
  - Um export principal por arquivo quando possível (facilita navegação e testes); evite arquivos "barril" (`index.ts`) gigantes que reexportam dezenas de símbolos sem necessidade.
49
49
 
50
50
  ## Nulidade e valores opcionais
@@ -57,7 +57,7 @@ function handle(payload: OrderPayload) { return payload.items.map((i) => i.id);
57
57
  - Classes/interfaces/tipos: `PascalCase`. Variáveis/funções: `camelCase`. Constantes verdadeiramente globais/imutáveis: `UPPER_SNAKE_CASE`.
58
58
  - Nomes de função descrevem a ação (`calculateTotal`, não `total` ou `doIt`); booleanos começam com `is`/`has`/`should` (`isValid`, `hasDiscount`).
59
59
  - Evite abreviações não óbvias (`qty` ok se for convenção do domínio; `tmp`, `data2` não).
60
- - Nome de **arquivo**: `kebab-case`, com sufixo que indica o papel do arquivo quando aplicável (`order-repository.port.ts`, `create-order.use-case.ts`, `typeorm-order.repository.ts`, `order.entity.ts`, `order.controller.ts`, `order.module.ts`). Arquivo de teste usa o mesmo nome base + `.spec.ts`/`.e2e-spec.ts` (ver skill `jest-testing`).
60
+ - Nome de **arquivo**: `kebab-case`, com sufixo que indica o papel do arquivo quando aplicável (`order-repository.port.ts`, `create-order.use-case.ts`, `order.repository.ts`, `order.entity.ts`, `order.controller.ts`, `order.module.ts`). Arquivo de teste usa o mesmo nome base + `.spec.ts`/`.e2e-spec.ts` (seguindo a convenção de testes do projeto).
61
61
 
62
62
  ## Checklist ao escrever/revisar código TypeScript
63
63
 
@@ -9,6 +9,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
9
9
  import { dirname, join } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { parse as parseYaml } from "yaml";
12
+ import { scanSkillFiles } from "./skill-files.js";
12
13
  // Frontmatter = bloco YAML entre dois `---` no topo do arquivo. Aceita \r\n porque os arquivos
13
14
  // podem ter sido salvos no Windows.
14
15
  const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
@@ -50,9 +51,12 @@ export function resolveContentRoot() {
50
51
  * Skill = `skills/<pasta>/SKILL.md` (id = nome da pasta).
51
52
  * Agente = `agents/<nome>.agent.md` (id = nome do arquivo; estrutura plana, sem subpastas).
52
53
  */
53
- export function loadEntries(root = resolveContentRoot()) {
54
+ export function loadEntries(root = resolveContentRoot(),
55
+ // Por padrão os avisos vão para stderr; testes podem injetar um coletor.
56
+ onWarning = (w) => console.error(`[ds9-mcp] aviso: ${w}`)) {
54
57
  const entries = [];
55
58
  const warnings = [];
59
+ const fileWarnings = [];
56
60
  const skillsDir = join(root, "skills");
57
61
  for (const dirent of readdirSync(skillsDir, { withFileTypes: true })) {
58
62
  const file = join(skillsDir, dirent.name, "SKILL.md");
@@ -71,7 +75,10 @@ export function loadEntries(root = resolveContentRoot()) {
71
75
  name: String(data.name ?? dirent.name),
72
76
  description: String(data.description ?? ""),
73
77
  body,
78
+ files: scanSkillFiles(join(skillsDir, dirent.name), fileWarnings),
74
79
  });
80
+ for (const w of fileWarnings.splice(0))
81
+ warnings.push(`skill "${dirent.name}": ${w}`);
75
82
  }
76
83
  const agentsDir = join(root, "agents");
77
84
  if (existsSync(agentsDir)) {
@@ -87,12 +94,13 @@ export function loadEntries(root = resolveContentRoot()) {
87
94
  name: String(data.name ?? id),
88
95
  description: String(data.description ?? ""),
89
96
  body,
97
+ files: [],
90
98
  });
91
99
  }
92
100
  }
93
101
  // stderr, nunca stdout: no transporte stdio o stdout é o canal do protocolo MCP, e qualquer
94
102
  // texto extra nele corromperia a comunicação com a IDE.
95
103
  for (const w of warnings)
96
- console.error(`[ds9-mcp] aviso: ${w}`);
104
+ onWarning(w);
97
105
  return entries;
98
106
  }
package/dist/src/init.js CHANGED
@@ -78,6 +78,7 @@ Antes de implementar, revisar ou testar código:
78
78
  1. Chame a tool \`recommend_for_task\` com a descrição da tarefa.
79
79
  2. Leia as skills e agentes recomendados com \`get_skill\` / \`get_agent\` e siga as diretrizes.
80
80
  3. Use \`search_guidelines\` para dúvidas pontuais.
81
+ 4. Se a skill tiver arquivos complementares, leia-os com \`list_skill_files\` / \`get_skill_file\`.
81
82
  ${BLOCK_END}`;
82
83
  /**
83
84
  * Insere o bloco DS9 num AGENTS.md ou, se já existir, substitui apenas o trecho entre os
@@ -68,18 +68,26 @@ export function searchBodies(entries, query, limit = 8) {
68
68
  return [];
69
69
  const hits = [];
70
70
  for (const entry of entries) {
71
- entry.body.split(/\r?\n/).forEach((line, i) => {
72
- const score = count(line, tokens);
73
- if (score > 0 && line.trim()) {
74
- hits.push({
75
- kind: entry.kind,
76
- id: entry.id,
77
- line: i + 1,
78
- text: line.trim().slice(0, 300), // limita linhas gigantes
79
- score,
80
- });
81
- }
82
- });
71
+ // Corpo principal + arquivos complementares (exemplos, referências) da skill.
72
+ const sources = [
73
+ { file: undefined, content: entry.body },
74
+ ...entry.files.map((f) => ({ file: f.path, content: f.text })),
75
+ ];
76
+ for (const src of sources) {
77
+ src.content.split(/\r?\n/).forEach((line, i) => {
78
+ const score = count(line, tokens);
79
+ if (score > 0 && line.trim()) {
80
+ hits.push({
81
+ kind: entry.kind,
82
+ id: entry.id,
83
+ ...(src.file ? { file: src.file } : {}),
84
+ line: i + 1,
85
+ text: line.trim().slice(0, 300), // limita linhas gigantes
86
+ score,
87
+ });
88
+ }
89
+ });
90
+ }
83
91
  }
84
92
  return hits
85
93
  .sort((a, b) => b.score - a.score)