@thiagodiogo/pscode 1.0.0 → 2.0.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/README.md +2 -2
- package/bin/pscode.js +0 -0
- package/dist/cli/index.js +6 -7
- 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 -3
- package/dist/core/{archive.js → complete.js} +63 -64
- package/dist/core/completions/command-registry.js +5 -9
- 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 -21
- 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 -2
- package/dist/core/profiles.d.ts +23 -21
- package/dist/core/profiles.js +28 -25
- package/dist/core/shared/skill-generation.js +3 -5
- package/dist/core/shared/tool-detection.d.ts +2 -2
- package/dist/core/shared/tool-detection.js +1 -3
- package/dist/core/templates/skill-templates.d.ts +1 -2
- package/dist/core/templates/skill-templates.js +1 -2
- 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 +21 -22
- 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
- package/dist/core/templates/workflows/sync-specs.d.ts +0 -10
- package/dist/core/templates/workflows/sync-specs.js +0 -290
|
@@ -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
|
+
```
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Arquitetura — React/Next.js (Feature-Sliced Design)
|
|
2
|
+
|
|
3
|
+
## Camadas (de baixo para cima)
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
pages / app ← Next.js routes, composição de features
|
|
7
|
+
features ← unidades funcionais independentes
|
|
8
|
+
entities ← modelos de negócio reutilizáveis
|
|
9
|
+
shared ← utilitários, UI base, types globais
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Camadas superiores podem importar das inferiores. **Camadas inferiores nunca importam das superiores.**
|
|
13
|
+
|
|
14
|
+
## Estrutura de pastas
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
src/
|
|
18
|
+
├── app/ # Next.js App Router (ou pages/)
|
|
19
|
+
│ ├── layout.tsx
|
|
20
|
+
│ └── checkout/
|
|
21
|
+
│ └── page.tsx # Apenas composição de features
|
|
22
|
+
├── features/
|
|
23
|
+
│ ├── checkout/ # Feature autocontida
|
|
24
|
+
│ │ ├── components/ # Componentes internos da feature
|
|
25
|
+
│ │ ├── hooks/ # Hooks internos
|
|
26
|
+
│ │ ├── services/ # Chamadas de API da feature
|
|
27
|
+
│ │ ├── types/ # Tipos TypeScript da feature
|
|
28
|
+
│ │ └── index.ts # Barrel export — única entrada pública
|
|
29
|
+
│ └── auth/
|
|
30
|
+
│ └── index.ts
|
|
31
|
+
├── entities/
|
|
32
|
+
│ ├── product/
|
|
33
|
+
│ │ ├── model.ts # Tipos e schemas da entidade
|
|
34
|
+
│ │ └── index.ts
|
|
35
|
+
│ └── user/
|
|
36
|
+
│ └── index.ts
|
|
37
|
+
└── shared/
|
|
38
|
+
├── ui/ # Componentes base (Button, Input, Modal)
|
|
39
|
+
├── lib/ # Utilitários e helpers
|
|
40
|
+
├── api/ # Cliente HTTP base (axios, fetch wrapper)
|
|
41
|
+
└── types/ # Tipos globais compartilhados
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Regras fundamentais
|
|
45
|
+
|
|
46
|
+
### No cross-import entre features
|
|
47
|
+
|
|
48
|
+
Features **não podem importar umas das outras** diretamente:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// ❌ PROIBIDO
|
|
52
|
+
import { useCart } from '../cart'; // feature cart importando de outra feature
|
|
53
|
+
|
|
54
|
+
// ✅ CORRETO — mova o dado compartilhado para entities
|
|
55
|
+
import { Product } from '@/entities/product';
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Se duas features precisam de algo em comum, mova esse algo para `entities` ou `shared`.
|
|
59
|
+
|
|
60
|
+
### Barrel export como contrato público
|
|
61
|
+
|
|
62
|
+
Tudo que sai de uma feature deve passar pelo `index.ts`:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// features/checkout/index.ts
|
|
66
|
+
export { CheckoutPage } from './components/CheckoutPage';
|
|
67
|
+
export { useCheckoutForm } from './hooks/useCheckoutForm';
|
|
68
|
+
export type { CheckoutFormData } from './types';
|
|
69
|
+
// Services e hooks internos NÃO são exportados
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Externos à feature importam **apenas do barrel**:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// ✅ CORRETO
|
|
76
|
+
import { CheckoutPage } from '@/features/checkout';
|
|
77
|
+
|
|
78
|
+
// ❌ PROIBIDO — acesso direto a módulo interno
|
|
79
|
+
import { CheckoutPage } from '@/features/checkout/components/CheckoutPage';
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Pages/App são apenas composição
|
|
83
|
+
|
|
84
|
+
Pages não contêm lógica de negócio — apenas montam features:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// app/checkout/page.tsx
|
|
88
|
+
import { CheckoutPage } from '@/features/checkout';
|
|
89
|
+
import { AuthGuard } from '@/features/auth';
|
|
90
|
+
|
|
91
|
+
export default function Page() {
|
|
92
|
+
return (
|
|
93
|
+
<AuthGuard>
|
|
94
|
+
<CheckoutPage />
|
|
95
|
+
</AuthGuard>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Quando criar uma nova feature
|
|
101
|
+
|
|
102
|
+
Crie uma nova feature quando:
|
|
103
|
+
- A funcionalidade tem um domínio claramente delimitado
|
|
104
|
+
- Possui componentes, hooks e serviços próprios
|
|
105
|
+
- Não é reutilizável por natureza (se for reutilizável, considere `entities` ou `shared`)
|
|
106
|
+
|
|
107
|
+
## Skeleton e guardrails automáticos (profile dixi)
|
|
108
|
+
|
|
109
|
+
`pscode init --profile dixi` cria automaticamente a estrutura feature-sliced com `.gitkeep` nos diretórios folha e `features/README.md` com as convenções documentadas. Também instala `eslint-architecture.mjs` na raiz com regras `no-restricted-imports` para:
|
|
110
|
+
|
|
111
|
+
- Isolar features entre si (features não importam umas das outras)
|
|
112
|
+
- Impedir que páginas importem lógica de negócio diretamente
|
|
113
|
+
|
|
114
|
+
Para ativar as regras no ESLint, adicione ao `eslint.config.js`:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
import architectureRules from './eslint-architecture.mjs';
|
|
118
|
+
export default [...existingConfig, ...architectureRules];
|
|
119
|
+
```
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Convenções de Nomenclatura — React/Next.js
|
|
2
|
+
|
|
3
|
+
## Resumo rápido
|
|
4
|
+
|
|
5
|
+
| Tipo | Convenção | Exemplos |
|
|
6
|
+
|-----------------------|--------------------|----------------------------------------------|
|
|
7
|
+
| Componente React | `PascalCase` | `CheckoutForm`, `UserAvatar`, `ProductCard` |
|
|
8
|
+
| Hook | `use` + PascalCase | `useCheckoutForm`, `useAuth`, `useProductList`|
|
|
9
|
+
| Service / função util | `camelCase` | `submitCheckout`, `formatCurrency`, `parseDate`|
|
|
10
|
+
| Arquivo de componente | `kebab-case.tsx` | `checkout-form.tsx`, `user-avatar.tsx` |
|
|
11
|
+
| Arquivo de hook | `use-*.ts` | `use-checkout-form.ts`, `use-auth.ts` |
|
|
12
|
+
| Arquivo de service | `*-service.ts` | `checkout-service.ts`, `auth-service.ts` |
|
|
13
|
+
| Constante | `SCREAMING_SNAKE` | `MAX_ITEMS`, `API_BASE_URL`, `DEFAULT_TIMEOUT`|
|
|
14
|
+
| Tipo / Interface | `PascalCase` | `CheckoutFormData`, `UserProfile`, `ApiError`|
|
|
15
|
+
| Enum | `PascalCase` (valor `SCREAMING_SNAKE`) | `enum Status { ACTIVE = 'ACTIVE' }` |
|
|
16
|
+
|
|
17
|
+
## Componentes React
|
|
18
|
+
|
|
19
|
+
**PascalCase** — sempre.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// ✅ Correto
|
|
23
|
+
export function CheckoutForm() { ... }
|
|
24
|
+
export const UserAvatar: React.FC<Props> = () => { ... }
|
|
25
|
+
|
|
26
|
+
// ❌ Errado
|
|
27
|
+
export function checkoutForm() { ... }
|
|
28
|
+
export function checkout_form() { ... }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Arquivos de componente em **kebab-case**:
|
|
32
|
+
```
|
|
33
|
+
checkout-form.tsx ✅
|
|
34
|
+
CheckoutForm.tsx ❌ (exceto se a convenção do projeto usar PascalCase em arquivos)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Hooks
|
|
38
|
+
|
|
39
|
+
`use` + PascalCase. O nome deve descrever o que o hook faz ou gerencia.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// ✅ Correto
|
|
43
|
+
function useCheckoutForm() { ... }
|
|
44
|
+
function useProductList(categoryId: string) { ... }
|
|
45
|
+
function useAuth() { ... }
|
|
46
|
+
|
|
47
|
+
// ❌ Errado
|
|
48
|
+
function checkoutFormHook() { ... }
|
|
49
|
+
function UseCheckoutForm() { ... } // PascalCase não é hook
|
|
50
|
+
function useGetProductList() { ... } // evite "get" — já está implícito
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Services (funções de acesso a API ou lógica assíncrona)
|
|
54
|
+
|
|
55
|
+
**camelCase** para a função, arquivo em **kebab-case**:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// checkout-service.ts
|
|
59
|
+
export async function submitCheckout(data: CheckoutData): Promise<OrderResponse> { ... }
|
|
60
|
+
export async function fetchCheckoutSummary(cartId: string): Promise<Summary> { ... }
|
|
61
|
+
|
|
62
|
+
// ❌ Errado
|
|
63
|
+
export async function SubmitCheckout() { ... } // PascalCase é componente
|
|
64
|
+
export async function submit_checkout() { ... } // snake_case não é JS/TS
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Constantes
|
|
68
|
+
|
|
69
|
+
**SCREAMING_SNAKE_CASE** para valores imutáveis de escopo global ou de módulo:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// ✅ Correto
|
|
73
|
+
const MAX_RETRY_ATTEMPTS = 3;
|
|
74
|
+
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
75
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
76
|
+
|
|
77
|
+
// ❌ Errado
|
|
78
|
+
const maxRetryAttempts = 3; // parece variável local mutável
|
|
79
|
+
const MaxRetryAttempts = 3; // PascalCase é componente ou tipo
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Tipos e Interfaces
|
|
83
|
+
|
|
84
|
+
**PascalCase**, com sufixo descritivo quando necessário para clareza:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// ✅ Correto
|
|
88
|
+
type CheckoutFormData = { email: string; items: CartItem[] };
|
|
89
|
+
interface UserProfile { id: string; name: string }
|
|
90
|
+
type ApiError = { code: string; message: string };
|
|
91
|
+
|
|
92
|
+
// Use sufixos quando o nome base é ambíguo
|
|
93
|
+
type CheckoutRequest = { ... }; // DTO de entrada
|
|
94
|
+
type CheckoutResponse = { ... }; // DTO de saída
|
|
95
|
+
type CheckoutState = { ... }; // estado do componente/hook
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Props de componentes
|
|
99
|
+
|
|
100
|
+
Sufixo `Props` no tipo:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
type CheckoutFormProps = {
|
|
104
|
+
onSubmit: (data: CheckoutFormData) => void;
|
|
105
|
+
initialValues?: Partial<CheckoutFormData>;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
function CheckoutForm({ onSubmit, initialValues }: CheckoutFormProps) { ... }
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Handlers de eventos
|
|
112
|
+
|
|
113
|
+
Prefixo `handle` para funções internas; prefixo `on` para props de callback:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// Props (contrato externo)
|
|
117
|
+
type CardProps = {
|
|
118
|
+
onSelect: (id: string) => void; // prefixo "on"
|
|
119
|
+
onDelete: () => void;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Handlers internos
|
|
123
|
+
function ProductCard({ onSelect }: CardProps) {
|
|
124
|
+
function handleClick() { // prefixo "handle"
|
|
125
|
+
onSelect(product.id);
|
|
126
|
+
}
|
|
127
|
+
return <div onClick={handleClick} />;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Testes — React/Next.js
|
|
2
|
+
|
|
3
|
+
## Pirâmide de testes
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
[E2E — Playwright] ← fluxos críticos de usuário
|
|
7
|
+
[Componentes — RTL] ← comportamento visível ao usuário
|
|
8
|
+
[Unitários — Vitest] ← hooks, services, utils (maioria)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Nível 1 — Testes unitários (hooks, services, utils)
|
|
12
|
+
|
|
13
|
+
Testam lógica pura sem renderizar componentes.
|
|
14
|
+
|
|
15
|
+
**Ferramenta:** Vitest
|
|
16
|
+
|
|
17
|
+
**Onde ficam:** ao lado do arquivo testado (`*.test.ts`) ou em `__tests__/`
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// features/checkout/hooks/useCheckoutForm.test.ts
|
|
21
|
+
import { renderHook, act } from '@testing-library/react';
|
|
22
|
+
import { useCheckoutForm } from './useCheckoutForm';
|
|
23
|
+
|
|
24
|
+
describe('useCheckoutForm', () => {
|
|
25
|
+
it('deve inicializar com campos vazios', () => {
|
|
26
|
+
const { result } = renderHook(() => useCheckoutForm());
|
|
27
|
+
expect(result.current.values.email).toBe('');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('deve validar email inválido', async () => {
|
|
31
|
+
const { result } = renderHook(() => useCheckoutForm());
|
|
32
|
+
await act(async () => {
|
|
33
|
+
result.current.setField('email', 'nao-e-email');
|
|
34
|
+
await result.current.validate();
|
|
35
|
+
});
|
|
36
|
+
expect(result.current.errors.email).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// features/checkout/services/checkoutService.test.ts
|
|
43
|
+
import { vi, describe, it, expect } from 'vitest';
|
|
44
|
+
import { submitCheckout } from './checkoutService';
|
|
45
|
+
import { apiClient } from '@/shared/api';
|
|
46
|
+
|
|
47
|
+
vi.mock('@/shared/api');
|
|
48
|
+
|
|
49
|
+
describe('submitCheckout', () => {
|
|
50
|
+
it('deve chamar a API com os dados corretos', async () => {
|
|
51
|
+
const mockPost = vi.mocked(apiClient.post).mockResolvedValue({ data: { orderId: '123' } });
|
|
52
|
+
const result = await submitCheckout({ email: 'test@test.com', items: [] });
|
|
53
|
+
expect(mockPost).toHaveBeenCalledWith('/checkout', expect.objectContaining({ email: 'test@test.com' }));
|
|
54
|
+
expect(result.orderId).toBe('123');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Nível 2 — Testes de componentes (React Testing Library)
|
|
60
|
+
|
|
61
|
+
Testam o comportamento do componente do ponto de vista do usuário, não a implementação.
|
|
62
|
+
|
|
63
|
+
**Ferramenta:** React Testing Library (RTL) + Vitest
|
|
64
|
+
|
|
65
|
+
**Princípio:** Consulte elementos como o usuário os veria (por role, label, texto — não por classe CSS ou estrutura interna)
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// features/checkout/components/CheckoutForm.test.tsx
|
|
69
|
+
import { render, screen, userEvent } from '@testing-library/react';
|
|
70
|
+
import { CheckoutForm } from './CheckoutForm';
|
|
71
|
+
|
|
72
|
+
describe('CheckoutForm', () => {
|
|
73
|
+
it('deve exibir erro quando email está vazio e o form é enviado', async () => {
|
|
74
|
+
render(<CheckoutForm onSubmit={vi.fn()} />);
|
|
75
|
+
|
|
76
|
+
await userEvent.click(screen.getByRole('button', { name: /confirmar/i }));
|
|
77
|
+
|
|
78
|
+
expect(screen.getByText(/email é obrigatório/i)).toBeInTheDocument();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('deve chamar onSubmit com dados corretos quando form é válido', async () => {
|
|
82
|
+
const onSubmit = vi.fn();
|
|
83
|
+
render(<CheckoutForm onSubmit={onSubmit} />);
|
|
84
|
+
|
|
85
|
+
await userEvent.type(screen.getByLabelText(/e-mail/i), 'user@example.com');
|
|
86
|
+
await userEvent.click(screen.getByRole('button', { name: /confirmar/i }));
|
|
87
|
+
|
|
88
|
+
expect(onSubmit).toHaveBeenCalledWith(expect.objectContaining({ email: 'user@example.com' }));
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Nível 3 — Testes E2E (Playwright)
|
|
94
|
+
|
|
95
|
+
Testam fluxos críticos de ponta a ponta no browser real.
|
|
96
|
+
|
|
97
|
+
**Ferramenta:** Playwright
|
|
98
|
+
|
|
99
|
+
**Onde ficam:** `e2e/` na raiz do projeto
|
|
100
|
+
|
|
101
|
+
**Escopo:** apenas fluxos críticos de negócio (login, checkout, fluxo principal)
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// e2e/checkout.spec.ts
|
|
105
|
+
import { test, expect } from '@playwright/test';
|
|
106
|
+
|
|
107
|
+
test('usuário consegue completar o checkout', async ({ page }) => {
|
|
108
|
+
await page.goto('/checkout');
|
|
109
|
+
await page.getByLabel('E-mail').fill('user@example.com');
|
|
110
|
+
await page.getByRole('button', { name: 'Confirmar pedido' }).click();
|
|
111
|
+
|
|
112
|
+
await expect(page.getByText('Pedido confirmado!')).toBeVisible();
|
|
113
|
+
await expect(page).toHaveURL(/\/confirmacao/);
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Configuração
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// vitest.config.ts
|
|
121
|
+
import { defineConfig } from 'vitest/config';
|
|
122
|
+
import react from '@vitejs/plugin-react';
|
|
123
|
+
|
|
124
|
+
export default defineConfig({
|
|
125
|
+
plugins: [react()],
|
|
126
|
+
test: {
|
|
127
|
+
environment: 'jsdom',
|
|
128
|
+
setupFiles: ['./src/test/setup.ts'],
|
|
129
|
+
coverage: {
|
|
130
|
+
reporter: ['text', 'lcov'],
|
|
131
|
+
exclude: ['**/*.test.*', 'e2e/**', 'src/test/**'],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## O que NÃO testar
|
|
138
|
+
|
|
139
|
+
- Lógica de estilo (CSS, classes) — use Storybook para isso
|
|
140
|
+
- Detalhes de implementação interna (state do componente, refs)
|
|
141
|
+
- Snapshots de componentes complexos — ficam desatualizados rapidamente
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Convenção de Commits
|
|
2
|
+
|
|
3
|
+
## Formato obrigatório
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
tipo(escopo): descrição imperativa [PROJ-123]
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
**Exemplos válidos:**
|
|
10
|
+
```
|
|
11
|
+
feat(pagamento): adicionar integração com gateway PIX [PAY-456]
|
|
12
|
+
fix(auth): corrigir expiração de token JWT [AUTH-789]
|
|
13
|
+
refactor(pedido): extrair lógica de cálculo de frete [ORD-321]
|
|
14
|
+
test(usuario): adicionar testes de integração para cadastro [USR-100]
|
|
15
|
+
docs(readme): atualizar instruções de setup local
|
|
16
|
+
chore(deps): atualizar Spring Boot para 3.2.0
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Tipos válidos
|
|
20
|
+
|
|
21
|
+
| Tipo | Quando usar |
|
|
22
|
+
|------------|----------------------------------------------------|
|
|
23
|
+
| `feat` | Nova funcionalidade |
|
|
24
|
+
| `fix` | Correção de bug |
|
|
25
|
+
| `refactor` | Mudança de código sem alterar comportamento externo|
|
|
26
|
+
| `test` | Adição ou modificação de testes |
|
|
27
|
+
| `docs` | Documentação apenas |
|
|
28
|
+
| `chore` | Tarefas de manutenção, deps, build, CI |
|
|
29
|
+
|
|
30
|
+
## Regras de escopo
|
|
31
|
+
|
|
32
|
+
O escopo deve corresponder ao módulo, bounded context ou feature afetada.
|
|
33
|
+
|
|
34
|
+
- Java/Spring: nome do bounded context ou pacote de domínio (ex: `pagamento`, `pedido`, `usuario`)
|
|
35
|
+
- React/Next.js: nome da feature ou camada (ex: `checkout`, `auth`, `shared`)
|
|
36
|
+
|
|
37
|
+
## Ticket JIRA obrigatório
|
|
38
|
+
|
|
39
|
+
- **Obrigatório** em todos os commits **exceto** `docs` e `chore`
|
|
40
|
+
- Formato: `[PROJ-NNN]` ao final da primeira linha
|
|
41
|
+
- O ticket deve estar em estado "Em Desenvolvimento" ou "Em Revisão" antes do commit
|
|
42
|
+
|
|
43
|
+
## Mensagem no imperativo
|
|
44
|
+
|
|
45
|
+
Use verbos no imperativo: "adicionar", "corrigir", "remover", "atualizar" (não "adicionado", "corrigido").
|
|
46
|
+
|
|
47
|
+
A mensagem deve completar a frase: *"Se aplicado, este commit vai [mensagem]"*
|