@ds9-solutions/mcp 0.1.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.
- package/content/agents/code-reviewer.agent.md +55 -0
- package/content/agents/db-specialist.agent.md +78 -0
- package/content/agents/nestjs-api-designer.agent.md +61 -0
- package/content/agents/security-reviewer.agent.md +60 -0
- package/content/agents/test-writer.agent.md +65 -0
- package/content/skills/docker/SKILL.md +104 -0
- package/content/skills/hexagonal/SKILL.md +156 -0
- package/content/skills/jest-testing/SKILL.md +88 -0
- package/content/skills/mysql/SKILL.md +94 -0
- package/content/skills/nestjs/SKILL.md +100 -0
- package/content/skills/typeorm/SKILL.md +119 -0
- package/content/skills/typescript/SKILL.md +75 -0
- package/dist/src/content.js +98 -0
- package/dist/src/index.js +59 -0
- package/dist/src/init.js +123 -0
- package/dist/src/search.js +88 -0
- package/dist/src/server.js +125 -0
- package/dist/test/mcp.test.js +112 -0
- package/package.json +37 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jest-testing
|
|
3
|
+
description: Diretrizes de testes com Jest para projetos NestJS/TypeScript da DS9 — testes unitários de use cases/services, testes de integração de controllers, mocks de ports, cobertura e convenções de nomenclatura. Use ao escrever/revisar testes, criar mocks/fakes, configurar `jest.config`, ou quando o usuário pedir "teste unitário", "teste de integração" ou "cobertura de testes".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Jest — Diretrizes de Teste DS9
|
|
7
|
+
|
|
8
|
+
Jest é o test runner padrão para projetos TypeScript/NestJS da DS9. Esta skill cobre convenções de teste; a organização em camadas segue a skill `hexagonal` e as convenções de framework seguem a skill `nestjs`.
|
|
9
|
+
|
|
10
|
+
## Estrutura e nomenclatura
|
|
11
|
+
|
|
12
|
+
- Arquivo de teste ao lado do arquivo testado: `order.use-case.ts` → `order.use-case.spec.ts` (unitário) ou `*.e2e-spec.ts` (ponta a ponta, pasta `test/`).
|
|
13
|
+
- `describe` nomeia a unidade testada (`describe('CreateOrderUseCase', ...)`); `it`/`test` descreve o comportamento esperado em linguagem natural (`it('deve lançar OrderNotFoundError quando o pedido não existe', ...)`), nunca o nome do método (`it('deve testar execute', ...)` é proibido).
|
|
14
|
+
- Um `describe` por método/comportamento público quando a classe tiver múltiplos cenários relevantes.
|
|
15
|
+
|
|
16
|
+
## Pirâmide de testes no projeto
|
|
17
|
+
|
|
18
|
+
| Camada | O que testar | Ferramenta | Sobe banco/HTTP real? |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| Domínio (entities, value objects) | Invariantes e regras de negócio puras | Jest puro | Não |
|
|
21
|
+
| Use cases / application | Orquestração, chamando ports mockadas | Jest + mocks das `ports` | Não |
|
|
22
|
+
| Adapters de saída (repositórios TypeORM) | Mapeamento correto entidade ↔ persistência | Jest + banco de teste (ou testcontainers, ver skill `docker`) | Sim (banco de teste isolado) |
|
|
23
|
+
| Controllers / adapters de entrada | Contrato HTTP: status, shape, validação | Jest + `supertest` (`*.e2e-spec.ts`) | Sim (app NestJS em memória, banco de teste) |
|
|
24
|
+
|
|
25
|
+
A maior parte dos testes deve estar nas duas primeiras camadas (rápidos, sem I/O); testes de integração/e2e cobrem os caminhos críticos, não todo caso de borda.
|
|
26
|
+
|
|
27
|
+
## Mocks de Ports (hexagonal)
|
|
28
|
+
|
|
29
|
+
- Use cases são testados mockando as `ports` (interfaces), nunca subindo banco/HTTP real. Prefira um *fake* em memória simples a um mock excessivamente configurado quando a port tem poucos métodos:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
class InMemoryOrderRepository implements OrderRepositoryPort {
|
|
33
|
+
private readonly rows = new Map<string, Order>();
|
|
34
|
+
async save(order: Order): Promise<void> { this.rows.set(order.id, order); }
|
|
35
|
+
async findById(id: string): Promise<Order | null> { return this.rows.get(id) ?? null; }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('CreateOrderUseCase', () => {
|
|
39
|
+
it('deve salvar o pedido com status PENDING', async () => {
|
|
40
|
+
const repo = new InMemoryOrderRepository();
|
|
41
|
+
const useCase = new CreateOrderUseCase(repo);
|
|
42
|
+
|
|
43
|
+
const order = await useCase.execute({ customerId: 'c1', items: [...] });
|
|
44
|
+
|
|
45
|
+
expect(order.status).toBe('PENDING');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- Para mocks com `jest.fn()`, tipar sempre contra a interface da port (`jest.Mocked<OrderRepositoryPort>`), nunca `any`.
|
|
51
|
+
- Não mocke o que você não é dono (bibliotecas externas/SDKs) diretamente nos testes de domínio — isole-as atrás de uma port e mocke a port.
|
|
52
|
+
|
|
53
|
+
## Testes de controller (NestJS)
|
|
54
|
+
|
|
55
|
+
- Monte o módulo real via `Test.createTestingModule({ imports: [OrderModule] })`, sobrescrevendo apenas as dependências externas (`overrideProvider(ORDER_REPOSITORY_PORT)`), para validar o wiring de DI de verdade.
|
|
56
|
+
- Use `supertest` contra a instância HTTP da aplicação Nest (`app.getHttpServer()`), verificando status code, shape do body e erros de validação (400) com payload inválido.
|
|
57
|
+
|
|
58
|
+
## Assertions
|
|
59
|
+
|
|
60
|
+
- Prefira matchers específicos (`toEqual`, `toMatchObject`, `toThrow(OrderNotFoundError)`) a `toBeTruthy()`/`toBeDefined()` genéricos quando o valor exato importa.
|
|
61
|
+
- Para erros de domínio, sempre assertar a **classe/tipo** do erro (`expect(fn).rejects.toThrow(OrderNotFoundError)`), não só a mensagem em texto.
|
|
62
|
+
- Nunca deixe um teste sem nenhuma asserção (`it` vazio/`TODO`) marcado como passando; use `it.todo('...')` ou `.skip` explicitamente se o teste ainda não foi implementado.
|
|
63
|
+
|
|
64
|
+
## Isolamento e determinismo
|
|
65
|
+
|
|
66
|
+
- Cada teste deve poder rodar isoladamente e em qualquer ordem — sem estado global compartilhado entre `it`s (use `beforeEach` para recriar fakes/mocks).
|
|
67
|
+
- Nunca dependa de `Date.now()`/hora real sem controlar o clock (`jest.useFakeTimers()`); nunca dependa de ordem de `Promise`/timing sem `await` explícito.
|
|
68
|
+
- Testes de integração usam um banco de dados de teste próprio (schema isolado por execução), nunca o banco de dev/homolog compartilhado. Se o projeto usar testcontainers para subir esse banco, siga a skill `docker` para a imagem/configuração do container.
|
|
69
|
+
|
|
70
|
+
## Cobertura
|
|
71
|
+
|
|
72
|
+
- Cobertura mínima definida pelo projeto é validada no CI (`jest --coverage`); regras de negócio no domínio/use-cases devem ter cobertura próxima de 100% dos branches (if/else, early-return de erro).
|
|
73
|
+
- Cobertura alta não substitui qualidade das asserções — não escreva teste só para "bater número" sem validar comportamento real.
|
|
74
|
+
|
|
75
|
+
## Checklist ao escrever/revisar testes
|
|
76
|
+
|
|
77
|
+
1. O teste de use case mocka a port (sem banco/HTTP real)?
|
|
78
|
+
2. Nomes de `describe`/`it` descrevem comportamento, não implementação?
|
|
79
|
+
3. Erros de domínio são testados por tipo/classe, não só mensagem?
|
|
80
|
+
4. O teste é determinístico (sem dependência de tempo real/ordem)?
|
|
81
|
+
5. Testes de controller cobrem pelo menos um caso de validação inválida (400) além do caminho feliz?
|
|
82
|
+
|
|
83
|
+
## Não fazer
|
|
84
|
+
|
|
85
|
+
- Não usar `any` em mocks de ports.
|
|
86
|
+
- Não deixar `it.skip`/`xit` sem um comentário explicando o motivo e um acompanhamento (issue/TODO).
|
|
87
|
+
- Não testar detalhes de implementação privada (métodos privados) — teste pelo comportamento público.
|
|
88
|
+
- Não compartilhar estado mutável entre testes sem reset em `beforeEach`/`afterEach`.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mysql
|
|
3
|
+
description: Diretrizes de modelagem, indexação, migrations, performance e segurança para bancos MySQL/MariaDB usados pelos projetos DS9. Use ao criar/alterar tabelas, escrever migrations, definir índices, revisar queries lentas, ou quando o usuário pedir "schema MySQL", "migration", "índice" ou "otimizar query".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# MySQL — Diretrizes DS9
|
|
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.
|
|
9
|
+
|
|
10
|
+
## Engine e charset
|
|
11
|
+
|
|
12
|
+
- Engine padrão: `InnoDB` (suporta transações e FKs). Nunca usar `MyISAM` para tabelas transacionais.
|
|
13
|
+
- Charset/collation padrão: `utf8mb4` / `utf8mb4_unicode_ci` (ou `utf8mb4_0900_ai_ci` em MySQL 8+). Nunca `utf8` puro (não suporta 4 bytes/emojis).
|
|
14
|
+
|
|
15
|
+
## Convenções de nomenclatura
|
|
16
|
+
|
|
17
|
+
- Tabelas: `snake_case`, plural (`orders`, `order_items`).
|
|
18
|
+
- Colunas: `snake_case` (`created_at`, `customer_id`).
|
|
19
|
+
- Chave primária: `id` (`BIGINT UNSIGNED AUTO_INCREMENT` ou `CHAR(36)`/`BINARY(16)` para UUID — definir por projeto e manter consistente).
|
|
20
|
+
- Chave estrangeira: `<tabela_singular>_id` (`customer_id` referenciando `customers.id`).
|
|
21
|
+
- Índices: `idx_<tabela>_<colunas>`; únicos: `uq_<tabela>_<colunas>`; FKs: `fk_<tabela>_<tabela_referenciada>`.
|
|
22
|
+
|
|
23
|
+
## Colunas obrigatórias
|
|
24
|
+
|
|
25
|
+
Toda tabela transacional deve ter:
|
|
26
|
+
|
|
27
|
+
```sql
|
|
28
|
+
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
29
|
+
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
30
|
+
deleted_at DATETIME(3) NULL -- se o domínio usa soft delete
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- Use `DATETIME` (não `TIMESTAMP`) para evitar o limite de ano 2038 e problemas de fuso; grave sempre em UTC na aplicação.
|
|
34
|
+
- Nunca armazene datas/horas como `VARCHAR`.
|
|
35
|
+
|
|
36
|
+
## Tipos de dados
|
|
37
|
+
|
|
38
|
+
- Dinheiro/valores monetários: `DECIMAL(19,4)` (nunca `FLOAT`/`DOUBLE`).
|
|
39
|
+
- Texto curto com tamanho conhecido: `VARCHAR(n)` com `n` justificado pelo domínio (evitar `VARCHAR(255)` por padrão sem pensar).
|
|
40
|
+
- Texto longo: `TEXT`/`LONGTEXT` apenas quando necessário (impacta performance de index/row size).
|
|
41
|
+
- Enums de domínio fixo e pequeno: `VARCHAR` + `CHECK` constraint (MySQL 8.0.16+) ou tabela de referência; evite `ENUM` nativo do MySQL (dificulta `ALTER` e não é portável).
|
|
42
|
+
- Booleanos: `TINYINT(1)` (convenção do MySQL; mapeie como `boolean` na aplicação).
|
|
43
|
+
- JSON: tipo nativo `JSON` apenas para dados verdadeiramente semiestruturados, nunca para modelar relações que deveriam ser colunas/tabelas.
|
|
44
|
+
|
|
45
|
+
## Índices
|
|
46
|
+
|
|
47
|
+
- Toda FK **precisa** de índice (MySQL não cria automaticamente para colunas de FK em alguns casos — sempre declare explicitamente).
|
|
48
|
+
- Índices compostos: ordem das colunas segue o padrão de consulta (coluna de igualdade antes de coluna de range/order).
|
|
49
|
+
- Evite índices redundantes (um índice composto `(a, b)` já cobre buscas por `a` sozinho).
|
|
50
|
+
- Toda query de produção recorrente deve ter `EXPLAIN` validado antes do merge — sem `type: ALL` (full table scan) em tabelas grandes.
|
|
51
|
+
- Use índice único (`UNIQUE KEY`) para garantir invariantes de negócio no banco (ex.: `email` único), nunca confie só em validação da aplicação.
|
|
52
|
+
|
|
53
|
+
## Migrations
|
|
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.
|
|
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
|
+
- 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
|
+
- 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.
|
|
59
|
+
- Nunca faça `DROP TABLE`/`DROP COLUMN` na mesma migration que ainda está em uso por código em produção (deploy em etapas: parar de usar → migration de remoção).
|
|
60
|
+
|
|
61
|
+
## Transações
|
|
62
|
+
|
|
63
|
+
- Operações que alteram múltiplas tabelas com invariantes relacionadas (ex.: criar pedido + baixar estoque) **devem** rodar em uma transação explícita.
|
|
64
|
+
- Nível de isolamento padrão do InnoDB é `REPEATABLE READ`; use `SELECT ... FOR UPDATE` quando precisar de lock pessimista para evitar race conditions em concorrência (ex.: controle de estoque, saldo).
|
|
65
|
+
- Mantenha transações curtas: nunca faça chamadas de rede (HTTP, fila) dentro de uma transação de banco aberta.
|
|
66
|
+
|
|
67
|
+
## Queries
|
|
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.
|
|
70
|
+
- Evite `SELECT *`; liste as colunas necessárias, especialmente em tabelas largas.
|
|
71
|
+
- Pagine toda listagem (`LIMIT`/`OFFSET` ou keyset pagination para datasets grandes); nunca retorne uma tabela inteira sem limite.
|
|
72
|
+
- Prefira keyset pagination (`WHERE id > :lastId ORDER BY id LIMIT :n`) a `OFFSET` alto em tabelas grandes (OFFSET grande é O(n)).
|
|
73
|
+
|
|
74
|
+
## Segurança e acesso
|
|
75
|
+
|
|
76
|
+
- Usuário da aplicação nunca deve ter privilégio `SUPER`, `GRANT OPTION` ou DDL (`CREATE`/`DROP`/`ALTER`) em produção — apenas `SELECT`, `INSERT`, `UPDATE`, `DELETE` nas tabelas do próprio schema. Migrations rodam com usuário/privilégio separado (pipeline de deploy).
|
|
77
|
+
- Nunca commitar credenciais de conexão; usar variáveis de ambiente/secret manager (ver README do MCP).
|
|
78
|
+
- Dados sensíveis (PII, senha, token) nunca em texto plano: hash (ex.: `bcrypt`/`argon2`) para senha, criptografia em nível de aplicação para PII quando exigido por compliance.
|
|
79
|
+
|
|
80
|
+
## Checklist ao revisar schema/migration/query
|
|
81
|
+
|
|
82
|
+
1. Engine `InnoDB`, charset `utf8mb4`?
|
|
83
|
+
2. Nomenclatura `snake_case` consistente e FKs com índice?
|
|
84
|
+
3. Tipos corretos (`DECIMAL` para dinheiro, sem `ENUM` nativo, sem `VARCHAR` para datas)?
|
|
85
|
+
4. Migration tem `up`/`down`, é aditiva/segura para produção?
|
|
86
|
+
5. Query usa parâmetros (sem concatenação), tem `LIMIT`, e passou por `EXPLAIN`?
|
|
87
|
+
6. Alterações concorrentes têm transação e/ou lock explícito quando necessário?
|
|
88
|
+
|
|
89
|
+
## Não fazer
|
|
90
|
+
|
|
91
|
+
- Não usar `SELECT *` em código de produção.
|
|
92
|
+
- Não usar `FLOAT`/`DOUBLE` para valores monetários.
|
|
93
|
+
- Não rodar DDL manual direto em produção fora de migration versionada.
|
|
94
|
+
- Não deixar queries sem paginação em endpoints de listagem.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nestjs
|
|
3
|
+
description: Diretrizes e convenções NestJS da DS9 — módulos, controllers, DTOs/validação, injeção de dependência, tratamento de erros, configuração e segurança. Use ao criar/revisar controllers, services, DTOs, guards, interceptors, pipes, ou quando o usuário pedir "endpoint", "controller", "module NestJS" ou "boas práticas NestJS".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# NestJS — Diretrizes DS9
|
|
7
|
+
|
|
8
|
+
Framework padrão de backend da DS9. Esta skill cobre convenções específicas do NestJS; a organização em camadas (domain/application/infrastructure) segue a skill `hexagonal`, e a persistência segue a skill `typeorm`.
|
|
9
|
+
|
|
10
|
+
## Estrutura de módulo
|
|
11
|
+
|
|
12
|
+
- Um módulo por bounded context/feature (`OrderModule`, `CustomerModule`), nunca um módulo "god" por tipo técnico (ex.: não criar `ControllersModule`).
|
|
13
|
+
- Cada módulo exporta só o que outros módulos precisam (`exports: [...]`); mantenha providers internos privados por padrão.
|
|
14
|
+
- `AppModule` apenas importa feature modules + módulos globais de infraestrutura (`ConfigModule`, `TypeOrmModule.forRoot`, etc.). Sem lógica de negócio.
|
|
15
|
+
|
|
16
|
+
## Controllers (adapters de entrada)
|
|
17
|
+
|
|
18
|
+
- Controller é "burro": recebe request, valida via DTO, chama um use case/service, mapeia o retorno. Nenhuma regra de negócio aqui (ver skill `hexagonal`).
|
|
19
|
+
- Um controller por recurso REST, rotas no plural (`/orders`, `/orders/:id`).
|
|
20
|
+
- Sempre tipar o retorno do handler e usar os decorators HTTP corretos (`@Get`, `@Post`, `@Patch` para update parcial, `@Put` para substituição completa, `@Delete`).
|
|
21
|
+
- Use `@HttpCode()` explícito quando o código padrão do método não for o desejado (ex.: `201` em `POST`, `204` em `DELETE`).
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
@Controller('orders')
|
|
25
|
+
export class OrderController {
|
|
26
|
+
constructor(private readonly createOrder: CreateOrderUseCase) {}
|
|
27
|
+
|
|
28
|
+
@Post()
|
|
29
|
+
@HttpCode(HttpStatus.CREATED)
|
|
30
|
+
async create(@Body() dto: CreateOrderDto): Promise<OrderResponseDto> {
|
|
31
|
+
const order = await this.createOrder.execute(dto.toCommand());
|
|
32
|
+
return OrderResponseDto.fromDomain(order);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## DTOs e validação
|
|
38
|
+
|
|
39
|
+
- Todo input externo (`@Body`, `@Query`, `@Param`) é tipado com uma classe DTO usando `class-validator` + `class-transformer`. Nunca aceitar `any`/objeto não tipado.
|
|
40
|
+
- Ative globalmente `ValidationPipe` com `whitelist: true` e `forbidNonWhitelisted: true` para rejeitar campos não declarados no DTO:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }));
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- DTOs de request e response são classes separadas (`CreateOrderDto` ≠ `OrderResponseDto`); nunca devolver a entidade de domínio/persistência diretamente na resposta HTTP.
|
|
47
|
+
- Documente cada DTO com decorators do Swagger (`@ApiProperty`) quando o projeto expõe OpenAPI.
|
|
48
|
+
|
|
49
|
+
## Injeção de dependência
|
|
50
|
+
|
|
51
|
+
- Prefira injeção por construtor (`constructor(private readonly x: X) {}`), nunca `new` direto de um provider gerenciado pelo Nest.
|
|
52
|
+
- Dependências para fora do domínio (ver skill `hexagonal`) são injetadas por token/interface (`@Inject(PORT_TOKEN)`), não pela implementação concreta.
|
|
53
|
+
- Escopo padrão dos providers é singleton (`Scope.DEFAULT`); use `Scope.REQUEST` apenas quando houver necessidade real de estado por request (tem custo de performance).
|
|
54
|
+
|
|
55
|
+
## Tratamento de erros
|
|
56
|
+
|
|
57
|
+
- Erros de domínio são classes próprias (`OrderNotFoundError extends Error`), lançadas pelo use case/domínio.
|
|
58
|
+
- Um `ExceptionFilter` global traduz erros de domínio para `HttpException`/status code correto; controllers não fazem `try/catch` manual para mapear erro de negócio em HTTP.
|
|
59
|
+
- Nunca deixe vazar stack trace ou detalhes internos (query SQL, caminho de arquivo) na resposta HTTP em produção.
|
|
60
|
+
- Erros de validação (DTO) já retornam 400 automaticamente via `ValidationPipe`; não duplique essa validação no controller.
|
|
61
|
+
|
|
62
|
+
## Configuração
|
|
63
|
+
|
|
64
|
+
- Toda configuração de ambiente passa por `@nestjs/config` com schema de validação (ex.: `Joi`/`zod`) no bootstrap — a aplicação **falha ao subir** se faltar uma env var obrigatória, nunca falha silenciosamente em runtime.
|
|
65
|
+
- Nunca leia `process.env` diretamente em services/controllers; sempre via `ConfigService` tipado.
|
|
66
|
+
- Segredos (DB password, JWT secret, API keys) vêm de variável de ambiente/secret manager, nunca hardcoded ou commitados.
|
|
67
|
+
|
|
68
|
+
## Guards, Interceptors e Pipes
|
|
69
|
+
|
|
70
|
+
- Autenticação/autorização: `Guard` (`CanActivate`), nunca checagem manual de token dentro do controller.
|
|
71
|
+
- Transformação de resposta cross-cutting (ex.: envelope padrão, log de tempo de resposta): `Interceptor`.
|
|
72
|
+
- Validação/transformação de parâmetros individuais: `Pipe` (além do `ValidationPipe` global).
|
|
73
|
+
- Guards/interceptors de autorização específicos de negócio (ex.: "só o dono do pedido pode ver") ficam dentro do módulo de feature correspondente (ex.: `OrderModule`), na camada `infrastructure/adapters/in/` (ver skill `hexagonal`) — nunca em `domain/` (um `Guard`/`Interceptor` sempre importa `@nestjs/common`, o que viola a regra de pureza do domínio) nem em um guard genérico compartilhado por todo o app.
|
|
74
|
+
|
|
75
|
+
## Logging e observabilidade
|
|
76
|
+
|
|
77
|
+
- Use o `Logger` do Nest (ou logger estruturado configurado no projeto), nunca `console.log` em código de produção.
|
|
78
|
+
- Logs de erro incluem contexto (ex.: `orderId`, `userId`) mas nunca dados sensíveis (senha, token, PII completo).
|
|
79
|
+
- Toda rota crítica deve ter log de entrada/saída ou métrica associada (latência, taxa de erro) conforme padrão de observabilidade do projeto.
|
|
80
|
+
|
|
81
|
+
## Testes
|
|
82
|
+
|
|
83
|
+
- Services/use cases: teste unitário com `Test.createTestingModule` mockando as dependências (ports). Ver skill `jest-testing`.
|
|
84
|
+
- Controllers: teste de integração (`supertest`) cobrindo status code, shape da resposta e validação de DTO.
|
|
85
|
+
- Nunca suba banco real em teste unitário; use fakes/mocks das ports (ver skill `hexagonal`).
|
|
86
|
+
|
|
87
|
+
## Checklist ao criar/revisar um endpoint
|
|
88
|
+
|
|
89
|
+
1. Controller só traduz protocolo (sem regra de negócio)?
|
|
90
|
+
2. Request e response têm DTOs próprios e validados?
|
|
91
|
+
3. Erros de domínio são mapeados via `ExceptionFilter`, não `try/catch` ad hoc?
|
|
92
|
+
4. Configuração/segredos vêm de `ConfigService`, não de `process.env` direto?
|
|
93
|
+
5. Existe teste cobrindo o caminho feliz e pelo menos um erro de validação/negócio?
|
|
94
|
+
|
|
95
|
+
## Não fazer
|
|
96
|
+
|
|
97
|
+
- Não injetar `Repository`/clients externos diretamente em controllers.
|
|
98
|
+
- Não retornar entidades de domínio/persistência cruas na resposta HTTP.
|
|
99
|
+
- Não usar `any` em parâmetros de rota/body.
|
|
100
|
+
- Não usar `console.log` como mecanismo de log em produção.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typeorm
|
|
3
|
+
description: Diretrizes de uso do TypeORM como adapter de persistência em projetos NestJS/hexagonal da DS9 — entities, repositórios, migrations, relations, transações e mappers. Use ao criar/alterar entities, repositórios, migrations do TypeORM, ou quando o usuário pedir "entity", "migration TypeORM", "repository" ou "relação" (OneToMany, ManyToMany etc.).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TypeORM — Diretrizes DS9
|
|
7
|
+
|
|
8
|
+
TypeORM é o ORM padrão para a camada de persistência (`infrastructure/adapters/out/`) nos projetos NestJS/hexagonal da DS9, sobre banco MySQL (ver skill `mysql`). O domínio nunca conhece TypeORM diretamente (ver skill `hexagonal`).
|
|
9
|
+
|
|
10
|
+
## Localização no projeto
|
|
11
|
+
|
|
12
|
+
- Entities (`@Entity`), repositórios concretos e migrations ficam em `infrastructure/`, nunca em `domain/` ou `application/`.
|
|
13
|
+
- Entidade de persistência (`OrderEntity`) é uma classe **separada** da entidade de domínio (`Order`). A tradução entre as duas é feita por um `Mapper` dedicado (`OrderMapper.toDomain` / `toPersistence`), conforme a skill `hexagonal`.
|
|
14
|
+
|
|
15
|
+
## Configuração
|
|
16
|
+
|
|
17
|
+
- Sempre usar `migrationsRun: false` em produção — migrations rodam explicitamente no pipeline de deploy, nunca automaticamente no boot da aplicação.
|
|
18
|
+
- `synchronize: false` **sempre**, em todos os ambientes (inclusive dev/teste). Schema é controlado só por migrations, nunca por auto-sync do TypeORM (risco de perda de dados em produção).
|
|
19
|
+
- Configuração de conexão vem de `ConfigService` (ver skill `nestjs`), nunca hardcoded.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
TypeOrmModule.forRootAsync({
|
|
23
|
+
useFactory: (config: ConfigService) => ({
|
|
24
|
+
type: 'mysql',
|
|
25
|
+
host: config.getOrThrow('DB_HOST'),
|
|
26
|
+
port: config.getOrThrow('DB_PORT'),
|
|
27
|
+
synchronize: false,
|
|
28
|
+
migrationsRun: false,
|
|
29
|
+
entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
|
30
|
+
migrations: [__dirname + '/migrations/*{.ts,.js}'],
|
|
31
|
+
}),
|
|
32
|
+
inject: [ConfigService],
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Entities
|
|
37
|
+
|
|
38
|
+
- Nome da classe: `<Nome>Entity` (ex.: `OrderEntity`), tabela em `snake_case` plural via `@Entity('orders')` (ver convenções da skill `mysql`).
|
|
39
|
+
- Toda coluna usa `@Column` com tipo explícito (`type: 'decimal', precision: 19, scale: 4` para dinheiro — nunca deixar o TypeORM inferir tipo monetário).
|
|
40
|
+
- Datas: `@CreateDateColumn()` / `@UpdateDateColumn()` para `created_at`/`updated_at`; `@DeleteDateColumn()` se o projeto usa soft delete (`deleted_at`).
|
|
41
|
+
- Chave primária: `@PrimaryGeneratedColumn()` (auto-increment) ou `@PrimaryGeneratedColumn('uuid')`/`@PrimaryColumn()` conforme a estratégia de ID definida no projeto — manter consistência em todo o schema.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
@Entity('orders')
|
|
45
|
+
export class OrderEntity {
|
|
46
|
+
@PrimaryGeneratedColumn('uuid')
|
|
47
|
+
id: string;
|
|
48
|
+
|
|
49
|
+
@Column({ type: 'decimal', precision: 19, scale: 4 })
|
|
50
|
+
totalAmount: string; // decimal do MySQL vem como string — converter explicitamente no mapper
|
|
51
|
+
|
|
52
|
+
@ManyToOne(() => CustomerEntity, { nullable: false })
|
|
53
|
+
@JoinColumn({ name: 'customer_id' })
|
|
54
|
+
customer: CustomerEntity;
|
|
55
|
+
|
|
56
|
+
@CreateDateColumn({ name: 'created_at' })
|
|
57
|
+
createdAt: Date;
|
|
58
|
+
|
|
59
|
+
@UpdateDateColumn({ name: 'updated_at' })
|
|
60
|
+
updatedAt: Date;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Relations
|
|
65
|
+
|
|
66
|
+
- Declare `@JoinColumn({ name: '...' })` explicitamente em toda relação `@ManyToOne`/`@OneToOne` — nunca deixe o TypeORM inferir o nome da coluna FK.
|
|
67
|
+
- Evite `eager: true` em relations de entidades grandes/listas (causa N+1 e joins não intencionais); prefira carregar explicitamente via `relations: [...]` ou `QueryBuilder` quando necessário.
|
|
68
|
+
- `@ManyToMany` só quando a tabela de junção não tem atributos próprios; se a relação carrega dados extras (ex.: `quantity` em `order_items`), modele como entidade própria com duas `@ManyToOne`.
|
|
69
|
+
- Nunca use `cascade: true` sem necessidade explícita e documentada — cascatas implícitas de delete/update são fonte comum de perda de dados acidental.
|
|
70
|
+
|
|
71
|
+
## Repositórios (adapters de saída)
|
|
72
|
+
|
|
73
|
+
- Repositório concreto implementa a `port` definida no domínio (`OrderRepositoryPort`), conforme a skill `hexagonal`; nunca exponha `Repository<OrderEntity>` do TypeORM fora do adapter.
|
|
74
|
+
- Prefira `Repository`/`QueryBuilder` injetado via `@InjectRepository(OrderEntity)` a `EntityManager` cru, exceto em casos que exigem múltiplas entidades na mesma transação.
|
|
75
|
+
- Use `QueryBuilder` (não SQL raw) para queries complexas — mantém parametrização automática (proteção contra SQL Injection) e portabilidade.
|
|
76
|
+
- Pagine toda listagem (`skip`/`take` ou keyset) e nunca retorne `find()` sem filtro em tabelas grandes.
|
|
77
|
+
|
|
78
|
+
## Migrations
|
|
79
|
+
|
|
80
|
+
- Gere migrations via CLI (`typeorm migration:generate`) a partir da comparação de entities, e **revise manualmente** o SQL gerado antes de commitar (o gerador pode incluir drops/alters não intencionais).
|
|
81
|
+
- Toda migration tem `up()` e `down()` funcionais — ver regras detalhadas de migration na skill `mysql`.
|
|
82
|
+
- Nomeie migrations com timestamp + descrição clara (`1700000000000-CreateOrdersTable.ts`, `1700000100000-AddStatusToOrders.ts`).
|
|
83
|
+
- Nunca edite uma migration já aplicada em qualquer ambiente compartilhado (dev remoto, homolog, produção); crie uma nova migration corretiva.
|
|
84
|
+
|
|
85
|
+
## Transações
|
|
86
|
+
|
|
87
|
+
- Operações que tocam uma única port/agregado usam a transação implícita de cada chamada (`save`, `update`); para operações **cross-aggregate** (múltiplas ports), implemente a `UnitOfWorkPort` definida na skill `hexagonal` usando `DataSource.transaction()`:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// infrastructure/adapters/out/typeorm-unit-of-work.ts
|
|
91
|
+
@Injectable()
|
|
92
|
+
export class TypeOrmUnitOfWork implements UnitOfWorkPort {
|
|
93
|
+
constructor(private readonly dataSource: DataSource) {}
|
|
94
|
+
|
|
95
|
+
async run<T>(work: () => Promise<T>): Promise<T> {
|
|
96
|
+
return this.dataSource.transaction(async () => work());
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- Dentro de `work()`, os repositórios concretos (`TypeOrmOrderRepository`, `TypeOrmStockRepository`) devem operar sobre o `QueryRunner`/`EntityManager` **da transação ativa**, não sobre o `Repository` injetado globalmente — normalmente resolvido com `AsyncLocalStorage` ou passando o `EntityManager` corrente via contexto de request, conforme o padrão de UoW adotado pelo projeto.
|
|
102
|
+
- Nunca abra uma transação e faça chamadas de rede (HTTP/fila) dentro dela — mantenha transações curtas (ver skill `mysql`).
|
|
103
|
+
- Não implemente a coordenação cross-aggregate manipulando entidades de outro agregado dentro do repositório de um agregado diferente (ex.: `TypeOrmOrderRepository` não deve escrever em `StockEntity`); isso é responsabilidade exclusiva do `UnitOfWorkPort` + use case (ver skill `hexagonal`).
|
|
104
|
+
|
|
105
|
+
## Checklist ao criar/revisar entity/migration/repositório
|
|
106
|
+
|
|
107
|
+
1. Entity e migration estão em `infrastructure/`, nunca vazando para `domain/application`?
|
|
108
|
+
2. Tipos de coluna corretos (`decimal` para dinheiro, FKs com `@JoinColumn` explícito)?
|
|
109
|
+
3. `synchronize: false` e migration revisada manualmente?
|
|
110
|
+
4. Repositório implementa a port do domínio e usa `QueryBuilder`/`Repository` parametrizado (sem SQL concatenado)?
|
|
111
|
+
5. Operações multi-entidade usam transação explícita?
|
|
112
|
+
6. Existe mapper `toDomain`/`toPersistence` separando entidade de persistência da entidade de domínio?
|
|
113
|
+
|
|
114
|
+
## Não fazer
|
|
115
|
+
|
|
116
|
+
- Não usar `synchronize: true` em nenhum ambiente.
|
|
117
|
+
- Não expor `Entity`/`Repository` do TypeORM fora da camada de infraestrutura.
|
|
118
|
+
- Não usar `cascade: true` ou `eager: true` sem justificativa documentada.
|
|
119
|
+
- Não escrever SQL raw concatenando valores de usuário.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typescript
|
|
3
|
+
description: Convenções de TypeScript da DS9 — tipagem estrita, modelagem de domínio, tratamento de erros, imports e estilo de código. Use ao escrever ou revisar qualquer código TypeScript (independente de framework), definir tipos/interfaces, ou quando o usuário pedir "tipagem", "strict mode" ou "boas práticas TypeScript".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TypeScript — Diretrizes DS9
|
|
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`).
|
|
9
|
+
|
|
10
|
+
## Configuração do compilador
|
|
11
|
+
|
|
12
|
+
- `strict: true` obrigatório no `tsconfig.json` (ativa `strictNullChecks`, `noImplicitAny`, etc.). Não desative flags de strict mode isoladamente sem justificativa documentada.
|
|
13
|
+
- `noUnusedLocals` e `noUnusedParameters` ativados — código morto não compila.
|
|
14
|
+
- `noImplicitReturns` e `noFallthroughCasesInSwitch` ativados para evitar bugs de controle de fluxo.
|
|
15
|
+
|
|
16
|
+
## Tipagem
|
|
17
|
+
|
|
18
|
+
- Proibido `any` explícito em código novo. Quando o tipo é genuinamente desconhecido (ex.: payload externo não tipado), use `unknown` e faça *narrowing* explícito antes de usar.
|
|
19
|
+
- Prefira `interface` para formas de objeto/contratos públicos (ports, DTOs) e `type` para unions, intersections e tipos utilitários.
|
|
20
|
+
- Nunca use type assertion (`as Tipo`) para "calar" o compilador sem validação real; prefira type guards (`function isOrder(x: unknown): x is Order`).
|
|
21
|
+
- Habilite e respeite `readonly` em propriedades que não devem mudar após criação (especialmente em entidades de domínio e DTOs).
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// Evitar
|
|
25
|
+
function handle(payload: any) { return payload.items.map((i: any) => i.id); }
|
|
26
|
+
|
|
27
|
+
// Preferir
|
|
28
|
+
interface OrderPayload { items: Array<{ id: string }>; }
|
|
29
|
+
function handle(payload: OrderPayload) { return payload.items.map((i) => i.id); }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Modelagem de domínio
|
|
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`).
|
|
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
|
+
- 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
|
+
|
|
38
|
+
## Tratamento de erros
|
|
39
|
+
|
|
40
|
+
- Erros de domínio são classes que estendem `Error`, com nome específico (`class OrderNotFoundError extends Error`), nunca `throw` de string ou objeto genérico (`throw 'erro'` é proibido).
|
|
41
|
+
- Nunca capture um erro (`catch`) só para logar e relançar sem adicionar contexto; se não for tratar, deixe propagar.
|
|
42
|
+
- Funções assíncronas que podem falhar previsivelmente (ex.: "não encontrado") devem deixar isso explícito no tipo de retorno (`Promise<Order | null>`) quando fizer sentido, em vez de forçar o chamador a sempre usar `try/catch`.
|
|
43
|
+
|
|
44
|
+
## Imports e organização de módulos
|
|
45
|
+
|
|
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`).
|
|
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
|
+
|
|
50
|
+
## Nulidade e valores opcionais
|
|
51
|
+
|
|
52
|
+
- Prefira `T | null` para "ausência de valor conhecida" e `T | undefined`/parâmetro opcional (`param?: T`) para "valor não fornecido". Seja consistente dentro do mesmo módulo.
|
|
53
|
+
- Nunca use `!` (non-null assertion) para contornar `strictNullChecks` sem uma garantia real logo acima no código (ex.: dentro de um `if` que já checou).
|
|
54
|
+
|
|
55
|
+
## Nomenclatura
|
|
56
|
+
|
|
57
|
+
- Classes/interfaces/tipos: `PascalCase`. Variáveis/funções: `camelCase`. Constantes verdadeiramente globais/imutáveis: `UPPER_SNAKE_CASE`.
|
|
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
|
+
- 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`).
|
|
61
|
+
|
|
62
|
+
## Checklist ao escrever/revisar código TypeScript
|
|
63
|
+
|
|
64
|
+
1. Existe algum `any` que poderia ser `unknown` + narrowing, ou um tipo concreto?
|
|
65
|
+
2. Erros são classes nomeadas, não strings/objetos genéricos?
|
|
66
|
+
3. Nenhum `as Tipo`/`!` sem uma garantia lógica imediatamente visível?
|
|
67
|
+
4. Nomes de variáveis/funções comunicam intenção sem precisar ler a implementação?
|
|
68
|
+
5. O `tsconfig.json` do projeto mantém `strict: true` e nenhuma flag de strict foi desabilitada pontualmente?
|
|
69
|
+
|
|
70
|
+
## Não fazer
|
|
71
|
+
|
|
72
|
+
- Não usar `any` em código novo.
|
|
73
|
+
- Não usar `// @ts-ignore` para silenciar erro de tipo sem comentário explicando o motivo e um plano de correção.
|
|
74
|
+
- Não lançar (`throw`) valores que não sejam instância de `Error`.
|
|
75
|
+
- Não criar imports circulares entre módulos/camadas.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader de conteúdo: lê as skills e os agentes (arquivos Markdown) do disco e os
|
|
3
|
+
* transforma em uma lista de `Entry` em memória.
|
|
4
|
+
*
|
|
5
|
+
* Por que existe: o servidor MCP não tem conhecimento embutido sobre nenhuma skill/agente.
|
|
6
|
+
* Tudo vem dos arquivos, então adicionar/editar/remover um `.md` não exige mexer em código.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { parse as parseYaml } from "yaml";
|
|
12
|
+
// Frontmatter = bloco YAML entre dois `---` no topo do arquivo. Aceita \r\n porque os arquivos
|
|
13
|
+
// podem ter sido salvos no Windows.
|
|
14
|
+
const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
15
|
+
/**
|
|
16
|
+
* Separa o frontmatter (metadados) do corpo Markdown.
|
|
17
|
+
* Sem frontmatter, devolve o texto inteiro como corpo, para não perder conteúdo.
|
|
18
|
+
*/
|
|
19
|
+
export function parseFrontmatter(raw) {
|
|
20
|
+
const match = FRONTMATTER.exec(raw);
|
|
21
|
+
if (!match)
|
|
22
|
+
return { data: {}, body: raw };
|
|
23
|
+
return { data: (parseYaml(match[1]) ?? {}), body: match[2].trim() };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Descobre onde estão `skills/` e `agents/`. Testa várias origens, em ordem, porque o mesmo código
|
|
27
|
+
* roda em contextos diferentes:
|
|
28
|
+
* 1. `DS9_MCP_CONTENT` — override manual (útil para depurar com outro conteúdo);
|
|
29
|
+
* 2. `content/` na raiz do pacote — o caso de produção (copiado pelo `sync-content` no npm pack);
|
|
30
|
+
* 3. `plugins/ds9-mcp/` do monorepo — o caso de desenvolvimento, sem precisar sincronizar.
|
|
31
|
+
* A mistura de `..` existe porque o arquivo roda de `dist/src/` (compilado) ou de `src/`.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveContentRoot() {
|
|
34
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
const candidates = [
|
|
36
|
+
process.env.DS9_MCP_CONTENT,
|
|
37
|
+
join(here, "..", "content"),
|
|
38
|
+
join(here, "..", "..", "content"),
|
|
39
|
+
join(here, "..", "..", "..", "plugins", "ds9-mcp"),
|
|
40
|
+
join(here, "..", "..", "..", "..", "plugins", "ds9-mcp"),
|
|
41
|
+
];
|
|
42
|
+
for (const c of candidates) {
|
|
43
|
+
if (c && existsSync(join(c, "skills")))
|
|
44
|
+
return c;
|
|
45
|
+
}
|
|
46
|
+
throw new Error("Conteúdo (skills/agents) não encontrado. Rode `npm run sync-content`.");
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Carrega todas as skills e agentes.
|
|
50
|
+
* Skill = `skills/<pasta>/SKILL.md` (id = nome da pasta).
|
|
51
|
+
* Agente = `agents/<nome>.agent.md` (id = nome do arquivo; estrutura plana, sem subpastas).
|
|
52
|
+
*/
|
|
53
|
+
export function loadEntries(root = resolveContentRoot()) {
|
|
54
|
+
const entries = [];
|
|
55
|
+
const warnings = [];
|
|
56
|
+
const skillsDir = join(root, "skills");
|
|
57
|
+
for (const dirent of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
58
|
+
const file = join(skillsDir, dirent.name, "SKILL.md");
|
|
59
|
+
// Ignora arquivos soltos e pastas sem SKILL.md em vez de quebrar o servidor inteiro.
|
|
60
|
+
if (!dirent.isDirectory() || !existsSync(file))
|
|
61
|
+
continue;
|
|
62
|
+
const { data, body } = parseFrontmatter(readFileSync(file, "utf8"));
|
|
63
|
+
// Regra do AGENTS.md: `name` da skill deve ser idêntico ao nome da pasta. Só avisamos (não
|
|
64
|
+
// falhamos) para que uma skill mal formatada não derrube o acesso às demais.
|
|
65
|
+
if (data.name !== dirent.name) {
|
|
66
|
+
warnings.push(`skill "${dirent.name}": frontmatter name ("${String(data.name)}") difere da pasta`);
|
|
67
|
+
}
|
|
68
|
+
entries.push({
|
|
69
|
+
kind: "skill",
|
|
70
|
+
id: dirent.name,
|
|
71
|
+
name: String(data.name ?? dirent.name),
|
|
72
|
+
description: String(data.description ?? ""),
|
|
73
|
+
body,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const agentsDir = join(root, "agents");
|
|
77
|
+
if (existsSync(agentsDir)) {
|
|
78
|
+
for (const file of readdirSync(agentsDir)) {
|
|
79
|
+
if (!file.endsWith(".agent.md"))
|
|
80
|
+
continue;
|
|
81
|
+
const id = file.slice(0, -".agent.md".length);
|
|
82
|
+
const { data, body } = parseFrontmatter(readFileSync(join(agentsDir, file), "utf8"));
|
|
83
|
+
entries.push({
|
|
84
|
+
kind: "agent",
|
|
85
|
+
id,
|
|
86
|
+
// O `name` do agente é um rótulo de exibição ("Code Reviewer"); o id estável é o arquivo.
|
|
87
|
+
name: String(data.name ?? id),
|
|
88
|
+
description: String(data.description ?? ""),
|
|
89
|
+
body,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// stderr, nunca stdout: no transporte stdio o stdout é o canal do protocolo MCP, e qualquer
|
|
94
|
+
// texto extra nele corromperia a comunicação com a IDE.
|
|
95
|
+
for (const w of warnings)
|
|
96
|
+
console.error(`[ds9-mcp] aviso: ${w}`);
|
|
97
|
+
return entries;
|
|
98
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Ponto de entrada do pacote (o `bin` do npm aponta para este arquivo).
|
|
4
|
+
* Um único executável, dois modos:
|
|
5
|
+
* - `ds9-mcp` -> sobe o servidor MCP via stdio (é o que a IDE executa);
|
|
6
|
+
* - `ds9-mcp init` -> configura a IDE do usuário e sai.
|
|
7
|
+
* Manter os dois no mesmo comando permite que o `npx -y @ds9-solutions/mcp` sirva a ambos.
|
|
8
|
+
*/
|
|
9
|
+
import { createInterface } from "node:readline/promises";
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import { loadEntries } from "./content.js";
|
|
13
|
+
import { createServer } from "./server.js";
|
|
14
|
+
import { TARGETS, runInit } from "./init.js";
|
|
15
|
+
// A versão vem do package.json para existir uma única fonte (o que foi publicado no npm).
|
|
16
|
+
const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
17
|
+
/** Lê `--nome valor` dos argumentos. Parser mínimo de propósito: evita uma dependência de CLI. */
|
|
18
|
+
function flag(args, name) {
|
|
19
|
+
const i = args.indexOf(`--${name}`);
|
|
20
|
+
return i !== -1 ? args[i + 1] : undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Modo `init`: escolhe a IDE (por `--ide` ou perguntando) e grava a configuração. */
|
|
23
|
+
async function init(args) {
|
|
24
|
+
let ide = flag(args, "ide");
|
|
25
|
+
if (!ide) {
|
|
26
|
+
// Sem `--ide`, pergunta de forma interativa para o caso de uso "rodei e pronto".
|
|
27
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
28
|
+
console.log("Em qual IDE/agente configurar o MCP ds9?");
|
|
29
|
+
TARGETS.forEach((t, i) => console.log(` ${i + 1}) ${t.label}`));
|
|
30
|
+
const answer = (await rl.question("Número: ")).trim();
|
|
31
|
+
rl.close();
|
|
32
|
+
ide = TARGETS[Number(answer) - 1]?.id;
|
|
33
|
+
}
|
|
34
|
+
const target = TARGETS.find((t) => t.id === ide);
|
|
35
|
+
if (!target) {
|
|
36
|
+
console.error(`IDE inválida. Opções: ${TARGETS.map((t) => t.id).join(", ")}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
const result = runInit(target, flag(args, "dir") ?? process.cwd(), {
|
|
40
|
+
agentsMd: !args.includes("--no-agents-md"),
|
|
41
|
+
});
|
|
42
|
+
console.log(`✔ MCP ds9 configurado em ${result.configFile}`);
|
|
43
|
+
if (result.agentsFile)
|
|
44
|
+
console.log(`✔ Instruções adicionadas em ${result.agentsFile}`);
|
|
45
|
+
console.log("Reinicie a IDE/agente para carregar o servidor.");
|
|
46
|
+
}
|
|
47
|
+
/** Modo servidor: carrega o conteúdo e fica atendendo a IDE pela entrada/saída padrão. */
|
|
48
|
+
async function serve() {
|
|
49
|
+
const server = createServer(loadEntries(), pkg.version);
|
|
50
|
+
await server.connect(new StdioServerTransport());
|
|
51
|
+
// stderr: o stdout pertence ao protocolo MCP e não pode receber logs.
|
|
52
|
+
console.error(`[ds9-mcp] v${pkg.version} pronto (stdio)`);
|
|
53
|
+
}
|
|
54
|
+
// Qualquer erro vira mensagem curta + código de saída 1, em vez de um stack trace assustador.
|
|
55
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
56
|
+
(command === "init" ? init(rest) : serve()).catch((err) => {
|
|
57
|
+
console.error(err instanceof Error ? err.message : err);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|