maestro-bundle 1.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 +91 -0
- package/package.json +25 -0
- package/src/cli.mjs +212 -0
- package/templates/bundle-ai-agents/.spec/constitution.md +33 -0
- package/templates/bundle-ai-agents/AGENTS.md +140 -0
- package/templates/bundle-ai-agents/skills/agent-orchestration/SKILL.md +132 -0
- package/templates/bundle-ai-agents/skills/api-design/SKILL.md +100 -0
- package/templates/bundle-ai-agents/skills/clean-architecture/SKILL.md +99 -0
- package/templates/bundle-ai-agents/skills/context-engineering/SKILL.md +98 -0
- package/templates/bundle-ai-agents/skills/database-modeling/SKILL.md +59 -0
- package/templates/bundle-ai-agents/skills/docker-containerization/SKILL.md +114 -0
- package/templates/bundle-ai-agents/skills/eval-testing/SKILL.md +115 -0
- package/templates/bundle-ai-agents/skills/memory-management/SKILL.md +106 -0
- package/templates/bundle-ai-agents/skills/prompt-engineering/SKILL.md +66 -0
- package/templates/bundle-ai-agents/skills/rag-pipeline/SKILL.md +128 -0
- package/templates/bundle-ai-agents/skills/testing-strategy/SKILL.md +95 -0
- package/templates/bundle-base/AGENTS.md +118 -0
- package/templates/bundle-base/skills/branch-strategy/SKILL.md +42 -0
- package/templates/bundle-base/skills/code-review/SKILL.md +54 -0
- package/templates/bundle-base/skills/commit-pattern/SKILL.md +58 -0
- package/templates/bundle-data-pipeline/.spec/constitution.md +32 -0
- package/templates/bundle-data-pipeline/AGENTS.md +115 -0
- package/templates/bundle-data-pipeline/skills/data-preprocessing/SKILL.md +75 -0
- package/templates/bundle-data-pipeline/skills/docker-containerization/SKILL.md +114 -0
- package/templates/bundle-data-pipeline/skills/feature-engineering/SKILL.md +76 -0
- package/templates/bundle-data-pipeline/skills/mlops-pipeline/SKILL.md +77 -0
- package/templates/bundle-data-pipeline/skills/model-training/SKILL.md +68 -0
- package/templates/bundle-data-pipeline/skills/rag-pipeline/SKILL.md +128 -0
- package/templates/bundle-frontend-spa/.spec/constitution.md +32 -0
- package/templates/bundle-frontend-spa/AGENTS.md +107 -0
- package/templates/bundle-frontend-spa/skills/authentication/SKILL.md +90 -0
- package/templates/bundle-frontend-spa/skills/component-design/SKILL.md +115 -0
- package/templates/bundle-frontend-spa/skills/e2e-testing/SKILL.md +101 -0
- package/templates/bundle-frontend-spa/skills/integration-api/SKILL.md +95 -0
- package/templates/bundle-frontend-spa/skills/react-patterns/SKILL.md +130 -0
- package/templates/bundle-frontend-spa/skills/responsive-layout/SKILL.md +65 -0
- package/templates/bundle-frontend-spa/skills/state-management/SKILL.md +86 -0
- package/templates/bundle-jhipster-microservices/.spec/constitution.md +37 -0
- package/templates/bundle-jhipster-microservices/AGENTS.md +307 -0
- package/templates/bundle-jhipster-microservices/skills/ci-cd-pipeline/SKILL.md +112 -0
- package/templates/bundle-jhipster-microservices/skills/clean-architecture/SKILL.md +99 -0
- package/templates/bundle-jhipster-microservices/skills/ddd-tactical/SKILL.md +138 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-angular/SKILL.md +97 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-docker-k8s/SKILL.md +183 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-entities/SKILL.md +87 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-gateway/SKILL.md +96 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-kafka/SKILL.md +145 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-registry/SKILL.md +83 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-service/SKILL.md +131 -0
- package/templates/bundle-jhipster-microservices/skills/testing-strategy/SKILL.md +95 -0
- package/templates/bundle-jhipster-monorepo/.spec/constitution.md +32 -0
- package/templates/bundle-jhipster-monorepo/AGENTS.md +227 -0
- package/templates/bundle-jhipster-monorepo/skills/clean-architecture/SKILL.md +99 -0
- package/templates/bundle-jhipster-monorepo/skills/ddd-tactical/SKILL.md +138 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-angular/SKILL.md +166 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-entities/SKILL.md +141 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-liquibase/SKILL.md +95 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-security/SKILL.md +89 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-spring/SKILL.md +155 -0
- package/templates/bundle-jhipster-monorepo/skills/testing-strategy/SKILL.md +95 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jhipster-kafka
|
|
3
|
+
description: Configurar Apache Kafka no JHipster para comunicação assíncrona entre microsserviços com eventos de domínio. Use quando precisar de messaging, eventos entre serviços, ou processamento assíncrono.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Kafka no JHipster Microservices
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
```yaml
|
|
11
|
+
# docker-compose/kafka.yml
|
|
12
|
+
services:
|
|
13
|
+
zookeeper:
|
|
14
|
+
image: confluentinc/cp-zookeeper:7.5.0
|
|
15
|
+
environment:
|
|
16
|
+
ZOOKEEPER_CLIENT_PORT: 2181
|
|
17
|
+
|
|
18
|
+
kafka:
|
|
19
|
+
image: confluentinc/cp-kafka:7.5.0
|
|
20
|
+
depends_on:
|
|
21
|
+
- zookeeper
|
|
22
|
+
ports:
|
|
23
|
+
- "9092:9092"
|
|
24
|
+
environment:
|
|
25
|
+
KAFKA_BROKER_ID: 1
|
|
26
|
+
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
|
27
|
+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
|
|
28
|
+
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
|
|
29
|
+
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Tópicos por domínio
|
|
33
|
+
|
|
34
|
+
| Tópico | Producer | Consumer | Evento |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| `demand-events` | demand-service | agent-service, tracking-service | DemandCreated, DemandDecomposed |
|
|
37
|
+
| `agent-events` | agent-service | demand-service, tracking-service | AgentAllocated, TaskCompleted |
|
|
38
|
+
| `bundle-events` | bundle-service | agent-service | BundleUpdated, SkillAdded |
|
|
39
|
+
| `tracking-events` | tracking-service | gateway (WebSocket) | MetricsUpdated |
|
|
40
|
+
|
|
41
|
+
## Eventos de domínio
|
|
42
|
+
|
|
43
|
+
```java
|
|
44
|
+
// Evento base
|
|
45
|
+
public sealed interface DomainEvent permits DemandCreated, DemandDecomposed, TaskCompleted {
|
|
46
|
+
String eventId();
|
|
47
|
+
Instant occurredAt();
|
|
48
|
+
String aggregateId();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Eventos específicos
|
|
52
|
+
public record DemandCreated(
|
|
53
|
+
String eventId,
|
|
54
|
+
Instant occurredAt,
|
|
55
|
+
String aggregateId,
|
|
56
|
+
String description,
|
|
57
|
+
String priority
|
|
58
|
+
) implements DomainEvent {}
|
|
59
|
+
|
|
60
|
+
public record TaskCompleted(
|
|
61
|
+
String eventId,
|
|
62
|
+
Instant occurredAt,
|
|
63
|
+
String aggregateId,
|
|
64
|
+
Long taskId,
|
|
65
|
+
Long agentId,
|
|
66
|
+
String branchName,
|
|
67
|
+
double durationSeconds
|
|
68
|
+
) implements DomainEvent {}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Producer
|
|
72
|
+
|
|
73
|
+
```java
|
|
74
|
+
@Service
|
|
75
|
+
@RequiredArgsConstructor
|
|
76
|
+
public class DemandEventProducer {
|
|
77
|
+
|
|
78
|
+
private final KafkaTemplate<String, DomainEvent> kafkaTemplate;
|
|
79
|
+
|
|
80
|
+
public void publish(DomainEvent event) {
|
|
81
|
+
kafkaTemplate.send("demand-events", event.aggregateId(), event)
|
|
82
|
+
.whenComplete((result, ex) -> {
|
|
83
|
+
if (ex != null) {
|
|
84
|
+
log.error("Failed to publish event {}", event.eventId(), ex);
|
|
85
|
+
} else {
|
|
86
|
+
log.info("Published {} to partition {}", event.eventId(),
|
|
87
|
+
result.getRecordMetadata().partition());
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Consumer com idempotência
|
|
95
|
+
|
|
96
|
+
```java
|
|
97
|
+
@Service
|
|
98
|
+
@RequiredArgsConstructor
|
|
99
|
+
public class DemandEventConsumer {
|
|
100
|
+
|
|
101
|
+
private final ProcessedEventRepository processedEventRepo;
|
|
102
|
+
private final AgentAllocationService allocationService;
|
|
103
|
+
|
|
104
|
+
@KafkaListener(topics = "demand-events", groupId = "agent-service")
|
|
105
|
+
public void onDemandEvent(DomainEvent event) {
|
|
106
|
+
// Idempotência: ignorar evento já processado
|
|
107
|
+
if (processedEventRepo.existsById(event.eventId())) {
|
|
108
|
+
log.info("Event {} already processed, skipping", event.eventId());
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
switch (event) {
|
|
113
|
+
case DemandDecomposed e -> allocationService.allocateForDemand(e);
|
|
114
|
+
case DemandCreated e -> log.info("Demand created: {}", e.aggregateId());
|
|
115
|
+
default -> log.warn("Unknown event type: {}", event.getClass().getSimpleName());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
processedEventRepo.save(new ProcessedEvent(event.eventId(), Instant.now()));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Dead Letter Queue
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
spring:
|
|
127
|
+
cloud:
|
|
128
|
+
stream:
|
|
129
|
+
kafka:
|
|
130
|
+
bindings:
|
|
131
|
+
demandEvents-in-0:
|
|
132
|
+
consumer:
|
|
133
|
+
enableDlq: true
|
|
134
|
+
dlqName: demand-events-dlq
|
|
135
|
+
autoCommitOnError: false
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Regras
|
|
139
|
+
|
|
140
|
+
- Um tópico por bounded context (não por entidade)
|
|
141
|
+
- Consumers DEVEM ser idempotentes
|
|
142
|
+
- DLQ para eventos que falharam
|
|
143
|
+
- Events são imutáveis (records)
|
|
144
|
+
- Incluir `eventId` e `occurredAt` em todo evento
|
|
145
|
+
- Consumer group = nome do serviço
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jhipster-registry
|
|
3
|
+
description: Configurar service discovery com JHipster Registry (Consul ou Eureka) e configuração centralizada. Use quando precisar registrar serviços, configurar discovery, ou centralizar configurações.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JHipster Registry / Service Discovery
|
|
7
|
+
|
|
8
|
+
## Consul (recomendado)
|
|
9
|
+
|
|
10
|
+
### Docker Compose
|
|
11
|
+
```yaml
|
|
12
|
+
consul:
|
|
13
|
+
image: consul:1.15
|
|
14
|
+
ports:
|
|
15
|
+
- "8500:8500"
|
|
16
|
+
command: agent -server -bootstrap -ui -client=0.0.0.0
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Configuração no serviço
|
|
20
|
+
```yaml
|
|
21
|
+
# application.yml de cada microsserviço
|
|
22
|
+
spring:
|
|
23
|
+
cloud:
|
|
24
|
+
consul:
|
|
25
|
+
host: consul
|
|
26
|
+
port: 8500
|
|
27
|
+
discovery:
|
|
28
|
+
service-name: ${spring.application.name}
|
|
29
|
+
health-check-path: /management/health
|
|
30
|
+
health-check-interval: 15s
|
|
31
|
+
instance-id: ${spring.application.name}:${random.value}
|
|
32
|
+
config:
|
|
33
|
+
enabled: true
|
|
34
|
+
format: yaml
|
|
35
|
+
default-context: application
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Configuração centralizada no Consul
|
|
39
|
+
|
|
40
|
+
Colocar configs compartilhadas no Consul KV store:
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
# config/application/data no Consul KV
|
|
44
|
+
spring:
|
|
45
|
+
datasource:
|
|
46
|
+
type: com.zaxxer.hikari.HikariDataSource
|
|
47
|
+
hikari:
|
|
48
|
+
maximum-pool-size: 20
|
|
49
|
+
|
|
50
|
+
management:
|
|
51
|
+
endpoints:
|
|
52
|
+
web:
|
|
53
|
+
exposure:
|
|
54
|
+
include: health,info,prometheus
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Service-to-Service communication
|
|
58
|
+
|
|
59
|
+
Cada serviço se registra automaticamente. Para chamar outro serviço:
|
|
60
|
+
|
|
61
|
+
```java
|
|
62
|
+
// Feign Client com discovery
|
|
63
|
+
@FeignClient(name = "agent-service")
|
|
64
|
+
public interface AgentServiceClient {
|
|
65
|
+
@GetMapping("/api/agents/{id}")
|
|
66
|
+
AgentDTO getAgent(@PathVariable Long id);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// O nome "agent-service" é resolvido via Consul/Eureka
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Health checks
|
|
73
|
+
|
|
74
|
+
- Consul verifica `/management/health` de cada serviço
|
|
75
|
+
- Se um serviço ficar unhealthy, Consul remove do registry
|
|
76
|
+
- Gateway para de rotear para instâncias unhealthy automaticamente
|
|
77
|
+
|
|
78
|
+
## Regras
|
|
79
|
+
|
|
80
|
+
- Todo serviço DEVE se registrar no discovery
|
|
81
|
+
- Health check endpoint DEVE responder em < 1s
|
|
82
|
+
- Usar `instance-id` com random para permitir múltiplas instâncias
|
|
83
|
+
- Configs compartilhadas no Consul KV, específicas no application.yml local
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jhipster-service
|
|
3
|
+
description: Criar e configurar um microsserviço JHipster com Spring Boot, incluindo API, banco próprio e comunicação inter-serviço. Use quando precisar criar um novo microsserviço, configurar Feign clients, ou definir APIs internas.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JHipster Microservice
|
|
7
|
+
|
|
8
|
+
## Criar novo serviço
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Gerar via JDL (recomendado)
|
|
12
|
+
jhipster import-jdl jhipster-jdl.jdl
|
|
13
|
+
|
|
14
|
+
# Ou gerar manualmente
|
|
15
|
+
mkdir bundle-service && cd bundle-service
|
|
16
|
+
jhipster --blueprints="" --skip-client
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Configuração do serviço
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
# application.yml
|
|
23
|
+
spring:
|
|
24
|
+
application:
|
|
25
|
+
name: bundle-service
|
|
26
|
+
server:
|
|
27
|
+
port: 8084
|
|
28
|
+
|
|
29
|
+
# Banco próprio (database per service)
|
|
30
|
+
spring:
|
|
31
|
+
datasource:
|
|
32
|
+
url: jdbc:postgresql://localhost:5432/bundle_service
|
|
33
|
+
username: bundle
|
|
34
|
+
password: bundle
|
|
35
|
+
|
|
36
|
+
# Kafka
|
|
37
|
+
spring:
|
|
38
|
+
cloud:
|
|
39
|
+
stream:
|
|
40
|
+
kafka:
|
|
41
|
+
binder:
|
|
42
|
+
brokers: kafka:9092
|
|
43
|
+
bindings:
|
|
44
|
+
bundleEvents-out-0:
|
|
45
|
+
destination: bundle-events
|
|
46
|
+
demandEvents-in-0:
|
|
47
|
+
destination: demand-events
|
|
48
|
+
group: bundle-service
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Comunicação Síncrona — Feign
|
|
52
|
+
|
|
53
|
+
```java
|
|
54
|
+
@FeignClient(name = "demand-service", fallback = DemandServiceFallback.class)
|
|
55
|
+
public interface DemandServiceClient {
|
|
56
|
+
|
|
57
|
+
@GetMapping("/api/demands/{id}")
|
|
58
|
+
DemandDTO getDemand(@PathVariable Long id);
|
|
59
|
+
|
|
60
|
+
@PutMapping("/api/demands/{id}/status")
|
|
61
|
+
void updateStatus(@PathVariable Long id, @RequestBody StatusUpdateDTO dto);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Component
|
|
65
|
+
public class DemandServiceFallback implements DemandServiceClient {
|
|
66
|
+
|
|
67
|
+
@Override
|
|
68
|
+
public DemandDTO getDemand(Long id) {
|
|
69
|
+
log.warn("Demand service unavailable for demand {}", id);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Override
|
|
74
|
+
public void updateStatus(Long id, StatusUpdateDTO dto) {
|
|
75
|
+
log.warn("Cannot update demand {} status, service unavailable", id);
|
|
76
|
+
throw new ServiceUnavailableException("demand-service");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Comunicação Assíncrona — Kafka
|
|
82
|
+
|
|
83
|
+
```java
|
|
84
|
+
// Producer
|
|
85
|
+
@Service
|
|
86
|
+
public class BundleEventProducer {
|
|
87
|
+
private final StreamBridge streamBridge;
|
|
88
|
+
|
|
89
|
+
public void publishBundleUpdated(Bundle bundle) {
|
|
90
|
+
BundleUpdatedEvent event = new BundleUpdatedEvent(
|
|
91
|
+
bundle.getId(), bundle.getName(), bundle.getVersion()
|
|
92
|
+
);
|
|
93
|
+
streamBridge.send("bundleEvents-out-0", event);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Consumer
|
|
98
|
+
@Bean
|
|
99
|
+
public Consumer<DemandCreatedEvent> demandEvents() {
|
|
100
|
+
return event -> {
|
|
101
|
+
log.info("Demand created: {}", event.demandId());
|
|
102
|
+
bundleService.suggestBundleForDemand(event.demandId(), event.description());
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Padrão de API interna
|
|
108
|
+
|
|
109
|
+
APIs entre serviços usam DTOs compartilhados (nunca entities):
|
|
110
|
+
|
|
111
|
+
```java
|
|
112
|
+
// Shared DTO — package separado ou duplicado (não compartilhar JARs)
|
|
113
|
+
public record AgentAllocationRequest(
|
|
114
|
+
Long taskId,
|
|
115
|
+
AgentType requiredType,
|
|
116
|
+
String branchName
|
|
117
|
+
) {}
|
|
118
|
+
|
|
119
|
+
public record AgentAllocationResponse(
|
|
120
|
+
Long agentId,
|
|
121
|
+
String worktreePath,
|
|
122
|
+
String status
|
|
123
|
+
) {}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Regras
|
|
127
|
+
|
|
128
|
+
- Cada serviço tem seu próprio banco — NUNCA acessar banco de outro
|
|
129
|
+
- Falha de um serviço NÃO pode derrubar outros (circuit breaker)
|
|
130
|
+
- Eventos Kafka para notificação, Feign para query
|
|
131
|
+
- Idempotência em consumers Kafka (mesmo evento pode chegar 2x)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing-strategy
|
|
3
|
+
description: Implementar estratégia de testes com unitários, integração e e2e usando Pytest ou JUnit. Use quando for escrever testes, definir estratégia de testes, ou melhorar cobertura.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Estratégia de Testes
|
|
7
|
+
|
|
8
|
+
## Pirâmide
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
/ E2E \ Poucos, lentos, caros
|
|
12
|
+
/ Integr. \ Moderados
|
|
13
|
+
/ Unitários \ Muitos, rápidos, baratos
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Testes Unitários — Domínio
|
|
17
|
+
|
|
18
|
+
Testar regras de negócio sem infraestrutura.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
# tests/domain/test_demand.py
|
|
22
|
+
class TestDemand:
|
|
23
|
+
def test_should_decompose_new_demand(self):
|
|
24
|
+
demand = Demand(id=DemandId.generate(), description="Criar CRUD")
|
|
25
|
+
planner = FakePlanner(tasks=[Task(...), Task(...)])
|
|
26
|
+
|
|
27
|
+
tasks = demand.decompose(planner)
|
|
28
|
+
|
|
29
|
+
assert len(tasks) == 2
|
|
30
|
+
assert demand.status == DemandStatus.PLANNED
|
|
31
|
+
|
|
32
|
+
def test_should_reject_decompose_if_already_planned(self):
|
|
33
|
+
demand = Demand(id=DemandId.generate(), description="Criar CRUD")
|
|
34
|
+
demand.decompose(FakePlanner(tasks=[Task(...)]))
|
|
35
|
+
|
|
36
|
+
with pytest.raises(DemandAlreadyDecomposedException):
|
|
37
|
+
demand.decompose(FakePlanner(tasks=[]))
|
|
38
|
+
|
|
39
|
+
def test_should_not_allow_more_than_20_tasks(self):
|
|
40
|
+
demand = Demand(id=DemandId.generate(), description="Mega projeto")
|
|
41
|
+
for i in range(20):
|
|
42
|
+
demand.add_task(Task(...))
|
|
43
|
+
|
|
44
|
+
with pytest.raises(TooManyTasksException):
|
|
45
|
+
demand.add_task(Task(...))
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Testes Unitários — Value Objects
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
class TestComplianceScore:
|
|
52
|
+
def test_passing_score(self):
|
|
53
|
+
score = ComplianceScore(85.0)
|
|
54
|
+
assert score.is_passing() is True
|
|
55
|
+
|
|
56
|
+
def test_failing_score(self):
|
|
57
|
+
score = ComplianceScore(60.0)
|
|
58
|
+
assert score.is_passing() is False
|
|
59
|
+
|
|
60
|
+
def test_invalid_score_raises(self):
|
|
61
|
+
with pytest.raises(ValueError):
|
|
62
|
+
ComplianceScore(150.0)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Testes de Integração — Repositórios
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# tests/infrastructure/test_pg_demand_repository.py
|
|
69
|
+
@pytest.fixture
|
|
70
|
+
def db_session():
|
|
71
|
+
engine = create_engine(TEST_DATABASE_URL)
|
|
72
|
+
with Session(engine) as session:
|
|
73
|
+
yield session
|
|
74
|
+
session.rollback()
|
|
75
|
+
|
|
76
|
+
class TestPgDemandRepository:
|
|
77
|
+
def test_should_save_and_find_demand(self, db_session):
|
|
78
|
+
repo = PgDemandRepository(db_session)
|
|
79
|
+
demand = Demand(id=DemandId.generate(), description="Test")
|
|
80
|
+
|
|
81
|
+
repo.save(demand)
|
|
82
|
+
found = repo.find_by_id(demand.id)
|
|
83
|
+
|
|
84
|
+
assert found.description == "Test"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Naming
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
test_should_<resultado>_when_<condição>
|
|
91
|
+
|
|
92
|
+
test_should_return_error_when_email_is_invalid
|
|
93
|
+
test_should_decompose_demand_when_status_is_created
|
|
94
|
+
test_should_reject_merge_when_conflicts_exist
|
|
95
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Constitution — Projeto JHipster Monorepo
|
|
2
|
+
|
|
3
|
+
## Princípios
|
|
4
|
+
|
|
5
|
+
1. **Spec primeiro, código depois** — Toda demanda passa pelo fluxo SDD antes de implementação
|
|
6
|
+
2. **JDL como fonte de verdade** — Modelo de entidades definido em JDL, código gerado pelo JHipster
|
|
7
|
+
3. **Enriquecer, não substituir** — Entidades geradas pelo JHipster são enriquecidas com DDD, não reescritas do zero
|
|
8
|
+
4. **Migrations versionadas** — Toda mudança de schema via Liquibase changeset, nunca manual
|
|
9
|
+
5. **DTO na fronteira** — Nunca expor entidade JPA na API REST
|
|
10
|
+
|
|
11
|
+
## Padrões de desenvolvimento
|
|
12
|
+
|
|
13
|
+
### Java / Spring Boot
|
|
14
|
+
- Java 21+, Records para DTOs
|
|
15
|
+
- Constructor injection (nunca @Autowired em campo)
|
|
16
|
+
- @Transactional apenas no Service
|
|
17
|
+
- Entidades com comportamento rico (não anêmicas)
|
|
18
|
+
- MapStruct para conversão Entity ↔ DTO
|
|
19
|
+
|
|
20
|
+
### Angular
|
|
21
|
+
- Standalone components (Angular 17+)
|
|
22
|
+
- Lazy loading por rota
|
|
23
|
+
- Reactive Forms com validação
|
|
24
|
+
- Services para chamadas HTTP
|
|
25
|
+
|
|
26
|
+
## Padrões de qualidade
|
|
27
|
+
|
|
28
|
+
- Cobertura mínima: 80%
|
|
29
|
+
- JUnit 5 + Mockito para backend
|
|
30
|
+
- Jest + Cypress para frontend
|
|
31
|
+
- Code review obrigatório
|
|
32
|
+
- Commits seguem Conventional Commits
|