From 4596d1c6a05bc82a095ff9e68e5d8f134bdac9ec Mon Sep 17 00:00:00 2001 From: Bruno Silva Date: Sat, 29 Aug 2026 23:51:04 -0300 Subject: [PATCH 1/2] Add feature-scaffolder custom agent, instructions and skill - .github/agents/feature-scaffolder.agent.md: custom agent that scaffolds a complete new domain feature following project conventions. - .github/instructions/feature-scaffolding.instructions.md: standing rules for layers, naming, migrations and tests, applied to Java/SQL files. - .github/skills/feature-scaffolding/SKILL.md: reusable operational skill generating Entity, Repository, Mapper, DTOs, Service, Controller+ApiDocs, Flyway migration and unit tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/agents/feature-scaffolder.agent.md | 52 +++++++++ .../feature-scaffolding.instructions.md | 96 +++++++++++++++++ .github/skills/feature-scaffolding/SKILL.md | 102 ++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 .github/agents/feature-scaffolder.agent.md create mode 100644 .github/instructions/feature-scaffolding.instructions.md create mode 100644 .github/skills/feature-scaffolding/SKILL.md diff --git a/.github/agents/feature-scaffolder.agent.md b/.github/agents/feature-scaffolder.agent.md new file mode 100644 index 0000000..ec4d349 --- /dev/null +++ b/.github/agents/feature-scaffolder.agent.md @@ -0,0 +1,52 @@ +--- +name: Feature Scaffolder +description: 'Gera o esqueleto completo e funcional de uma nova feature do Personal Budget (Controller, Service, Repository, Mapper, DTOs, Entity, migration Flyway e testes) seguindo as convenções do projeto.' +--- + +# Feature Scaffolder + +Você é um especialista na arquitetura em camadas e nas convenções do projeto +**Personal Budget** (Java 21 + Spring Boot 3.2.4, JPA/MySQL, MapStruct, +Flyway). Seu papel é criar, do zero, todos os arquivos necessários para uma +nova feature de domínio (ex.: "Category", "Tag", "Wallet"), com código +completo e funcional — nunca comentários, TODOs ou templates em lugar de +código real. + +Sempre siga integralmente as regras detalhadas em +[feature-scaffolding.instructions.md](../instructions/feature-scaffolding.instructions.md) +e execute o passo a passo operacional descrito na skill `feature-scaffolding` +(`.github/skills/feature-scaffolding/SKILL.md`) para gerar os arquivos. + +## Seu processo + +1. **Descobrir requisitos.** Se o usuário não informou, pergunte: + - Nome da feature (singular, ex.: `Category`) + - Campos principais e tipos (ex.: `name: String`, `color: String`, `parentCode: UUID`) + - Regras de negócio essenciais (validações, status possíveis, relações com outras entidades) + - Se a feature precisa de paginação/listagem (`Paged{Feature}Response`) + Não avance para geração de código sem essas informações mínimas. + +2. **Inspecionar o projeto antes de gerar código.** Releia rapidamente uma + feature existente equivalente (ex.: `FixedBill` ou `FinancialMovement`) e + a última migration em `src/main/resources/db/migration/` para manter + consistência de estilo e descobrir o próximo número de migration. + +3. **Gerar todos os arquivos** conforme a skill `feature-scaffolding`: + Entity, Repository, Mapper, DTOs, Service, Controller + `{Feature}ControllerApiDocs`, + migration Flyway e testes unitários com factory. + +4. **Resumir o resultado** ao final: liste todos os arquivos criados/editados + e sugira rodar `./gradlew test` para validar a compilação e os testes. + +## Regras rígidas (não negociáveis) + +- Controllers **nunca** acessam repositórios diretamente. +- Services **nunca** retornam entidades — sempre domain models/records. +- Domain models **nunca** são expostos na API — sempre convertidos via Mapper para Response. +- Toda conversão entre camadas passa pelo Mapper (MapStruct). +- Identificador público é sempre `code` (UUID); `id` (Long) é interno ao banco. +- Soft delete: setar `flagActive = false`, nunca `DELETE` físico. +- JSON em `snake_case` via `@JsonProperty`. +- Injeção de dependência sempre via `@RequiredArgsConstructor` com campos `final`. +- Log no início de cada método público: `log.info("m={método}, param={valor}")`. +- Nunca deixe código incompleto: se algo não puder ser gerado com certeza (ex.: relação com entidade que não existe), pare e pergunte ao usuário em vez de adivinhar. diff --git a/.github/instructions/feature-scaffolding.instructions.md b/.github/instructions/feature-scaffolding.instructions.md new file mode 100644 index 0000000..0a1878f --- /dev/null +++ b/.github/instructions/feature-scaffolding.instructions.md @@ -0,0 +1,96 @@ +--- +description: 'Convenções obrigatórias para criar uma nova feature de domínio no Personal Budget (camadas Spring Boot, DTOs, mapper, migrations Flyway e testes).' +applyTo: 'src/main/java/**/*.java, src/main/resources/db/migration/**/*.sql, src/test/java/**/*.java' +--- + +# Convenções para novas features — Personal Budget + +Estas regras se aplicam sempre que arquivos Java em `src/main/java`, +migrations Flyway ou testes em `src/test/java` forem criados ou editados, +independente de estar usando o agente `feature-scaffolder` ou não. + +## Estrutura de pacotes (raiz `com.bts.personalbudget`) + +| Pacote | Responsabilidade | +|---|---| +| `controller/{feature}/` | Controllers REST por feature | +| `controller/{feature}/config/` | Interface com anotações OpenAPI (`{Feature}ControllerApiDocs`) | +| `core/domain/entity/` | Entidades JPA (`{Feature}Entity`) | +| `core/domain/enumerator/` | Enums de domínio | +| `core/domain/exception/` | Exceções de domínio | +| `core/domain/model/` | Domain models (records) | +| `core/domain/service/{feature}/` | Serviços com regras de negócio da feature | +| `core/domain/factory/` (em `src/test`) | Factories de teste | +| `mapper/` | Interfaces MapStruct (`{Feature}Mapper`) | +| `repository/` | Repositórios Spring Data JPA (`{Feature}Repository`) | + +> Nota: features mais complexas (ex. `FixedBill`, `InstallmentBill`) mantêm +> seu domain model dentro do próprio subpacote de serviço, não em +> `core/domain/model/`. Para features novas e simples, prefira +> `core/domain/model/` como padrão, a menos que haja um motivo claro para +> localizar o model junto do serviço. + +## Nomenclatura + +- Controllers: `{Feature}Controller` +- Services: `{Feature}Service` +- Entidades JPA: `{Feature}Entity` +- Repositórios: `{Feature}Repository` +- Mappers: `{Feature}Mapper` +- DTOs de entrada: `{Feature}Request`, `{Feature}UpdateRequest` +- DTOs de saída: `{Feature}Response`, `Paged{Feature}Response` +- Interface OpenAPI: `{Feature}ControllerApiDocs` +- Enums: singular descritivo (ex.: `OperationType`, `FinancialMovementStatus`) +- Testes: `should{Ação}{Condição}` (ex.: `shouldCreateCategoryWhenNameIsValid`) + +## Camadas — regras rígidas + +- Controllers **nunca** acessam repositórios diretamente; sempre passam pelo Service. +- Services **nunca** retornam entidades — sempre domain models (records). +- Domain models **nunca** são expostos na API — sempre convertidos para `{Feature}Response` via Mapper. +- Toda conversão entre camadas (Request/Response ↔ Domain Model ↔ Entity) passa pelo Mapper (MapStruct). +- Injeção de dependência sempre via `@RequiredArgsConstructor` com campos `final` (nunca `@Autowired` em campo). +- Boilerplate via Lombok (`@Getter`, `@Setter`, `@Builder`, `@Slf4j`, etc.), nunca getters/setters manuais. + +## Identificadores e persistência + +- Identificador público sempre `code` (UUID gerado na criação); `id` (Long, `@GeneratedValue`) é interno ao banco e nunca exposto na API. +- Soft delete: setar `flagActive = false` no update; nunca `DELETE` físico. +- Campos de auditoria padrão: `createdDate`, `lastModifiedDate` (via `@CreatedDate`/`@LastModifiedDate` ou equivalente já usado no projeto). + +## JSON e validação + +- Campos JSON em `snake_case` via `@JsonProperty` nos DTOs (nomes de campo Java continuam `camelCase`). +- Validação de entrada com Bean Validation (`@Valid`, `@NotNull`, `@NotBlank`, etc.) nos `Request`/`UpdateRequest`. + +## Logs + +- Todo método público de Controller/Service inicia com: + `log.info("m={nomeDoMétodo}, param={valorRelevante}")`. + +## Migrations Flyway + +- Local: `src/main/resources/db/migration/`. +- Nomenclatura: `V{n}__{descricao_snake_case}.sql`, onde `{n}` é o próximo + inteiro sequencial após a última migration existente (verifique sempre os + arquivos existentes antes de numerar; nunca reutilize ou pule números). +- Uma migration por feature/tabela nova; alterações de tabelas existentes vão + em uma nova migration, nunca editando uma migration já aplicada. +- Tipos e defaults devem ser coerentes com o padrão MySQL 8.0 já usado nas + migrations existentes (ex.: `BIGINT` para PK/FK, `VARCHAR` com tamanho + explícito, `DATETIME` para timestamps, `flag_active TINYINT(1) DEFAULT 1`). + +## Testes + +- Nomenclatura de métodos: `should{Ação}{Condição}`. +- Dados de teste construídos via factories em `core/domain/factory/` + (criar uma factory nova por feature quando não existir uma reutilizável). +- Testes unitários usam JUnit 5 + Mockito; testes de integração (quando + aplicável) usam TestContainers com MySQL, seguindo os exemplos já + presentes em `src/test_integration`. + +## Documentação OpenAPI + +- Toda anotação Swagger/OpenAPI fica concentrada na interface + `{Feature}ControllerApiDocs`, implementada pelo Controller — o Controller + em si permanece livre de anotações de documentação. diff --git a/.github/skills/feature-scaffolding/SKILL.md b/.github/skills/feature-scaffolding/SKILL.md new file mode 100644 index 0000000..7de3da6 --- /dev/null +++ b/.github/skills/feature-scaffolding/SKILL.md @@ -0,0 +1,102 @@ +--- +name: feature-scaffolding +description: 'Gera o esqueleto completo de uma nova feature de domínio no Personal Budget (Entity, Repository, Mapper, DTOs, Service, Controller+ApiDocs, migration Flyway e testes). Use quando o usuário pedir para "criar uma feature", "gerar um CRUD", "scaffold" ou "adicionar um novo domínio" ao projeto.' +metadata: + argument-hint: [campos e tipos] [regras de negócio] +--- + +# Feature Scaffolding — Personal Budget + +Esta skill gera todos os arquivos de uma nova feature de domínio, seguindo +rigorosamente as convenções descritas em +`.github/instructions/feature-scaffolding.instructions.md` e nos documentos +`.kiro/steering/*.md`. Gere sempre código completo e funcional — nunca +placeholders, comentários "implemente aqui" ou métodos vazios. + +## 1. Reunir requisitos + +Se não fornecido pelo usuário, pergunte: +- **Nome da feature** (singular, PascalCase, ex.: `Category`) +- **Campos** e tipos (ex.: `name: String`, `amount: BigDecimal`, `dueDate: LocalDate`, relações com outras entidades) +- **Regras de negócio** (status possíveis, validações obrigatórias, cálculos) +- **Endpoints necessários** (CRUD completo? apenas leitura? listagem paginada?) + +Não prossiga para geração de código sem essas informações mínimas. + +## 2. Inspecionar o projeto + +Antes de escrever qualquer arquivo: +1. Liste `src/main/resources/db/migration/` e identifique o maior `V{n}__` + existente — a nova migration usa `n+1`. +2. Abra uma feature existente semelhante em complexidade (ex.: + `core/domain/service/fixedbill/` ou o pacote de `FinancialMovement`) para + replicar exatamente o estilo de código, anotações e imports usados. +3. Confirme o pacote raiz `com.bts.personalbudget` e a estrutura de pastas + real do repositório (não assuma — verifique com `find`/`glob`). + +## 3. Gerar os arquivos + +Para uma feature `{Feature}` (ex.: `Category`), gerar, nesta ordem: + +### 3.1 Migration Flyway +`src/main/resources/db/migration/V{n}__create_table_{feature_snake_case}.sql` +- Tabela com PK `id BIGINT AUTO_INCREMENT`, `code CHAR(36)` (UUID) único, + colunas para cada campo, `flag_active TINYINT(1) DEFAULT 1`, + `created_date DATETIME`, `last_modified_date DATETIME`. +- FKs para relações informadas pelo usuário. + +### 3.2 Entity JPA +`core/domain/entity/{Feature}Entity.java` +- `@Entity`, `@Table(name = "...")`, Lombok (`@Getter`, `@Setter`, `@Builder`, `@NoArgsConstructor`, `@AllArgsConstructor`). +- Campo `code` do tipo `UUID`, gerado em `@PrePersist` ou no builder do Service. + +### 3.3 Domain Model +`core/domain/model/{Feature}.java` (record) — apenas os campos relevantes ao negócio, nunca a entidade JPA. + +### 3.4 Repository +`repository/{Feature}Repository.java` — `extends JpaRepository<{Feature}Entity, Long>`, com métodos derivados (`findByCodeAndFlagActiveTrue`, paginação, etc.) conforme necessário. + +### 3.5 Mapper (MapStruct) +`mapper/{Feature}Mapper.java` — `@Mapper(componentModel = "spring")`, métodos: +- `{Feature}Entity toEntity({Feature}Request request)` +- `{Feature} toDomain({Feature}Entity entity)` +- `{Feature}Response toResponse({Feature} domain)` +- `Paged{Feature}Response toPagedResponse(Page<{Feature}> page)` (se houver listagem) + +### 3.6 DTOs +`controller/{feature}/dto/` (ou pacote de DTOs já usado no projeto): +- `{Feature}Request` — campos de entrada com Bean Validation, `@JsonProperty` em snake_case. +- `{Feature}UpdateRequest` — campos opcionais para atualização parcial/total. +- `{Feature}Response` — campos de saída (inclui `code`, nunca `id`), `@JsonProperty` em snake_case. +- `Paged{Feature}Response` — se houver listagem paginada. + +### 3.7 Service +`core/domain/service/{feature}/{Feature}Service.java` +- `@Slf4j`, `@RequiredArgsConstructor`, `@Transactional` nos métodos de escrita. +- Log `log.info("m={método}, param={valor}")` no início de cada método público. +- Métodos: `create`, `update` (soft delete via `flagActive=false` quando aplicável), `findByCode`, `findAll`/`findAllPaged`. +- Lança exceções de domínio existentes (ex.: `EntityNotFoundException` já usada no projeto) quando `code` não é encontrado. + +### 3.8 Controller + ApiDocs +- `controller/{feature}/config/{Feature}ControllerApiDocs.java` — interface com todas as anotações Swagger/OpenAPI (`@Operation`, `@ApiResponse`, etc.). +- `controller/{feature}/{Feature}Controller.java` — `@RestController`, `@RequestMapping("/{feature_snake_case}")`, `@RequiredArgsConstructor`, `implements {Feature}ControllerApiDocs`; delega tudo ao Service e usa o Mapper só para request→domain e domain→response. + +### 3.9 Testes +- `src/test/java/.../core/domain/factory/{Feature}Factory.java` — builder de dados de teste válidos. +- Testes unitários do Service (`{Feature}ServiceTest`) e do Mapper, se relevante, com nomenclatura `should{Ação}{Condição}` (ex.: `shouldThrowExceptionWhenCategoryCodeNotFound`). +- Cobrir: criação com sucesso, validação de erro, atualização, soft delete, busca por code inexistente. + +## 4. Validar + +Depois de gerar os arquivos, rode (ou sugira rodar, se não for possível +executar): +``` +./gradlew test +``` +e resolva quaisquer erros de compilação antes de considerar a tarefa concluída. + +## 5. Resumir + +Ao final, liste todos os arquivos criados/alterados, agrupados por camada, e +aponte próximos passos manuais que o usuário precise fazer (ex.: registrar +rotas em algum gateway, ajustar documentação externa). From 928429fa91f5a31c7fb8cd36f79f9fcdf60140fd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Aug 2026 12:20:01 +0000 Subject: [PATCH 2/2] docs: align feature scaffolding guidance with review feedback Co-authored-by: brunosilvaJava <12662607+brunosilvaJava@users.noreply.github.com> --- .github/agents/feature-scaffolder.agent.md | 9 +++-- .github/skills/feature-scaffolding/SKILL.md | 37 ++++++++++++++++----- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/.github/agents/feature-scaffolder.agent.md b/.github/agents/feature-scaffolder.agent.md index ec4d349..2b29fa3 100644 --- a/.github/agents/feature-scaffolder.agent.md +++ b/.github/agents/feature-scaffolder.agent.md @@ -31,9 +31,12 @@ e execute o passo a passo operacional descrito na skill `feature-scaffolding` a última migration em `src/main/resources/db/migration/` para manter consistência de estilo e descobrir o próximo número de migration. -3. **Gerar todos os arquivos** conforme a skill `feature-scaffolding`: - Entity, Repository, Mapper, DTOs, Service, Controller + `{Feature}ControllerApiDocs`, - migration Flyway e testes unitários com factory. +3. **Gerar apenas os arquivos necessários** conforme a skill + `feature-scaffolding` e o conjunto de endpoints solicitado: + Entity, Repository, Mapper, DTOs, Service, Controller + + `{Feature}ControllerApiDocs`, migration Flyway e testes unitários com + factory, sempre de forma condicional às operações pedidas (ex.: leitura vs. + escrita). 4. **Resumir o resultado** ao final: liste todos os arquivos criados/editados e sugira rodar `./gradlew test` para validar a compilação e os testes. diff --git a/.github/skills/feature-scaffolding/SKILL.md b/.github/skills/feature-scaffolding/SKILL.md index 7de3da6..0ee1c05 100644 --- a/.github/skills/feature-scaffolding/SKILL.md +++ b/.github/skills/feature-scaffolding/SKILL.md @@ -36,11 +36,12 @@ Antes de escrever qualquer arquivo: ## 3. Gerar os arquivos -Para uma feature `{Feature}` (ex.: `Category`), gerar, nesta ordem: +Para uma feature `{Feature}` (ex.: `Category`), gerar, nesta ordem, **somente os +artefatos necessários ao conjunto de endpoints solicitado pelo usuário**: ### 3.1 Migration Flyway `src/main/resources/db/migration/V{n}__create_table_{feature_snake_case}.sql` -- Tabela com PK `id BIGINT AUTO_INCREMENT`, `code CHAR(36)` (UUID) único, +- Tabela com PK `id BIGINT AUTO_INCREMENT`, `code BINARY(16)` (UUID) único, colunas para cada campo, `flag_active TINYINT(1) DEFAULT 1`, `created_date DATETIME`, `last_modified_date DATETIME`. - FKs para relações informadas pelo usuário. @@ -58,15 +59,24 @@ Para uma feature `{Feature}` (ex.: `Category`), gerar, nesta ordem: ### 3.5 Mapper (MapStruct) `mapper/{Feature}Mapper.java` — `@Mapper(componentModel = "spring")`, métodos: -- `{Feature}Entity toEntity({Feature}Request request)` +- `{Feature} toDomain({Feature}Request request)` (somente se houver `create`) +- `{Feature} toDomain({Feature}UpdateRequest request)` (somente se houver + `update`) +- `{Feature}Entity toEntity({Feature} domain)` - `{Feature} toDomain({Feature}Entity entity)` - `{Feature}Response toResponse({Feature} domain)` -- `Paged{Feature}Response toPagedResponse(Page<{Feature}> page)` (se houver listagem) +- `List<{Feature}Response> toResponseList(List<{Feature}> domains)` (se houver + listagem) +- `Paged{Feature}Response` deve ser montado explicitamente no + Controller/Service a partir de `page.getContent()` + metadados de paginação + (não mapear `Page` diretamente no MapStruct). ### 3.6 DTOs `controller/{feature}/dto/` (ou pacote de DTOs já usado no projeto): -- `{Feature}Request` — campos de entrada com Bean Validation, `@JsonProperty` em snake_case. -- `{Feature}UpdateRequest` — campos opcionais para atualização parcial/total. +- `{Feature}Request` — **somente se houver `create`**; campos de entrada com + Bean Validation, `@JsonProperty` em snake_case. +- `{Feature}UpdateRequest` — **somente se houver `update`**; campos opcionais para + atualização parcial/total. - `{Feature}Response` — campos de saída (inclui `code`, nunca `id`), `@JsonProperty` em snake_case. - `Paged{Feature}Response` — se houver listagem paginada. @@ -74,17 +84,26 @@ Para uma feature `{Feature}` (ex.: `Category`), gerar, nesta ordem: `core/domain/service/{feature}/{Feature}Service.java` - `@Slf4j`, `@RequiredArgsConstructor`, `@Transactional` nos métodos de escrita. - Log `log.info("m={método}, param={valor}")` no início de cada método público. -- Métodos: `create`, `update` (soft delete via `flagActive=false` quando aplicável), `findByCode`, `findAll`/`findAllPaged`. +- Métodos: incluir apenas os necessários para os endpoints solicitados + (`create`, `update`, `delete`, `findByCode`, `findAll`/`findAllPaged`). +- Soft delete: aplicar `flagActive=false` em método `delete`; `update` mantém o + registro ativo. - Lança exceções de domínio existentes (ex.: `EntityNotFoundException` já usada no projeto) quando `code` não é encontrado. ### 3.8 Controller + ApiDocs - `controller/{feature}/config/{Feature}ControllerApiDocs.java` — interface com todas as anotações Swagger/OpenAPI (`@Operation`, `@ApiResponse`, etc.). -- `controller/{feature}/{Feature}Controller.java` — `@RestController`, `@RequestMapping("/{feature_snake_case}")`, `@RequiredArgsConstructor`, `implements {Feature}ControllerApiDocs`; delega tudo ao Service e usa o Mapper só para request→domain e domain→response. +- `controller/{feature}/{Feature}Controller.java` — `@RestController`, + `@RequestMapping("/{feature_snake_case}")`, `@RequiredArgsConstructor`, + `implements {Feature}ControllerApiDocs`; cria apenas os endpoints solicitados, + delega tudo ao Service e usa o Mapper só para request→domain e + domain→response. ### 3.9 Testes - `src/test/java/.../core/domain/factory/{Feature}Factory.java` — builder de dados de teste válidos. - Testes unitários do Service (`{Feature}ServiceTest`) e do Mapper, se relevante, com nomenclatura `should{Ação}{Condição}` (ex.: `shouldThrowExceptionWhenCategoryCodeNotFound`). -- Cobrir: criação com sucesso, validação de erro, atualização, soft delete, busca por code inexistente. +- Cobrir somente os casos correspondentes aos endpoints solicitados + (ex.: criação com sucesso, validação de erro, atualização, soft delete, + busca por code inexistente). ## 4. Validar