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,227 @@
|
|
|
1
|
+
# Projeto: JHipster Monorepo
|
|
2
|
+
|
|
3
|
+
Você está construindo uma aplicação monolítica com JHipster. Backend em Java/Spring Boot, frontend em Angular, banco PostgreSQL, tudo em um único repositório.
|
|
4
|
+
|
|
5
|
+
## Specification-Driven Development (SDD)
|
|
6
|
+
|
|
7
|
+
Este projeto usa **GitHub Spec Kit** para governança. Antes de implementar qualquer demanda:
|
|
8
|
+
|
|
9
|
+
1. Rodar `/speckit.constitution` — se `.spec/constitution.md` não existir
|
|
10
|
+
2. Rodar `/speckit.specify` — descrever O QUE e POR QUÊ (não como)
|
|
11
|
+
3. Rodar `/speckit.plan` — arquitetura e decisões técnicas
|
|
12
|
+
4. Rodar `/speckit.tasks` — quebrar em tasks atômicas
|
|
13
|
+
5. Rodar `/speckit.implement` — executar as tasks
|
|
14
|
+
|
|
15
|
+
Nunca pular direto para código. Spec primeiro, código depois.
|
|
16
|
+
|
|
17
|
+
## References
|
|
18
|
+
|
|
19
|
+
Documentos de referência que o agente deve consultar quando necessário:
|
|
20
|
+
|
|
21
|
+
- `references/jhipster-jdl-guide.md` — Guia completo de JDL
|
|
22
|
+
- `references/spring-boot-patterns.md` — Padrões Spring Boot
|
|
23
|
+
- `references/angular-patterns.md` — Padrões Angular no JHipster
|
|
24
|
+
- `references/liquibase-guide.md` — Guia de migrations Liquibase
|
|
25
|
+
|
|
26
|
+
## Stack do projeto
|
|
27
|
+
|
|
28
|
+
- **Backend:** Java 21 + Spring Boot 3.x (gerado pelo JHipster)
|
|
29
|
+
- **Frontend:** Angular 17+ com TypeScript
|
|
30
|
+
- **Banco:** PostgreSQL
|
|
31
|
+
- **Cache:** Ehcache ou Redis
|
|
32
|
+
- **Migrations:** Liquibase (padrão JHipster)
|
|
33
|
+
- **Auth:** JWT (padrão JHipster) ou OAuth2/Keycloak
|
|
34
|
+
- **Testes:** JUnit 5 + Mockito (back), Jest + Cypress (front)
|
|
35
|
+
- **Build:** Maven ou Gradle
|
|
36
|
+
- **API docs:** Swagger/OpenAPI (auto-gerado)
|
|
37
|
+
|
|
38
|
+
## Estrutura JHipster Monorepo
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
project/
|
|
42
|
+
├── src/
|
|
43
|
+
│ ├── main/
|
|
44
|
+
│ │ ├── java/com/empresa/projeto/
|
|
45
|
+
│ │ │ ├── domain/ # Entidades JPA (enriquecer com DDD)
|
|
46
|
+
│ │ │ │ ├── Demand.java
|
|
47
|
+
│ │ │ │ ├── Task.java
|
|
48
|
+
│ │ │ │ └── enumeration/
|
|
49
|
+
│ │ │ ├── repository/ # Spring Data JPA repositories
|
|
50
|
+
│ │ │ ├── service/ # Services (use cases)
|
|
51
|
+
│ │ │ │ ├── dto/ # DTOs
|
|
52
|
+
│ │ │ │ ├── mapper/ # MapStruct mappers
|
|
53
|
+
│ │ │ │ └── impl/
|
|
54
|
+
│ │ │ ├── web/rest/ # REST controllers
|
|
55
|
+
│ │ │ ├── config/ # Spring configs
|
|
56
|
+
│ │ │ └── security/ # Security configs
|
|
57
|
+
│ │ ├── resources/
|
|
58
|
+
│ │ │ ├── config/
|
|
59
|
+
│ │ │ │ ├── application.yml
|
|
60
|
+
│ │ │ │ ├── application-dev.yml
|
|
61
|
+
│ │ │ │ └── application-prod.yml
|
|
62
|
+
│ │ │ └── config/liquibase/ # Migrations
|
|
63
|
+
│ │ └── webapp/ # Angular app
|
|
64
|
+
│ │ ├── app/
|
|
65
|
+
│ │ │ ├── entities/ # Entity CRUD modules
|
|
66
|
+
│ │ │ ├── shared/ # Shared components
|
|
67
|
+
│ │ │ ├── core/ # Core services
|
|
68
|
+
│ │ │ └── layouts/ # Page layouts
|
|
69
|
+
│ │ └── content/ # Assets
|
|
70
|
+
│ └── test/
|
|
71
|
+
│ ├── java/ # JUnit tests
|
|
72
|
+
│ └── javascript/ # Jest tests
|
|
73
|
+
├── .jhipster/ # JDL entity definitions
|
|
74
|
+
│ ├── Demand.json
|
|
75
|
+
│ └── Task.json
|
|
76
|
+
├── jhipster-jdl.jdl # JDL model file
|
|
77
|
+
├── pom.xml # Maven
|
|
78
|
+
└── package.json # Frontend deps
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Padrões de código
|
|
82
|
+
|
|
83
|
+
### Java
|
|
84
|
+
- Máximo 500 linhas por classe
|
|
85
|
+
- Records para DTOs imutáveis (Java 21)
|
|
86
|
+
- Optional ao invés de null returns
|
|
87
|
+
- Streams API para coleções (não loops imperativos)
|
|
88
|
+
- Google Java Style Guide
|
|
89
|
+
- Lombok apenas para: `@Slf4j`, `@RequiredArgsConstructor`. Evitar `@Data`.
|
|
90
|
+
- Nunca `@Autowired` em campo — usar constructor injection
|
|
91
|
+
|
|
92
|
+
### Angular
|
|
93
|
+
- Strict mode habilitado
|
|
94
|
+
- Standalone components (Angular 17+)
|
|
95
|
+
- Signals para estado reativo quando possível
|
|
96
|
+
- Lazy loading por rota/feature
|
|
97
|
+
- Reactive Forms com validação
|
|
98
|
+
|
|
99
|
+
## JDL — Modelo de entidades
|
|
100
|
+
|
|
101
|
+
Usar JDL para definir entidades e gerar código:
|
|
102
|
+
|
|
103
|
+
```jdl
|
|
104
|
+
entity Demand {
|
|
105
|
+
description TextBlob required
|
|
106
|
+
status DemandStatus required
|
|
107
|
+
priority Priority required
|
|
108
|
+
createdAt Instant required
|
|
109
|
+
completedAt Instant
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
entity Task {
|
|
113
|
+
description String required maxlength(500)
|
|
114
|
+
status TaskStatus required
|
|
115
|
+
branchName String maxlength(100)
|
|
116
|
+
startedAt Instant
|
|
117
|
+
completedAt Instant
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
enum DemandStatus {
|
|
121
|
+
CREATED, PLANNED, IN_PROGRESS, COMPLETED, CANCELLED
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
enum TaskStatus {
|
|
125
|
+
PENDING, IN_PROGRESS, COMPLETED, FAILED
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
enum Priority {
|
|
129
|
+
LOW, MEDIUM, HIGH, CRITICAL
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
relationship OneToMany {
|
|
133
|
+
Demand{tasks} to Task{demand required}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
paginate Demand, Task with pagination
|
|
137
|
+
dto * with mapstruct
|
|
138
|
+
service * with serviceImpl
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Enriquecer JHipster com DDD
|
|
142
|
+
|
|
143
|
+
O JHipster gera entidades anêmicas por padrão. Enriqueça com comportamento:
|
|
144
|
+
|
|
145
|
+
```java
|
|
146
|
+
// NÃO FAZER — entidade anêmica (padrão JHipster)
|
|
147
|
+
public class Demand {
|
|
148
|
+
private String description;
|
|
149
|
+
private DemandStatus status;
|
|
150
|
+
// apenas getters/setters
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// FAZER — entidade rica
|
|
154
|
+
public class Demand {
|
|
155
|
+
private String description;
|
|
156
|
+
private DemandStatus status;
|
|
157
|
+
private List<Task> tasks = new ArrayList<>();
|
|
158
|
+
|
|
159
|
+
public List<Task> decompose(TaskPlanner planner) {
|
|
160
|
+
if (this.status != DemandStatus.CREATED) {
|
|
161
|
+
throw new DemandAlreadyDecomposedException(this.id);
|
|
162
|
+
}
|
|
163
|
+
this.tasks = planner.plan(this.description);
|
|
164
|
+
this.status = DemandStatus.PLANNED;
|
|
165
|
+
return Collections.unmodifiableList(this.tasks);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public void startTask(Long taskId) {
|
|
169
|
+
Task task = findTaskOrThrow(taskId);
|
|
170
|
+
long activeCount = tasks.stream()
|
|
171
|
+
.filter(t -> t.getStatus() == TaskStatus.IN_PROGRESS)
|
|
172
|
+
.count();
|
|
173
|
+
if (activeCount >= 3) {
|
|
174
|
+
throw new TooManyActiveTasksException();
|
|
175
|
+
}
|
|
176
|
+
task.start();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Liquibase — Migrations
|
|
182
|
+
|
|
183
|
+
Sempre gerar changeset para mudanças de schema:
|
|
184
|
+
|
|
185
|
+
```xml
|
|
186
|
+
<changeSet id="20260327-1" author="dev">
|
|
187
|
+
<createTable tableName="demand">
|
|
188
|
+
<column name="id" type="bigint" autoIncrement="true">
|
|
189
|
+
<constraints primaryKey="true"/>
|
|
190
|
+
</column>
|
|
191
|
+
<column name="description" type="clob">
|
|
192
|
+
<constraints nullable="false"/>
|
|
193
|
+
</column>
|
|
194
|
+
<column name="status" type="varchar(20)">
|
|
195
|
+
<constraints nullable="false"/>
|
|
196
|
+
</column>
|
|
197
|
+
<column name="created_at" type="timestamp">
|
|
198
|
+
<constraints nullable="false"/>
|
|
199
|
+
</column>
|
|
200
|
+
</createTable>
|
|
201
|
+
</changeSet>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Testes
|
|
205
|
+
|
|
206
|
+
- **JUnit 5:** Testar services e entidades enriquecidas
|
|
207
|
+
- **MockMvc:** Testar controllers (status codes, validação)
|
|
208
|
+
- **Testcontainers:** Integração com PostgreSQL real
|
|
209
|
+
- **Jest:** Componentes Angular
|
|
210
|
+
- **Cypress:** E2E flows
|
|
211
|
+
- Cobertura mínima: 80%
|
|
212
|
+
|
|
213
|
+
## Git
|
|
214
|
+
|
|
215
|
+
- Commits: `feat(demand): adicionar decomposição automática`
|
|
216
|
+
- Branches: `feature/<entity>-<descricao>`
|
|
217
|
+
- Nunca alterar arquivos gerados do JHipster sem necessidade
|
|
218
|
+
- Regenerar com `jhipster import-jdl` quando mudar modelo
|
|
219
|
+
|
|
220
|
+
## O que NÃO fazer
|
|
221
|
+
|
|
222
|
+
- Não editar manualmente arquivos que o JHipster regenera (liquibase gerado, webpack config)
|
|
223
|
+
- Não colocar regras de negócio no REST controller
|
|
224
|
+
- Não usar `@Transactional` em todo lugar — só onde necessário
|
|
225
|
+
- Não criar services genéricos "God class"
|
|
226
|
+
- Não ignorar DTOs — nunca expor entidade JPA na API
|
|
227
|
+
- Não usar Lombok `@Data` (gera equals/hashCode perigoso com JPA)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clean-architecture
|
|
3
|
+
description: Implementar Clean Architecture com camadas de domínio, aplicação e infraestrutura. Use quando for criar módulos, organizar código em camadas, separar regras de negócio de infraestrutura, ou estruturar um projeto novo.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Clean Architecture
|
|
7
|
+
|
|
8
|
+
## Camadas
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
┌──────────────────────────────┐
|
|
12
|
+
│ API / CLI │ ← Controllers, Routers
|
|
13
|
+
├──────────────────────────────┤
|
|
14
|
+
│ APPLICATION │ ← Use Cases, DTOs
|
|
15
|
+
├──────────────────────────────┤
|
|
16
|
+
│ DOMAIN │ ← Entities, VOs, Events, Repos (interface)
|
|
17
|
+
├──────────────────────────────┤
|
|
18
|
+
│ INFRASTRUCTURE │ ← DB, HTTP clients, Frameworks
|
|
19
|
+
└──────────────────────────────┘
|
|
20
|
+
|
|
21
|
+
Dependency Rule: setas apontam para DENTRO (infra → domain)
|
|
22
|
+
Domain NUNCA importa de infrastructure
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Domain Layer
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
# domain/entities/demand.py
|
|
29
|
+
class Demand:
|
|
30
|
+
def __init__(self, id: DemandId, description: str):
|
|
31
|
+
self._id = id
|
|
32
|
+
self._description = description
|
|
33
|
+
self._status = DemandStatus.CREATED
|
|
34
|
+
self._events: list[DomainEvent] = []
|
|
35
|
+
|
|
36
|
+
def decompose(self, planner: TaskPlanner) -> list[Task]:
|
|
37
|
+
if self._status != DemandStatus.CREATED:
|
|
38
|
+
raise DemandAlreadyDecomposedException(self._id)
|
|
39
|
+
tasks = planner.plan(self._description)
|
|
40
|
+
self._status = DemandStatus.PLANNED
|
|
41
|
+
self._events.append(DemandDecomposed(self._id, [t.id for t in tasks]))
|
|
42
|
+
return tasks
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def pending_events(self) -> list[DomainEvent]:
|
|
46
|
+
return list(self._events)
|
|
47
|
+
|
|
48
|
+
# domain/repositories/demand_repository.py (PORT - apenas interface)
|
|
49
|
+
class DemandRepository(ABC):
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def find_by_id(self, id: DemandId) -> Demand: ...
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def save(self, demand: Demand) -> None: ...
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Application Layer
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# application/use_cases/decompose_demand.py
|
|
60
|
+
class DecomposeDemand:
|
|
61
|
+
def __init__(self, repo: DemandRepository, planner: TaskPlanner, event_bus: EventBus):
|
|
62
|
+
self._repo = repo
|
|
63
|
+
self._planner = planner
|
|
64
|
+
self._event_bus = event_bus
|
|
65
|
+
|
|
66
|
+
def execute(self, demand_id: str) -> DecomposeDemandOutput:
|
|
67
|
+
demand = self._repo.find_by_id(DemandId(demand_id))
|
|
68
|
+
tasks = demand.decompose(self._planner)
|
|
69
|
+
self._repo.save(demand)
|
|
70
|
+
for event in demand.pending_events:
|
|
71
|
+
self._event_bus.publish(event)
|
|
72
|
+
return DecomposeDemandOutput(tasks=[TaskDTO.from_entity(t) for t in tasks])
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Infrastructure Layer
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# infrastructure/persistence/pg_demand_repository.py (ADAPTER)
|
|
79
|
+
class PgDemandRepository(DemandRepository):
|
|
80
|
+
def __init__(self, session: Session):
|
|
81
|
+
self._session = session
|
|
82
|
+
|
|
83
|
+
def find_by_id(self, id: DemandId) -> Demand:
|
|
84
|
+
model = self._session.query(DemandModel).get(str(id))
|
|
85
|
+
if not model:
|
|
86
|
+
raise DemandNotFoundException(id)
|
|
87
|
+
return self._to_entity(model)
|
|
88
|
+
|
|
89
|
+
def save(self, demand: Demand) -> None:
|
|
90
|
+
model = self._to_model(demand)
|
|
91
|
+
self._session.merge(model)
|
|
92
|
+
self._session.commit()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Regra de ouro
|
|
96
|
+
|
|
97
|
+
Use Case orquestra → Entity contém regra → Repository persiste
|
|
98
|
+
|
|
99
|
+
Nunca colocar regra de negócio no Controller, Repository ou "Service" genérico.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ddd-tactical
|
|
3
|
+
description: Implementar padrões táticos do DDD - Entities, Value Objects, Aggregates, Domain Events, Repositories e Domain Services. Use quando for modelar domínio, criar entidades ricas, ou aplicar DDD no código.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DDD Tático
|
|
7
|
+
|
|
8
|
+
## Value Objects
|
|
9
|
+
|
|
10
|
+
Imutáveis, comparados por valor, contêm validação.
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class Email:
|
|
15
|
+
value: str
|
|
16
|
+
|
|
17
|
+
def __post_init__(self):
|
|
18
|
+
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', self.value):
|
|
19
|
+
raise InvalidEmailException(self.value)
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class ComplianceScore:
|
|
23
|
+
value: float
|
|
24
|
+
|
|
25
|
+
def __post_init__(self):
|
|
26
|
+
if not 0 <= self.value <= 100:
|
|
27
|
+
raise ValueError("Score deve ser entre 0 e 100")
|
|
28
|
+
|
|
29
|
+
def is_passing(self) -> bool:
|
|
30
|
+
return self.value >= 80.0
|
|
31
|
+
|
|
32
|
+
def __str__(self) -> str:
|
|
33
|
+
return f"{self.value:.1f}%"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Entities
|
|
37
|
+
|
|
38
|
+
Identidade única, mutáveis, contêm comportamento.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
class Task:
|
|
42
|
+
def __init__(self, id: TaskId, description: str, agent_type: AgentType):
|
|
43
|
+
self._id = id
|
|
44
|
+
self._description = description
|
|
45
|
+
self._agent_type = agent_type
|
|
46
|
+
self._status = TaskStatus.PENDING
|
|
47
|
+
self._started_at: datetime | None = None
|
|
48
|
+
self._completed_at: datetime | None = None
|
|
49
|
+
|
|
50
|
+
def start(self) -> None:
|
|
51
|
+
if self._status != TaskStatus.PENDING:
|
|
52
|
+
raise TaskNotPendingException(self._id)
|
|
53
|
+
self._status = TaskStatus.IN_PROGRESS
|
|
54
|
+
self._started_at = datetime.now()
|
|
55
|
+
|
|
56
|
+
def complete(self, result: TaskResult) -> None:
|
|
57
|
+
if self._status != TaskStatus.IN_PROGRESS:
|
|
58
|
+
raise TaskNotInProgressException(self._id)
|
|
59
|
+
self._status = TaskStatus.COMPLETED
|
|
60
|
+
self._completed_at = datetime.now()
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def duration(self) -> timedelta | None:
|
|
64
|
+
if self._started_at and self._completed_at:
|
|
65
|
+
return self._completed_at - self._started_at
|
|
66
|
+
return None
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Aggregates
|
|
70
|
+
|
|
71
|
+
Cluster de entities com uma raiz que protege invariantes.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
class Demand: # Aggregate Root
|
|
75
|
+
"""Demand é a raiz. Tasks só são acessadas via Demand."""
|
|
76
|
+
|
|
77
|
+
def add_task(self, task: Task) -> None:
|
|
78
|
+
if len(self._tasks) >= 20:
|
|
79
|
+
raise TooManyTasksException("Máximo 20 tasks por demanda")
|
|
80
|
+
self._tasks.append(task)
|
|
81
|
+
|
|
82
|
+
def start_task(self, task_id: TaskId) -> None:
|
|
83
|
+
task = self._find_task(task_id)
|
|
84
|
+
# Verificar invariante: não ter mais de 3 tasks simultâneas
|
|
85
|
+
active = [t for t in self._tasks if t.status == TaskStatus.IN_PROGRESS]
|
|
86
|
+
if len(active) >= 3:
|
|
87
|
+
raise TooManyActiveTasksException()
|
|
88
|
+
task.start()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Domain Events
|
|
92
|
+
|
|
93
|
+
Notificam que algo aconteceu no domínio.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
@dataclass(frozen=True)
|
|
97
|
+
class DomainEvent:
|
|
98
|
+
occurred_at: datetime = field(default_factory=datetime.now)
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True)
|
|
101
|
+
class TaskCompleted(DomainEvent):
|
|
102
|
+
task_id: TaskId
|
|
103
|
+
agent_id: AgentId
|
|
104
|
+
branch_name: str
|
|
105
|
+
duration_seconds: float
|
|
106
|
+
|
|
107
|
+
@dataclass(frozen=True)
|
|
108
|
+
class ComplianceViolationDetected(DomainEvent):
|
|
109
|
+
agent_id: AgentId
|
|
110
|
+
bundle_name: str
|
|
111
|
+
violations: list[str]
|
|
112
|
+
severity: str # "warning" | "critical"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Domain Services
|
|
116
|
+
|
|
117
|
+
Lógica que não pertence a uma única entidade.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
class TeamRecruitmentService:
|
|
121
|
+
def recruit_for_demand(self, demand: Demand, available: list[Agent]) -> AgentTeam:
|
|
122
|
+
complexity = demand.assess_complexity()
|
|
123
|
+
required = self._determine_roles(complexity)
|
|
124
|
+
team = AgentTeam()
|
|
125
|
+
for role in required:
|
|
126
|
+
agent = next((a for a in available if a.type == role and a.is_available), None)
|
|
127
|
+
if not agent:
|
|
128
|
+
raise NoAvailableAgentException(role)
|
|
129
|
+
team.add(agent)
|
|
130
|
+
return team
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Ordem de prioridade para regras
|
|
134
|
+
|
|
135
|
+
1. **Value Object** — validação e comportamento simples
|
|
136
|
+
2. **Entity** — regras que envolvem estado da entidade
|
|
137
|
+
3. **Domain Service** — regras que envolvem múltiplas entidades
|
|
138
|
+
4. **Use Case** — orquestração (NÃO contém regras)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jhipster-angular
|
|
3
|
+
description: Desenvolver frontend Angular no JHipster com componentes, services, routing e formulários reativos. Use quando precisar criar telas, componentes Angular, ou customizar o frontend JHipster.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JHipster Angular
|
|
7
|
+
|
|
8
|
+
## Estrutura gerada pelo JHipster
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
src/main/webapp/app/
|
|
12
|
+
├── entities/ # CRUD gerado por entidade
|
|
13
|
+
│ ├── demand/
|
|
14
|
+
│ │ ├── demand.model.ts
|
|
15
|
+
│ │ ├── demand.routes.ts
|
|
16
|
+
│ │ ├── service/
|
|
17
|
+
│ │ │ └── demand.service.ts
|
|
18
|
+
│ │ ├── list/
|
|
19
|
+
│ │ │ └── demand.component.ts
|
|
20
|
+
│ │ ├── detail/
|
|
21
|
+
│ │ │ └── demand-detail.component.ts
|
|
22
|
+
│ │ ├── update/
|
|
23
|
+
│ │ │ └── demand-update.component.ts
|
|
24
|
+
│ │ └── delete/
|
|
25
|
+
│ │ └── demand-delete-dialog.component.ts
|
|
26
|
+
│ └── task/
|
|
27
|
+
├── shared/ # Componentes compartilhados
|
|
28
|
+
│ ├── date/
|
|
29
|
+
│ ├── filter/
|
|
30
|
+
│ ├── pagination/
|
|
31
|
+
│ └── sort/
|
|
32
|
+
├── core/ # Services core
|
|
33
|
+
│ ├── auth/
|
|
34
|
+
│ ├── interceptor/
|
|
35
|
+
│ └── util/
|
|
36
|
+
├── layouts/ # Layouts de página
|
|
37
|
+
│ ├── main/
|
|
38
|
+
│ ├── navbar/
|
|
39
|
+
│ └── footer/
|
|
40
|
+
└── config/
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Customizar componentes gerados
|
|
44
|
+
|
|
45
|
+
O JHipster gera CRUD básico. Customizar SEM modificar a estrutura gerada:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// entities/demand/list/demand.component.ts
|
|
49
|
+
@Component({
|
|
50
|
+
standalone: true,
|
|
51
|
+
selector: 'jhi-demand',
|
|
52
|
+
templateUrl: './demand.component.html',
|
|
53
|
+
imports: [SharedModule, RouterModule, SortDirective, SortByDirective, FormsModule],
|
|
54
|
+
})
|
|
55
|
+
export class DemandComponent implements OnInit {
|
|
56
|
+
demands?: IDemand[];
|
|
57
|
+
isLoading = false;
|
|
58
|
+
|
|
59
|
+
// ADICIONAR — filtros customizados
|
|
60
|
+
statusFilter: DemandStatus | null = null;
|
|
61
|
+
priorityFilter: Priority | null = null;
|
|
62
|
+
|
|
63
|
+
constructor(
|
|
64
|
+
protected demandService: DemandService,
|
|
65
|
+
protected activatedRoute: ActivatedRoute,
|
|
66
|
+
protected router: Router,
|
|
67
|
+
protected sortService: SortService,
|
|
68
|
+
) {}
|
|
69
|
+
|
|
70
|
+
// ADICIONAR — método de filtro
|
|
71
|
+
filterByStatus(status: DemandStatus): void {
|
|
72
|
+
this.statusFilter = status;
|
|
73
|
+
this.loadDemands();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private loadDemands(): void {
|
|
77
|
+
this.isLoading = true;
|
|
78
|
+
this.demandService
|
|
79
|
+
.query({
|
|
80
|
+
page: this.page,
|
|
81
|
+
size: this.itemsPerPage,
|
|
82
|
+
sort: this.sort(),
|
|
83
|
+
'status.equals': this.statusFilter,
|
|
84
|
+
'priority.equals': this.priorityFilter,
|
|
85
|
+
})
|
|
86
|
+
.subscribe({
|
|
87
|
+
next: (res) => {
|
|
88
|
+
this.demands = res.body ?? [];
|
|
89
|
+
this.isLoading = false;
|
|
90
|
+
},
|
|
91
|
+
error: () => (this.isLoading = false),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Criar componentes novos (fora do CRUD gerado)
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// dashboard/dashboard.component.ts
|
|
101
|
+
@Component({
|
|
102
|
+
standalone: true,
|
|
103
|
+
selector: 'app-dashboard',
|
|
104
|
+
template: `
|
|
105
|
+
<div class="row">
|
|
106
|
+
<div class="col-md-3" *ngFor="let metric of metrics">
|
|
107
|
+
<div class="card">
|
|
108
|
+
<div class="card-body text-center">
|
|
109
|
+
<h5>{{ metric.label }}</h5>
|
|
110
|
+
<h2>{{ metric.value }}</h2>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="row mt-4">
|
|
116
|
+
<div class="col-md-8">
|
|
117
|
+
<app-agent-activity-feed />
|
|
118
|
+
</div>
|
|
119
|
+
<div class="col-md-4">
|
|
120
|
+
<app-active-demands />
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
`,
|
|
124
|
+
imports: [CommonModule, AgentActivityFeedComponent, ActiveDemandsComponent],
|
|
125
|
+
})
|
|
126
|
+
export class DashboardComponent implements OnInit {
|
|
127
|
+
metrics: Metric[] = [];
|
|
128
|
+
|
|
129
|
+
constructor(private dashboardService: DashboardService) {}
|
|
130
|
+
|
|
131
|
+
ngOnInit(): void {
|
|
132
|
+
this.dashboardService.getMetrics().subscribe(m => this.metrics = m);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Reactive Forms com validação
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
@Component({...})
|
|
141
|
+
export class DemandUpdateComponent {
|
|
142
|
+
editForm = new FormGroup({
|
|
143
|
+
description: new FormControl('', [Validators.required, Validators.minLength(10)]),
|
|
144
|
+
priority: new FormControl<Priority>(Priority.MEDIUM, Validators.required),
|
|
145
|
+
status: new FormControl<DemandStatus>(DemandStatus.CREATED),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
save(): void {
|
|
149
|
+
if (this.editForm.valid) {
|
|
150
|
+
const demand = this.editForm.getRawValue();
|
|
151
|
+
this.demandService.create(demand).subscribe({
|
|
152
|
+
next: () => this.router.navigate(['/demand']),
|
|
153
|
+
error: (err) => this.alertService.addAlert({ type: 'danger', message: err.message }),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Regras
|
|
161
|
+
|
|
162
|
+
- Não modificar `navbar.component.ts` diretamente — usar menus configuráveis
|
|
163
|
+
- Usar `SharedModule` para imports comuns
|
|
164
|
+
- Lazy loading para todas as rotas de entidades
|
|
165
|
+
- Usar `TranslateService` do JHipster para i18n
|
|
166
|
+
- Manter compatibilidade com regeneração: marcar customizações claramente
|