@thiagodiogo/pscode 1.0.0 → 1.0.1
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/README.md +2 -2
- package/dist/cli/index.js +6 -6
- package/dist/commands/config.d.ts +4 -12
- package/dist/commands/config.js +69 -242
- package/dist/core/change-metadata/schema.d.ts +1 -0
- package/dist/core/change-metadata/schema.js +1 -0
- package/dist/core/{archive.d.ts → complete.d.ts} +2 -2
- package/dist/core/{archive.js → complete.js} +28 -5
- package/dist/core/completions/command-registry.js +5 -5
- package/dist/core/config-schema.d.ts +1 -5
- package/dist/core/config-schema.js +2 -5
- package/dist/core/global-config.d.ts +1 -3
- package/dist/core/global-config.js +1 -1
- package/dist/core/init.d.ts +2 -0
- package/dist/core/init.js +81 -20
- package/dist/core/jira-transition.d.ts +16 -0
- package/dist/core/jira-transition.js +29 -0
- package/dist/core/migration.d.ts +3 -12
- package/dist/core/migration.js +10 -72
- package/dist/core/presets/dixi.d.ts +32 -0
- package/dist/core/presets/dixi.js +405 -0
- package/dist/core/profile-sync-drift.js +9 -1
- package/dist/core/profiles.d.ts +23 -21
- package/dist/core/profiles.js +28 -24
- package/dist/core/shared/skill-generation.js +3 -3
- package/dist/core/shared/tool-detection.d.ts +1 -1
- package/dist/core/shared/tool-detection.js +1 -1
- package/dist/core/templates/skill-templates.d.ts +1 -1
- package/dist/core/templates/skill-templates.js +1 -1
- package/dist/core/templates/workflows/apply-change.js +3 -3
- package/dist/core/templates/workflows/archive-change.d.ts +2 -2
- package/dist/core/templates/workflows/archive-change.js +10 -10
- package/dist/core/templates/workflows/onboard.js +9 -9
- package/dist/core/update.d.ts +1 -6
- package/dist/core/update.js +5 -29
- package/dist/core/workspace/foundation.d.ts +1 -1
- package/dist/core/workspace/foundation.js +1 -1
- package/dist/core/workspace/legacy-state.js +1 -1
- package/dist/core/workspace/skills.d.ts +4 -3
- package/dist/core/workspace/skills.js +3 -3
- package/package.json +4 -3
- package/pscode/content/dixi/architectures/feature-sliced-react/eslint-architecture.mjs.template +44 -0
- package/pscode/content/dixi/architectures/feature-sliced-react/features/README.md.template +30 -0
- package/pscode/content/dixi/architectures/feature-sliced-react/skeleton.yaml +8 -0
- package/pscode/content/dixi/architectures/hexagonal-spring/ArchitectureTest.java.template +41 -0
- package/pscode/content/dixi/architectures/hexagonal-spring/skeleton.yaml +11 -0
- package/pscode/content/dixi/claude-runtime/CLAUDE.md.java.template +62 -0
- package/pscode/content/dixi/claude-runtime/CLAUDE.md.react.template +74 -0
- package/pscode/content/dixi/claude-runtime/commands/adr.md +75 -0
- package/pscode/content/dixi/claude-runtime/commands/arch-check.md +64 -0
- package/pscode/content/dixi/claude-runtime/commands/dod.md +66 -0
- package/pscode/content/dixi/claude-runtime/commands/jira-draft.md +80 -0
- package/pscode/content/dixi/claude-runtime/commands/jira-setup.md +105 -0
- package/pscode/content/dixi/claude-runtime/commands/jira-sync.md +69 -0
- package/pscode/content/dixi/claude-runtime/commands/rfc.md +73 -0
- package/pscode/content/dixi/claude-runtime/hooks/arch-guard.mjs +101 -0
- package/pscode/content/dixi/claude-runtime/hooks/jira-context.mjs +60 -0
- package/pscode/content/dixi/claude-runtime/skills/pstld-arch-guardian.md +101 -0
- package/pscode/content/dixi/claude-runtime/skills/pstld-commit-crafter.md +98 -0
- package/pscode/content/dixi/claude-runtime/skills/pstld-jira-context.md +64 -0
- package/pscode/content/dixi/context/java/architecture.md +143 -0
- package/pscode/content/dixi/context/java/naming.md +62 -0
- package/pscode/content/dixi/context/java/testing.md +162 -0
- package/pscode/content/dixi/context/react/architecture.md +119 -0
- package/pscode/content/dixi/context/react/naming.md +129 -0
- package/pscode/content/dixi/context/react/testing.md +141 -0
- package/pscode/content/dixi/context/shared/commits.md +47 -0
- package/pscode/content/dixi/context/shared/dev-flow.md +53 -0
- package/pscode/content/dixi/context/shared/dod.md +38 -0
- package/pscode/content/dixi/context/shared/pr-flow.md +53 -0
- package/pscode/content/dixi/kit/java/.editorconfig +25 -0
- package/pscode/content/dixi/kit/java/.github/workflows/ci-java.yml +68 -0
- package/pscode/content/dixi/kit/java/.husky/commit-msg +2 -0
- package/pscode/content/dixi/kit/react/.editorconfig +20 -0
- package/pscode/content/dixi/kit/react/.github/workflows/ci-react.yml +80 -0
- package/pscode/content/dixi/kit/react/.husky/commit-msg +2 -0
- package/pscode/content/dixi/kit/react/.husky/pre-commit +2 -0
- package/pscode/content/dixi/kit/react/lint-staged.config.mjs +4 -0
- package/pscode/content/dixi/kit/shared/.commitlintrc.yml +15 -0
- package/pscode/content/dixi/kit/shared/.github/pull_request_template.md +24 -0
- package/schemas/pstld-workflow/schema.yaml +67 -0
- package/schemas/pstld-workflow/templates/design.md +15 -0
- package/schemas/pstld-workflow/templates/rfc.md +26 -0
- package/schemas/pstld-workflow/templates/tasks.md +15 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# pstld-arch-guardian
|
|
2
|
+
|
|
3
|
+
## Trigger
|
|
4
|
+
|
|
5
|
+
Esta skill é auto-invocada **antes de aplicar uma edição** em arquivos que se enquadrem em:
|
|
6
|
+
|
|
7
|
+
- Java: `src/**/infrastructure/**`
|
|
8
|
+
- React/Next: `src/features/**`, `src/app/**`, `src/pages/**`
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Comportamento
|
|
13
|
+
|
|
14
|
+
### 1. Detectar stack do projeto
|
|
15
|
+
|
|
16
|
+
Leia `.pscode-dixi.yaml` na raiz do projeto e extraia o campo `family`.
|
|
17
|
+
|
|
18
|
+
- Se o arquivo **não existir** ou `family` for `null` ou não reconhecido → **não faça nenhuma verificação**, prossiga normalmente.
|
|
19
|
+
- Se `family: java` → aplique verificação hexagonal (seção 2).
|
|
20
|
+
- Se `family: react` → aplique verificação feature-sliced (seção 3).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
### 2. Verificação hexagonal (Java)
|
|
25
|
+
|
|
26
|
+
Aplicável quando `family: java` e o arquivo editado está em `src/**/infrastructure/**`.
|
|
27
|
+
|
|
28
|
+
**Bloqueio — import direto de domain sem porta:**
|
|
29
|
+
|
|
30
|
+
Verifique se o novo conteúdo do arquivo contém imports de pacotes como:
|
|
31
|
+
- `import ...domain.model.*`
|
|
32
|
+
- `import ...domain.entity.*`
|
|
33
|
+
- `import ...domain.service.*`
|
|
34
|
+
|
|
35
|
+
...sem passar por uma interface de porta (`domain.port.in.*` ou `domain.port.out.*`).
|
|
36
|
+
|
|
37
|
+
Se detectar esta violação:
|
|
38
|
+
|
|
39
|
+
> ❌ **Violação hexagonal detectada**
|
|
40
|
+
>
|
|
41
|
+
> O arquivo `[caminho do arquivo]` importa diretamente de `domain/` sem usar uma porta.
|
|
42
|
+
> A regra de dependência exige que `infrastructure` acesse `domain` apenas via interfaces de porta.
|
|
43
|
+
>
|
|
44
|
+
> Consulte `pastelsdd/context/architecture.md` para a lista de imports permitidos.
|
|
45
|
+
>
|
|
46
|
+
> **A edição foi bloqueada.** Refatore para usar a porta adequada antes de continuar.
|
|
47
|
+
|
|
48
|
+
**Permitido — import via porta:**
|
|
49
|
+
|
|
50
|
+
Se o arquivo importa apenas de `domain.port.in.*` ou `domain.port.out.*`, permita a edição sem mensagem.
|
|
51
|
+
|
|
52
|
+
**Fora de infrastructure:**
|
|
53
|
+
|
|
54
|
+
Se o arquivo não está em `src/**/infrastructure/**`, não aplique nenhuma verificação hexagonal.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### 3. Verificação feature-sliced (React/Next)
|
|
59
|
+
|
|
60
|
+
Aplicável quando `family: react`.
|
|
61
|
+
|
|
62
|
+
#### 3a. Import cruzado entre features (bloqueio)
|
|
63
|
+
|
|
64
|
+
Se o arquivo editado está em `src/features/<feature-a>/` e o novo conteúdo importa de outro diretório sob `src/features/` (ex: `../feature-b/`, `../../features/feature-b/`):
|
|
65
|
+
|
|
66
|
+
> ❌ **Violação feature-sliced detectada**
|
|
67
|
+
>
|
|
68
|
+
> O arquivo `[caminho]` importa de outra feature (`[feature-b]`).
|
|
69
|
+
> Features devem ser independentes entre si — imports cruzados criam acoplamento proibido.
|
|
70
|
+
>
|
|
71
|
+
> Use `src/shared/` para código reutilizado entre features.
|
|
72
|
+
> Consulte `pastelsdd/context/architecture.md`.
|
|
73
|
+
>
|
|
74
|
+
> **A edição foi bloqueada.**
|
|
75
|
+
|
|
76
|
+
**Permitido:** imports dentro da mesma feature ou de `src/shared/`.
|
|
77
|
+
|
|
78
|
+
#### 3b. Lógica de negócio inline em páginas (warning)
|
|
79
|
+
|
|
80
|
+
Se o arquivo editado está em `src/app/**` ou `src/pages/**` e o novo conteúdo contém qualquer um dos sinais abaixo:
|
|
81
|
+
- Hook `useState` + `useEffect` com corpo de mais de 10 linhas no mesmo componente
|
|
82
|
+
- Função de transformação/validação de dados definida inline no componente (não em hook separado)
|
|
83
|
+
|
|
84
|
+
Emita aviso (não bloqueie):
|
|
85
|
+
|
|
86
|
+
> ⚠️ **Atenção — lógica de negócio em página detectada**
|
|
87
|
+
>
|
|
88
|
+
> O componente em `[caminho]` parece conter lógica de negócio inline.
|
|
89
|
+
> Considere extrair para um custom hook em `src/features/` ou `src/shared/hooks/`.
|
|
90
|
+
>
|
|
91
|
+
> Consulte `pastelsdd/context/architecture.md` para o padrão recomendado.
|
|
92
|
+
>
|
|
93
|
+
> A edição foi aplicada, mas revise antes de finalizar.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Comportamento seguro
|
|
98
|
+
|
|
99
|
+
- Se `.pscode-dixi.yaml` não existe → nenhuma ação, nenhum erro.
|
|
100
|
+
- Se `family` tem valor desconhecido → nenhuma ação.
|
|
101
|
+
- Em nenhuma hipótese esta skill modifica arquivos fora do arquivo-alvo da edição original.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# pstld-commit-crafter
|
|
2
|
+
|
|
3
|
+
## Trigger
|
|
4
|
+
|
|
5
|
+
Esta skill é auto-invocada quando o usuário mencionar **commit** no prompt ou solicitar fazer commit:
|
|
6
|
+
|
|
7
|
+
- "commit", "fazer commit", "commitar", "cria um commit", "faz o commit", "git commit"
|
|
8
|
+
|
|
9
|
+
Ative **antes** de executar qualquer `git commit`.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Comportamento
|
|
14
|
+
|
|
15
|
+
### 1. Detectar stack para inferência de escopo
|
|
16
|
+
|
|
17
|
+
Leia `.pscode-dixi.yaml` na raiz do projeto e extraia `family`.
|
|
18
|
+
|
|
19
|
+
**Se `family: java`:**
|
|
20
|
+
- Inspecione os arquivos modificados via `git status` / `git diff --name-only`.
|
|
21
|
+
- Identifique o bounded context pelo diretório após o base package.
|
|
22
|
+
- Ex: `src/main/java/com/empresa/payment/` → escopo `payment`
|
|
23
|
+
- Ex: `src/main/java/com/empresa/order/domain/` → escopo `order`
|
|
24
|
+
|
|
25
|
+
**Se `family: react`:**
|
|
26
|
+
- Identifique o nome da feature pelo diretório em `src/features/`.
|
|
27
|
+
- Ex: `src/features/user-management/` → escopo `user-management`
|
|
28
|
+
- Ex: `src/features/checkout/` → escopo `checkout`
|
|
29
|
+
|
|
30
|
+
**Se `.pscode-dixi.yaml` não existir ou `family` for `null`:**
|
|
31
|
+
- Infira o escopo pelo diretório principal dos arquivos modificados.
|
|
32
|
+
- Ex: maioria em `src/services/billing/` → escopo `billing`
|
|
33
|
+
- Ex: maioria em `src/` → escopo do módulo mais frequente nos caminhos
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### 2. Verificar ticket JIRA
|
|
38
|
+
|
|
39
|
+
Leia `pastelsdd/jira.yaml` na raiz do projeto.
|
|
40
|
+
|
|
41
|
+
- Se o arquivo **não existir** ou tiver `configured: false`: mensagem sem ticket JIRA (vá para seção 3).
|
|
42
|
+
- Se tiver `project_key` configurado: o ticket **é obrigatório**.
|
|
43
|
+
|
|
44
|
+
**Ticket presente no prompt:** use diretamente (ex: usuário mencionou "PROJ-42").
|
|
45
|
+
|
|
46
|
+
**Ticket ausente no prompt:** pergunte ao usuário:
|
|
47
|
+
|
|
48
|
+
> Qual o número do ticket JIRA? (ex: 42)
|
|
49
|
+
> O project key configurado é `[PROJECT_KEY]`.
|
|
50
|
+
|
|
51
|
+
Aguarde a resposta antes de continuar. Monte o sufixo `[PROJECT_KEY-NNN]` com o número fornecido.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### 3. Montar a mensagem de commit
|
|
56
|
+
|
|
57
|
+
Identifique o tipo baseado nos arquivos modificados e no contexto:
|
|
58
|
+
|
|
59
|
+
| Tipo | Quando usar |
|
|
60
|
+
|------|-------------|
|
|
61
|
+
| `feat` | nova funcionalidade |
|
|
62
|
+
| `fix` | correção de bug |
|
|
63
|
+
| `refactor` | mudança sem alterar comportamento |
|
|
64
|
+
| `test` | adição/correção de testes |
|
|
65
|
+
| `docs` | documentação apenas |
|
|
66
|
+
| `chore` | configuração, build, dependências |
|
|
67
|
+
|
|
68
|
+
Gere a mensagem no formato:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
tipo(escopo): descrição concisa em português ou inglês [PROJECT_KEY-NNN]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Sem ticket quando `pastelsdd/jira.yaml` não configurado:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
tipo(escopo): descrição concisa
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Exemplos:**
|
|
81
|
+
- `feat(payment): adiciona validação de cartão de crédito [PROJ-99]`
|
|
82
|
+
- `fix(user-management): corrige redirect após logout [APP-12]`
|
|
83
|
+
- `refactor(order): extrai lógica de cálculo para OrderPricingService [PROJ-55]`
|
|
84
|
+
- `test(checkout): cobre cenário de pedido sem estoque [SHOP-7]`
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### 4. Apresentar a mensagem ao usuário
|
|
89
|
+
|
|
90
|
+
Exiba a mensagem proposta e pergunte:
|
|
91
|
+
|
|
92
|
+
> Mensagem de commit proposta:
|
|
93
|
+
> ```
|
|
94
|
+
> tipo(escopo): descrição [PROJECT_KEY-NNN]
|
|
95
|
+
> ```
|
|
96
|
+
> Deseja usar esta mensagem ou ajustar?
|
|
97
|
+
|
|
98
|
+
Se o usuário aprovar, execute o commit. Se quiser ajustar, aplique a correção e confirme novamente.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# pstld-jira-context
|
|
2
|
+
|
|
3
|
+
## Trigger
|
|
4
|
+
|
|
5
|
+
Esta skill é auto-invocada quando o prompt do usuário contém uma string no formato `[A-Z]+-\d+`:
|
|
6
|
+
|
|
7
|
+
- Exemplos: `PROJ-123`, `MYAPP-42`, `BE-7`, `PAYMENT-1001`
|
|
8
|
+
- A ativação ocorre **antes de gerar a resposta**, para enriquecer o contexto.
|
|
9
|
+
|
|
10
|
+
Se o prompt não contém nenhum padrão `[A-Z]+-\d+`, a skill não é ativada.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Comportamento
|
|
15
|
+
|
|
16
|
+
### 1. Verificar configuração JIRA
|
|
17
|
+
|
|
18
|
+
Leia `pastelsdd/jira.yaml` na raiz do projeto.
|
|
19
|
+
|
|
20
|
+
**Se o arquivo não existir ou tiver `configured: false`:**
|
|
21
|
+
|
|
22
|
+
Exiba o seguinte aviso **uma única vez por sessão** (não repita em prompts subsequentes):
|
|
23
|
+
|
|
24
|
+
> ℹ️ Integração JIRA não configurada — edite `pastelsdd/jira.yaml` para habilitar.
|
|
25
|
+
|
|
26
|
+
Prossiga sem buscar contexto do ticket.
|
|
27
|
+
|
|
28
|
+
**Se tiver `configured: true`:** continue para a seção 2.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
### 2. Buscar contexto do ticket via MCP
|
|
33
|
+
|
|
34
|
+
Use a ferramenta `mcp__atlassian__getJiraIssue` com a chave detectada no prompt (ex: `PROJ-123`).
|
|
35
|
+
|
|
36
|
+
**Se o ticket for encontrado**, injete as seguintes informações como contexto adicional **antes** de gerar a resposta:
|
|
37
|
+
|
|
38
|
+
> **Contexto JIRA — [CHAVE]**
|
|
39
|
+
>
|
|
40
|
+
> **Título:** [título do ticket]
|
|
41
|
+
>
|
|
42
|
+
> **Descrição:**
|
|
43
|
+
> [descrição do ticket]
|
|
44
|
+
>
|
|
45
|
+
> **Critérios de aceite:**
|
|
46
|
+
> [critérios de aceite, se disponíveis]
|
|
47
|
+
|
|
48
|
+
Em seguida, prossiga normalmente para responder ao prompt do usuário.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### 3. Tratamento de erro
|
|
53
|
+
|
|
54
|
+
**Se `mcp__atlassian__getJiraIssue` retornar erro** (ticket inexistente, permissão negada, MCP indisponível):
|
|
55
|
+
|
|
56
|
+
> ⚠️ Não foi possível obter contexto de `[CHAVE]`: [mensagem de erro resumida]. Prosseguindo sem contexto JIRA.
|
|
57
|
+
|
|
58
|
+
Não bloqueie a resposta — prossiga normalmente.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Controle de sessão
|
|
63
|
+
|
|
64
|
+
O aviso "Integração JIRA não configurada" deve ser exibido **no máximo uma vez por sessão**. Se já foi exibido, omita-o em ativações subsequentes e prossiga em silêncio.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Arquitetura — Java/Spring (Hexagonal)
|
|
2
|
+
|
|
3
|
+
## Estrutura de pacotes obrigatória
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
com.example.app/
|
|
7
|
+
├── domain/
|
|
8
|
+
│ ├── model/ # Entidades, value objects, agregados
|
|
9
|
+
│ ├── port/
|
|
10
|
+
│ │ ├── in/ # Use case interfaces (portas de entrada)
|
|
11
|
+
│ │ └── out/ # Repository, gateway interfaces (portas de saída)
|
|
12
|
+
│ └── exception/ # Exceções de domínio
|
|
13
|
+
├── application/
|
|
14
|
+
│ └── usecase/ # Implementações dos use cases
|
|
15
|
+
└── infrastructure/
|
|
16
|
+
└── adapter/
|
|
17
|
+
├── in/ # Controllers REST, consumers de mensagem
|
|
18
|
+
└── out/ # Implementations de repository, clientes HTTP
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Regras de dependência
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
infrastructure → application → domain
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- `domain` não importa nada externo ao próprio domínio
|
|
28
|
+
- `application` importa apenas `domain`
|
|
29
|
+
- `infrastructure` importa `application` e `domain`, mas nunca ao contrário
|
|
30
|
+
- Frameworks Spring (anotações, contexto) pertencem à camada `infrastructure`
|
|
31
|
+
|
|
32
|
+
## Exemplos por camada
|
|
33
|
+
|
|
34
|
+
### Domain — modelo
|
|
35
|
+
|
|
36
|
+
```java
|
|
37
|
+
// domain/model/Pedido.java
|
|
38
|
+
public class Pedido {
|
|
39
|
+
private final PedidoId id;
|
|
40
|
+
private final List<ItemPedido> itens;
|
|
41
|
+
private StatusPedido status;
|
|
42
|
+
|
|
43
|
+
public void confirmar() {
|
|
44
|
+
if (itens.isEmpty()) throw new PedidoVazioException();
|
|
45
|
+
this.status = StatusPedido.CONFIRMADO;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Domain — porta de entrada
|
|
51
|
+
|
|
52
|
+
```java
|
|
53
|
+
// domain/port/in/ConfirmarPedidoUseCase.java
|
|
54
|
+
public interface ConfirmarPedidoUseCase {
|
|
55
|
+
Pedido confirmar(PedidoId pedidoId);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Domain — porta de saída
|
|
60
|
+
|
|
61
|
+
```java
|
|
62
|
+
// domain/port/out/PedidoRepository.java
|
|
63
|
+
public interface PedidoRepository {
|
|
64
|
+
Optional<Pedido> findById(PedidoId id);
|
|
65
|
+
Pedido save(Pedido pedido);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Application — use case
|
|
70
|
+
|
|
71
|
+
```java
|
|
72
|
+
// application/usecase/ConfirmarPedidoService.java
|
|
73
|
+
@Service
|
|
74
|
+
public class ConfirmarPedidoService implements ConfirmarPedidoUseCase {
|
|
75
|
+
private final PedidoRepository repository;
|
|
76
|
+
|
|
77
|
+
@Override
|
|
78
|
+
public Pedido confirmar(PedidoId pedidoId) {
|
|
79
|
+
Pedido pedido = repository.findById(pedidoId)
|
|
80
|
+
.orElseThrow(() -> new PedidoNaoEncontradoException(pedidoId));
|
|
81
|
+
pedido.confirmar();
|
|
82
|
+
return repository.save(pedido);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Infrastructure — adapter de entrada
|
|
88
|
+
|
|
89
|
+
```java
|
|
90
|
+
// infrastructure/adapter/in/PedidoController.java
|
|
91
|
+
@RestController
|
|
92
|
+
@RequestMapping("/pedidos")
|
|
93
|
+
public class PedidoController {
|
|
94
|
+
private final ConfirmarPedidoUseCase confirmarPedido;
|
|
95
|
+
|
|
96
|
+
@PostMapping("/{id}/confirmar")
|
|
97
|
+
public ResponseEntity<PedidoResponse> confirmar(@PathVariable String id) {
|
|
98
|
+
Pedido pedido = confirmarPedido.confirmar(new PedidoId(id));
|
|
99
|
+
return ResponseEntity.ok(PedidoMapper.toResponse(pedido));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Infrastructure — adapter de saída
|
|
105
|
+
|
|
106
|
+
```java
|
|
107
|
+
// infrastructure/adapter/out/PedidoJpaRepository.java
|
|
108
|
+
@Repository
|
|
109
|
+
public class PedidoJpaRepository implements PedidoRepository {
|
|
110
|
+
private final PedidoJpaEntityRepository jpa;
|
|
111
|
+
|
|
112
|
+
@Override
|
|
113
|
+
public Optional<Pedido> findById(PedidoId id) {
|
|
114
|
+
return jpa.findById(id.value()).map(PedidoMapper::toDomain);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Regras adicionais
|
|
120
|
+
|
|
121
|
+
- **Sem lógica de negócio em controllers ou repositories** — toda regra de negócio fica em `domain` ou `application`
|
|
122
|
+
- **Entidades de domínio são imutáveis por padrão** — use construtores e métodos explícitos, evite setters
|
|
123
|
+
- **Mappers** ficam em `infrastructure` para converter entre entidades de domínio e entidades JPA/DTOs
|
|
124
|
+
- **Anotações Spring** (`@Service`, `@Repository`, `@RestController`) pertencem à infraestrutura, não ao domínio
|
|
125
|
+
|
|
126
|
+
## Skeleton e guardrails automáticos (profile dixi)
|
|
127
|
+
|
|
128
|
+
`pscode init --profile dixi` cria automaticamente o skeleton de pastas hexagonal com `.gitkeep` em cada diretório e gera `src/test/java/{basePackage}/ArchitectureTest.java` com três regras ArchUnit pré-configuradas:
|
|
129
|
+
|
|
130
|
+
1. `domain` não depende de `infrastructure`
|
|
131
|
+
2. `application` não depende de `infrastructure`
|
|
132
|
+
3. `infrastructure.adapter.in` não depende de `infrastructure.adapter.out`
|
|
133
|
+
|
|
134
|
+
O `basePackage` é detectado automaticamente do `pom.xml` (`groupId` + `artifactId` sem hífens). Adicione a dependência ArchUnit ao `pom.xml` para ativar as verificações:
|
|
135
|
+
|
|
136
|
+
```xml
|
|
137
|
+
<dependency>
|
|
138
|
+
<groupId>com.tngtech.archunit</groupId>
|
|
139
|
+
<artifactId>archunit-junit5</artifactId>
|
|
140
|
+
<version>1.3.0</version>
|
|
141
|
+
<scope>test</scope>
|
|
142
|
+
</dependency>
|
|
143
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Convenções de Nomenclatura — Java/Spring
|
|
2
|
+
|
|
3
|
+
## Regra geral
|
|
4
|
+
|
|
5
|
+
O nome deve revelar o papel da classe na arquitetura hexagonal. Evite sufixos técnicos no domínio; use sufixos técnicos apenas na infraestrutura.
|
|
6
|
+
|
|
7
|
+
## Domain
|
|
8
|
+
|
|
9
|
+
Substantivos sem sufixo técnico. Nomes de negócio, não de tecnologia.
|
|
10
|
+
|
|
11
|
+
| Tipo | Padrão | Exemplos válidos | Inválidos |
|
|
12
|
+
|------------------|----------------------|----------------------------------------|------------------------------------|
|
|
13
|
+
| Entidade | `NomeDominio` | `Pedido`, `Usuario`, `Produto` | `PedidoEntity`, `UsuarioModel` |
|
|
14
|
+
| Value Object | `NomeDominio` | `Email`, `CPF`, `PedidoId` | `EmailVO`, `CPFValue` |
|
|
15
|
+
| Exceção domínio | `Motivo + Exception` | `PedidoVazioException`, `SaldoInsuficienteException` | `PedidoError`, `InvalidPedido` |
|
|
16
|
+
| Porta de entrada | `Verbo + UseCase` | `ConfirmarPedidoUseCase`, `BuscarUsuarioUseCase` | `PedidoService`, `PedidoPort` |
|
|
17
|
+
| Porta de saída | `NomeDominio + Repository/Gateway` | `PedidoRepository`, `NotificacaoGateway` | `IPedidoRepo`, `PedidoDAO` |
|
|
18
|
+
|
|
19
|
+
## Application
|
|
20
|
+
|
|
21
|
+
Implementações dos use cases. Verbo + sufixo `Service`.
|
|
22
|
+
|
|
23
|
+
| Tipo | Padrão | Exemplos válidos | Inválidos |
|
|
24
|
+
|-------------|---------------------|-------------------------------------------|----------------------------------|
|
|
25
|
+
| Use Case impl | `Verbo + Service` | `ConfirmarPedidoService`, `CadastrarUsuarioService` | `PedidoServiceImpl`, `OrderManager` |
|
|
26
|
+
|
|
27
|
+
## Infrastructure
|
|
28
|
+
|
|
29
|
+
Sufixos técnicos que revelam o mecanismo de implementação.
|
|
30
|
+
|
|
31
|
+
| Tipo | Padrão | Exemplos válidos |
|
|
32
|
+
|------------------------|---------------------------|-----------------------------------------------|
|
|
33
|
+
| Controller REST | `Dominio + Controller` | `PedidoController`, `UsuarioController` |
|
|
34
|
+
| JPA Entity | `Dominio + JpaEntity` | `PedidoJpaEntity`, `UsuarioJpaEntity` |
|
|
35
|
+
| JPA Repository impl | `Dominio + JpaRepository` | `PedidoJpaRepository` |
|
|
36
|
+
| Spring Data interface | `Dominio + JpaEntityRepository` | `PedidoJpaEntityRepository` |
|
|
37
|
+
| HTTP Client | `Servico + Client` | `PagamentoClient`, `NotificacaoClient` |
|
|
38
|
+
| Message Consumer | `Evento + Consumer` | `PedidoCriadoConsumer` |
|
|
39
|
+
| Message Producer | `Evento + Producer` | `PedidoConfirmadoProducer` |
|
|
40
|
+
| Mapper | `Dominio + Mapper` | `PedidoMapper`, `UsuarioMapper` |
|
|
41
|
+
| DTO de entrada | `Acao + Request` | `ConfirmarPedidoRequest`, `CadastrarUsuarioRequest` |
|
|
42
|
+
| DTO de saída | `Dominio + Response` | `PedidoResponse`, `UsuarioResponse` |
|
|
43
|
+
|
|
44
|
+
## Packages
|
|
45
|
+
|
|
46
|
+
Siga a estrutura hexagonal. Use nomes de domínio em lowercase, sem underscores.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
com.empresa.produto.pedido.domain.model
|
|
50
|
+
com.empresa.produto.pedido.domain.port.in
|
|
51
|
+
com.empresa.produto.pedido.application.usecase
|
|
52
|
+
com.empresa.produto.pedido.infrastructure.adapter.in
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Métodos
|
|
56
|
+
|
|
57
|
+
| Tipo | Padrão | Exemplos |
|
|
58
|
+
|------------------------|-----------------------|--------------------------------------------------|
|
|
59
|
+
| Use case (comando) | verbo no infinitivo | `confirmar()`, `cancelar()`, `processar()` |
|
|
60
|
+
| Use case (query) | `buscar/listar/obter` | `buscarPorId()`, `listarAtivos()`, `obterSaldo()`|
|
|
61
|
+
| Domain action | verbo de negócio | `ativar()`, `debitar()`, `aplicarDesconto()` |
|
|
62
|
+
| Factory method | `criar/novo` | `criar()`, `criarVazio()`, `novaInstancia()` |
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Testes — Java/Spring
|
|
2
|
+
|
|
3
|
+
## Pirâmide de testes
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
[E2E — RestAssured] ← poucos, fluxos críticos
|
|
7
|
+
[Integração — Testcontainers] ← adapters de saída, APIs
|
|
8
|
+
[Unitários — JUnit 5 + Mockito] ← domain e application (maioria)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Nível 1 — Testes unitários (domain e application)
|
|
12
|
+
|
|
13
|
+
Testam lógica pura sem Spring, sem banco, sem rede.
|
|
14
|
+
|
|
15
|
+
**Ferramentas:** JUnit 5, Mockito
|
|
16
|
+
|
|
17
|
+
**Onde ficam:** `test/java/.../domain/` e `test/java/.../application/`
|
|
18
|
+
|
|
19
|
+
```java
|
|
20
|
+
// Teste de domain
|
|
21
|
+
class PedidoTest {
|
|
22
|
+
@Test
|
|
23
|
+
void dado_pedido_com_itens_quando_confirmar_entao_status_confirmado() {
|
|
24
|
+
// Given
|
|
25
|
+
Pedido pedido = PedidoTestFactory.comUmItem();
|
|
26
|
+
|
|
27
|
+
// When
|
|
28
|
+
pedido.confirmar();
|
|
29
|
+
|
|
30
|
+
// Then
|
|
31
|
+
assertThat(pedido.getStatus()).isEqualTo(StatusPedido.CONFIRMADO);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Test
|
|
35
|
+
void dado_pedido_vazio_quando_confirmar_entao_lanca_excecao() {
|
|
36
|
+
Pedido pedido = PedidoTestFactory.vazio();
|
|
37
|
+
assertThatThrownBy(pedido::confirmar)
|
|
38
|
+
.isInstanceOf(PedidoVazioException.class);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```java
|
|
44
|
+
// Teste de application (use case)
|
|
45
|
+
class ConfirmarPedidoServiceTest {
|
|
46
|
+
@Mock PedidoRepository repository;
|
|
47
|
+
@InjectMocks ConfirmarPedidoService service;
|
|
48
|
+
|
|
49
|
+
@Test
|
|
50
|
+
void dado_pedido_existente_quando_confirmar_entao_salva_e_retorna() {
|
|
51
|
+
// Given
|
|
52
|
+
Pedido pedido = PedidoTestFactory.comUmItem();
|
|
53
|
+
when(repository.findById(pedido.getId())).thenReturn(Optional.of(pedido));
|
|
54
|
+
when(repository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
55
|
+
|
|
56
|
+
// When
|
|
57
|
+
Pedido resultado = service.confirmar(pedido.getId());
|
|
58
|
+
|
|
59
|
+
// Then
|
|
60
|
+
assertThat(resultado.getStatus()).isEqualTo(StatusPedido.CONFIRMADO);
|
|
61
|
+
verify(repository).save(pedido);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Nível 2 — Testes de integração (infrastructure)
|
|
67
|
+
|
|
68
|
+
Testam adapters com dependências reais (banco, mensageria, APIs externas).
|
|
69
|
+
|
|
70
|
+
**Ferramentas:** Testcontainers, `@SpringBootTest` com perfil de teste
|
|
71
|
+
|
|
72
|
+
**Onde ficam:** `test/java/.../infrastructure/`
|
|
73
|
+
|
|
74
|
+
```java
|
|
75
|
+
@SpringBootTest
|
|
76
|
+
@Testcontainers
|
|
77
|
+
class PedidoJpaRepositoryIT {
|
|
78
|
+
@Container
|
|
79
|
+
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
|
|
80
|
+
|
|
81
|
+
@DynamicPropertySource
|
|
82
|
+
static void props(DynamicPropertyRegistry r) {
|
|
83
|
+
r.add("spring.datasource.url", postgres::getJdbcUrl);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Autowired PedidoRepository repository;
|
|
87
|
+
|
|
88
|
+
@Test
|
|
89
|
+
void dado_pedido_salvo_quando_buscar_por_id_entao_retorna() {
|
|
90
|
+
Pedido pedido = repository.save(PedidoTestFactory.comUmItem());
|
|
91
|
+
assertThat(repository.findById(pedido.getId())).isPresent();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Nível 3 — Testes E2E (fluxos críticos)
|
|
97
|
+
|
|
98
|
+
Testam a API completa via HTTP, do controller ao banco.
|
|
99
|
+
|
|
100
|
+
**Ferramentas:** RestAssured, Testcontainers (banco real)
|
|
101
|
+
|
|
102
|
+
```java
|
|
103
|
+
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
|
104
|
+
@Testcontainers
|
|
105
|
+
class ConfirmarPedidoE2ETest {
|
|
106
|
+
@LocalServerPort int port;
|
|
107
|
+
|
|
108
|
+
@Test
|
|
109
|
+
void confirmar_pedido_retorna_200_com_status_confirmado() {
|
|
110
|
+
String pedidoId = criarPedidoViaApi();
|
|
111
|
+
|
|
112
|
+
given()
|
|
113
|
+
.port(port)
|
|
114
|
+
.contentType(ContentType.JSON)
|
|
115
|
+
.when()
|
|
116
|
+
.post("/pedidos/{id}/confirmar", pedidoId)
|
|
117
|
+
.then()
|
|
118
|
+
.statusCode(200)
|
|
119
|
+
.body("status", equalTo("CONFIRMADO"));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Nomenclatura
|
|
125
|
+
|
|
126
|
+
Padrão: `dado_[contexto]_quando_[acao]_entao_[resultado]`
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
dado_pedido_com_itens_quando_confirmar_entao_status_confirmado
|
|
130
|
+
dado_usuario_inativo_quando_autenticar_entao_lanca_UsuarioInativoException
|
|
131
|
+
dado_estoque_zerado_quando_reservar_entao_retorna_false
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Cobertura mínima
|
|
135
|
+
|
|
136
|
+
| Camada | Cobertura mínima |
|
|
137
|
+
|-----------------|-----------------|
|
|
138
|
+
| `domain` | 90% |
|
|
139
|
+
| `application` | 80% |
|
|
140
|
+
| `infrastructure`| 60% (integração complementa) |
|
|
141
|
+
|
|
142
|
+
Configure no `pom.xml` com JaCoCo:
|
|
143
|
+
|
|
144
|
+
```xml
|
|
145
|
+
<configuration>
|
|
146
|
+
<rules>
|
|
147
|
+
<rule>
|
|
148
|
+
<element>PACKAGE</element>
|
|
149
|
+
<includes>
|
|
150
|
+
<include>**.domain.**</include>
|
|
151
|
+
<include>**.application.**</include>
|
|
152
|
+
</includes>
|
|
153
|
+
<limits>
|
|
154
|
+
<limit>
|
|
155
|
+
<counter>LINE</counter>
|
|
156
|
+
<minimum>0.80</minimum>
|
|
157
|
+
</limit>
|
|
158
|
+
</limits>
|
|
159
|
+
</rule>
|
|
160
|
+
</rules>
|
|
161
|
+
</configuration>
|
|
162
|
+
```
|