@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
@@ -1,100 +0,0 @@
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.
@@ -1,119 +0,0 @@
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.
@@ -1,112 +0,0 @@
1
- /**
2
- * Testes do pacote, com o runner nativo do Node (`node:test`).
3
- *
4
- * Por que `node:test`: evita configurar Jest + ts-jest com ESM e não adiciona dependência.
5
- * Os testes usam o conteúdo REAL do plugin, então também falham se alguém quebrar o frontmatter
6
- * de uma skill ou agente (ex.: remover a `description`).
7
- */
8
- import { test } from "node:test";
9
- import assert from "node:assert/strict";
10
- import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
11
- import { tmpdir } from "node:os";
12
- import { join } from "node:path";
13
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
14
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
15
- import { loadEntries, parseFrontmatter } from "../src/content.js";
16
- import { createServer } from "../src/server.js";
17
- import { rankByMetadata, searchBodies } from "../src/search.js";
18
- import { TARGETS, mergeServerConfig, runInit, upsertAgentsBlock } from "../src/init.js";
19
- const entries = loadEntries();
20
- /**
21
- * Conecta um cliente MCP de verdade ao servidor, em memória (sem processo nem stdio). Assim os
22
- * testes exercitam o protocolo completo — o mesmo caminho que uma IDE usaria.
23
- */
24
- async function connect() {
25
- const [a, b] = InMemoryTransport.createLinkedPair();
26
- const client = new Client({ name: "test", version: "0" });
27
- await Promise.all([createServer(entries).connect(a), client.connect(b)]);
28
- return client;
29
- }
30
- // Extrai o texto da primeira parte de uma resposta de tool.
31
- const firstText = (r) => r.content[0].text;
32
- test("parseFrontmatter separa frontmatter e corpo", () => {
33
- const { data, body } = parseFrontmatter("---\nname: x\ndescription: d\n---\n\n# Corpo\n");
34
- assert.equal(data.name, "x");
35
- assert.equal(body, "# Corpo");
36
- });
37
- test("loader carrega skills e agentes reais", () => {
38
- assert.ok(entries.some((e) => e.kind === "skill" && e.id === "nestjs"));
39
- assert.ok(entries.some((e) => e.kind === "agent" && e.id === "code-reviewer"));
40
- // A description alimenta o ranking do `recommend_for_task`; sem ela a entrada fica invisível.
41
- assert.ok(entries.every((e) => e.description.length > 0));
42
- });
43
- test("rankByMetadata prioriza a skill relevante", () => {
44
- const top = rankByMetadata(entries, "criar Dockerfile multi-stage", "skill")[0];
45
- assert.equal(top.entry.id, "docker");
46
- });
47
- test("searchBodies encontra trechos", () => {
48
- assert.ok(searchBodies(entries, "migration").length > 0);
49
- // "a" é stopword/1 letra: a consulta vira vazia e não deve devolver tudo.
50
- assert.deepEqual(searchBodies(entries, "a"), []);
51
- });
52
- test("tools: list, get, erro em id inexistente, recommend", async () => {
53
- const client = await connect();
54
- const tools = (await client.listTools()).tools.map((t) => t.name);
55
- for (const n of [
56
- "list_skills",
57
- "get_skill",
58
- "list_agents",
59
- "get_agent",
60
- "search_guidelines",
61
- "recommend_for_task",
62
- ]) {
63
- assert.ok(tools.includes(n), n);
64
- }
65
- assert.match(firstText(await client.callTool({ name: "list_skills", arguments: {} })), /hexagonal/);
66
- assert.match(firstText(await client.callTool({ name: "get_skill", arguments: { name: "docker" } })), /Skill: docker/);
67
- // Id inexistente deve ser um erro "de uso" (isError), não uma exceção do protocolo.
68
- const missing = await client.callTool({ name: "get_agent", arguments: { name: "nope" } });
69
- assert.equal(missing.isError, true);
70
- assert.match(firstText(await client.callTool({
71
- name: "recommend_for_task",
72
- arguments: { task: "revisar segurança de código" },
73
- })), /security-reviewer/);
74
- });
75
- test("prompts e resources expostos", async () => {
76
- const client = await connect();
77
- assert.ok((await client.listPrompts()).prompts.some((p) => p.name === "skill-nestjs"));
78
- const res = await client.listResources();
79
- assert.ok(res.resources.some((r) => r.uri === "ds9://skills/typeorm"));
80
- const read = await client.readResource({ uri: "ds9://agents/test-writer" });
81
- assert.match(read.contents[0].text, /Agente: test-writer/);
82
- });
83
- test("mergeServerConfig preserva outros servidores e é idempotente", () => {
84
- const cursor = TARGETS.find((t) => t.id === "cursor");
85
- const once = mergeServerConfig({ mcpServers: { outro: { command: "x" } } }, cursor, "linux");
86
- // O servidor que o usuário já tinha ("outro") não pode ser perdido.
87
- assert.deepEqual(Object.keys(once.mcpServers).sort(), ["ds9", "outro"]);
88
- // Rodar de novo não pode mudar nada.
89
- assert.deepEqual(mergeServerConfig(once, cursor, "linux"), once);
90
- // VS Code usa `servers` + `type: stdio`; no Windows o comando passa por `cmd /c`.
91
- const vscode = TARGETS.find((t) => t.id === "vscode");
92
- const v = mergeServerConfig({}, vscode, "win32");
93
- assert.equal(v.servers.ds9.type, "stdio");
94
- assert.equal(v.servers.ds9.command, "cmd");
95
- });
96
- test("upsertAgentsBlock insere uma vez e atualiza sem duplicar", () => {
97
- const once = upsertAgentsBlock("# Meu projeto\n");
98
- assert.match(once, /# Meu projeto/); // conteúdo prévio preservado
99
- assert.equal(upsertAgentsBlock(once), once); // segunda execução não muda nada
100
- assert.equal(once.match(/ds9-mcp:start/g)?.length, 1); // bloco nunca duplica
101
- });
102
- test("runInit grava config e AGENTS.md; recusa JSON inválido", () => {
103
- // Diretório temporário: o teste nunca toca em arquivos reais do usuário.
104
- const dir = mkdtempSync(join(tmpdir(), "ds9-init-"));
105
- const cursor = TARGETS.find((t) => t.id === "cursor");
106
- const r = runInit(cursor, dir);
107
- assert.ok(JSON.parse(readFileSync(r.configFile, "utf8")).mcpServers.ds9);
108
- assert.match(readFileSync(r.agentsFile, "utf8"), /recommend_for_task/);
109
- // Arquivo com comentário (JSONC) não pode ser sobrescrito: deve falhar com orientação.
110
- writeFileSync(join(dir, ".mcp.json"), "{ // comentário\n}");
111
- assert.throws(() => runInit(TARGETS[0], dir), /não é JSON puro/);
112
- });