feature
Skill0 starsChanged 6 months agoMITView on GitHub
Cria bounded context completo (24 arquivos Java = 16 producao + 8 testes) alinhado a docs/crud-patterns.md baseline. Inclui 5 use cases (Criar/Listar/BuscarPorId/Atualizar/Deletar), controller com auditoria + GET /{id}, userId, repository completo, DTOs e os 4 niveis de teste (domain, 5 useCases com Mockito, RepositoryImpl com Testcontainers, Controller E2E com 7 cenarios). Elimina necessidade de invocar /write-test para o baseline. Recebe nome do contexto em snake_case minusculo.
73 other projects use a version of this.
Adopted by
Most recently taken up by
- JeffKarling/Anviltheir own version, first seen Nov 15, 2026
- 5uck1ess/devkittheir own version, first seen Nov 15, 2026
- crbnos/carbontheir own version, first seen Nov 15, 2026
- yicheng47/runnertheir own version, first seen Nov 13, 2026
- hex/claude-sessionstheir own version, first seen Nov 9, 2026
- 5hirish/ducttheir own version, first seen Nov 8, 2026
Used with
Projects with it also use bug, bugfix and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
paulocsbSkillbug
Investigation playbook for fixing a bug. Walks through 5 phases: reproduce → isolate → root cause → propose fix → validate. Forces concrete reasoning with code reads, log inspection, and git blame instead of pattern-matching guesses. Use for any non-trivial defect.
Also in 8 projects that use this
team-attentionSkillbugfix
Root cause based one-shot bug fix. debugger diagnosis → requirements.md generation → /execute. /bugfix "error description" Full investigation pipeline: debugger + gap-analyzer + standard verify. QA suggestion after successful fix.
Also in 7 projects that use this
codebycoreySkilladopt
Adopt an existing plan from conversation context into delta-spec format. Use when importing a plan, adopting planning that already happened, capturing plan mode output, or bringing in prior design work.
Also in 4 projects that use this
Google CloudSkillfix
Plan a remediation strategy after a test failure.
Also in 10 projects that use this
marylinSkillrecover
Recover work from a crashed or interrupted session. Use when Claude crashed mid-task, after an unexpected exit, when you see 'stale sessions detected', or when you lost context and need to figure out what was happening.
Also in 3 projects that use this
aegiswizardSkillcontext
Also in 4 projects that use this
Other versions73 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 73
- crbnos/carbon★ 2.8ksame name
- aeonfun/aeon★ 793same name
- aaronjmars/aeon★ 789same name
- astashov/liftosaur★ 775same name
- me2resh/apexyard★ 542same name
- yicheng47/runner★ 519same name
- bradtraversy/coding-with-ai-course-resources★ 344same name
- garagon/nanostack★ 206same name
- DeL-TaiseiOzaki/claude-code-orchestra★ 199same name
- vide/matedroid★ 86same name
63 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository27 other files it gives its agents.
- frontend/AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- frontend/CLAUDE.mdCLAUDE.md
- add-entity-to-auditSkill
- auditSkill
- babysit-prsSkill
- batchSkill
- daily-summarySkill
- factory-metricsSkill
- feature-frontSkill
- init-projectSkill
- migrateSkill
- planSkill
- register-screenSkill
- review-archSkill
- review-frontSkill
- review-prSkill
- setup-architectureSkill
- setup-designSkill
- setup-infraSkill
- shipSkill
- tasksSkill
- watch-ciSkill
- write-jobSkill
- write-migrationSkill
- write-reportSkill
- write-testSkill
The file
We split this skill into the rules it gives an agent
1 rule. 0 are common, 0 are distinctive, and 1 is very specific to this project.
0
Common
Most of the 67 edited versions we read keep these.
0
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 65
Use Write para cada arquivo.
“Common” is measured: kept by at least 6 in 10 of the 67 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Voce deve criar o bounded context $ARGUMENTS no projeto financas-lab gerando o
baseline completo ja alinhado com docs/crud-patterns.md (secoes 1.1, 1.4,
1.5, 2.1, 2.4-2.7, 3, 4.1-4.4, 5.1, 5.2).
Fonte unica de padrao: docs/crud-patterns.md. Esta skill gera o gabarito
canonico inline. O executor depois so adapta nomes/tipos de campos especificos
do dominio (Money, enum, FK, M:N, soft-delete, state machine etc -- secoes 1.2,
1.3, 1.6, 1.7, 5.2.1, 10.x de crud-patterns). NAO ler outros bounded contexts
como template -- os 15 arquivos abaixo ja sao o canonico.
Definicoes
ARG=$ARGUMENTS(ex:lembrete,meta_financeira)NOME= PascalCase deARG: capitalize a primeira letra de cada segmento separado por underscore e concatene (lembrete->Lembrete,meta_financeira->MetaFinanceira)nome= camelCase deARG(primeira letra minuscula):lembrete,metaFinanceiranomes= plural simples (lembretes,metaFinanceiras) -- executor ajusta plural pt-BR no @RequestMapping se preciso- Pacote base:
com.laboratorio.financas.ARG
Passo 0 -- Validacoes (ADR-011)
V1 -- formato:
ARG deve casar com ^[a-z][a-z0-9_]*$. Se nao casar:
"ERRO: argumento invalido -- use letras minusculas, digitos e underscore (ex: /feature lembrete)" e termine.
V2 -- existencia:
Test-Path "src/main/java/com/laboratorio/financas/ARG/"Se True: "ERRO: bounded context 'ARG' ja existe" e termine.
Passo 1 -- Criar diretorios
[System.Environment]::CurrentDirectory = (Get-Location).Path
New-Item -ItemType Directory -Force `
-Path "src/main/java/com/laboratorio/financas/ARG/domain", `
"src/main/java/com/laboratorio/financas/ARG/application", `
"src/main/java/com/laboratorio/financas/ARG/infrastructure/persistence", `
"src/main/java/com/laboratorio/financas/ARG/interfaces", `
"src/main/java/com/laboratorio/financas/ARG/interfaces/dto", `
"src/test/java/com/laboratorio/financas/ARG/domain", `
"src/test/java/com/laboratorio/financas/ARG/application", `
"src/test/java/com/laboratorio/financas/ARG/infrastructure/persistence", `
"src/test/java/com/laboratorio/financas/ARG/interfaces"Verifique com Test-Path que os 9 diretorios foram criados. Se algum ausente:
reporte qual falhou e termine.
Atencao: interfaces/ (NAO interfaces/rest/). Controller fica em
interfaces/, DTOs em interfaces/dto/. Convencao do projeto.
Passo 2 -- Criar os 24 arquivos (16 producao + 8 testes)
Use Write para cada arquivo. Substitua NOME, ARG, nome, nomes pelos
valores definidos. Codificacao: UTF-8 sem BOM.
ESTILO CHECKSTYLE OBRIGATORIO (o gate mvn test roda Checkstyle na fase
validate -- estilo errado FALHA o build). Os templates abaixo ja estao no
estilo correto; ao adaptar, MANTER:
- Getter em multiplas linhas -- NUNCA
public UUID getId() { return id; }numa linha (violaLeftCurly). Sempre:public UUID getId() { return id; } ifSEMPRE com chaves -- NUNCAif (x == null) return null;(violaNeedBraces). Sempre:if (x == null) { return null; }- Bloco vazio com espaco --
record Comando(...) { }eValorMonetario(...) { }com espaco entre as chaves, NUNCA{}(violaWhitespaceAround). - Logger constante MAIUSCULA --
private static final Logger LOG(nuncalog, violaConstantName). - Sem import nao usado (
UnusedImports), import na ordem, sem star import.
Arquivo 1: src/main/java/com/laboratorio/financas/ARG/domain/NOME.java
package com.laboratorio.financas.ARG.domain;
import java.time.Instant;
import java.util.Objects;
import java.util.UUID;
public final class NOME {
private static final int NOME_MAX_LENGTH = 100;
private final UUID id;
private final UUID userId;
private String nome;
private boolean ativo;
private final Instant criadoEm;
private Instant atualizadoEm;
public NOME(UUID userId, String nome) {
this(UUID.randomUUID(), userId, nome, true, Instant.now(), Instant.now());
}
public NOME(UUID id, UUID userId, String nome,
boolean ativo, Instant criadoEm, Instant atualizadoEm) {
Objects.requireNonNull(id, "id nao pode ser nulo");
Objects.requireNonNull(userId, "userId nao pode ser nulo");
Objects.requireNonNull(criadoEm, "criadoEm nao pode ser nulo");
validarNome(nome);
this.id = id;
this.userId = userId;
this.nome = nome.trim();
this.ativo = ativo;
this.criadoEm = criadoEm;
this.atualizadoEm = atualizadoEm;
}
private static void validarNome(String nome) {
Objects.requireNonNull(nome, "nome nao pode ser nulo");
String trimmed = nome.trim();
if (trimmed.isEmpty()) {
throw new IllegalArgumentException("nome nao pode ser vazio");
}
if (trimmed.length() > NOME_MAX_LENGTH) {
throw new IllegalArgumentException(
"nome nao pode ter mais de " + NOME_MAX_LENGTH + " caracteres");
}
}
public void desativar() {
this.ativo = false;
this.atualizadoEm = Instant.now();
}
public void atualizar(String novoNome) {
validarNome(novoNome);
this.nome = novoNome.trim();
this.atualizadoEm = Instant.now();
}
public UUID getId() {
return id;
}
public UUID getUserId() {
return userId;
}
public String getNome() {
return nome;
}
public boolean isAtivo() {
return ativo;
}
public Instant getCriadoEm() {
return criadoEm;
}
public Instant getAtualizadoEm() {
return atualizadoEm;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof NOME other)) {
return false;
}
return this.id.equals(other.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}Arquivo 2: src/main/java/com/laboratorio/financas/ARG/domain/NOMERepository.java
package com.laboratorio.financas.ARG.domain;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface NOMERepository {
NOME salvar(NOME entidade);
Optional<NOME> buscarPorId(UUID id);
List<NOME> listarPorUserId(UUID userId);
NOME atualizar(NOME entidade);
void deletar(UUID id);
}Arquivo 3: src/main/java/com/laboratorio/financas/ARG/domain/NOMENaoEncontradoException.java
package com.laboratorio.financas.ARG.domain;
import java.util.UUID;
public class NOMENaoEncontradoException extends RuntimeException {
private final UUID id;
public NOMENaoEncontradoException(UUID id) {
super("NOME nao encontrado: " + id);
this.id = id;
}
public UUID getId() {
return id;
}
}Arquivo 4: src/main/java/com/laboratorio/financas/ARG/application/CriarNOMEUseCase.java
package com.laboratorio.financas.ARG.application;
import com.laboratorio.financas.ARG.domain.NOME;
import com.laboratorio.financas.ARG.domain.NOMERepository;
import java.util.UUID;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class CriarNOMEUseCase {
private final NOMERepository repository;
public CriarNOMEUseCase(NOMERepository repository) {
this.repository = repository;
}
public record Comando(UUID userId, String nome) { }
@Transactional
public NOME executar(Comando comando) {
NOME entidade = new NOME(comando.userId(), comando.nome());
return repository.salvar(entidade);
}
}Arquivo 5: src/main/java/com/laboratorio/financas/ARG/application/ListarNOMEsUseCase.java
package com.laboratorio.financas.ARG.application;
import com.laboratorio.financas.ARG.domain.NOME;
import com.laboratorio.financas.ARG.domain.NOMERepository;
import java.util.List;
import java.util.UUID;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class ListarNOMEsUseCase {
private final NOMERepository repository;
public ListarNOMEsUseCase(NOMERepository repository) {
this.repository = repository;
}
@Transactional(readOnly = true)
public List<NOME> executar(UUID userId) {
return repository.listarPorUserId(userId);
}
}Arquivo 5b: src/main/java/com/laboratorio/financas/ARG/application/BuscarNOMEPorIdUseCase.java
package com.laboratorio.financas.ARG.application;
import com.laboratorio.financas.ARG.domain.NOME;
import com.laboratorio.financas.ARG.domain.NOMENaoEncontradoException;
import com.laboratorio.financas.ARG.domain.NOMERepository;
import java.util.UUID;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class BuscarNOMEPorIdUseCase {
private final NOMERepository repository;
public BuscarNOMEPorIdUseCase(NOMERepository repository) {
this.repository = repository;
}
@Transactional(readOnly = true)
public NOME executar(UUID id) {
return repository.buscarPorId(id)
.orElseThrow(() -> new NOMENaoEncontradoException(id));
}
}Arquivo 6: src/main/java/com/laboratorio/financas/ARG/application/AtualizarNOMEUseCase.java
package com.laboratorio.financas.ARG.application;
import com.laboratorio.financas.ARG.domain.NOME;
import com.laboratorio.financas.ARG.domain.NOMENaoEncontradoException;
import com.laboratorio.financas.ARG.domain.NOMERepository;
import java.util.UUID;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class AtualizarNOMEUseCase {
private final NOMERepository repository;
public AtualizarNOMEUseCase(NOMERepository repository) {
this.repository = repository;
}
public record Comando(UUID id, String nome) { }
@Transactional
public NOME executar(Comando comando) {
NOME entidade = repository.buscarPorId(comando.id())
.orElseThrow(() -> new NOMENaoEncontradoException(comando.id()));
entidade.atualizar(comando.nome());
return repository.atualizar(entidade);
}
}Arquivo 7: src/main/java/com/laboratorio/financas/ARG/application/DeletarNOMEUseCase.java
package com.laboratorio.financas.ARG.application;
import com.laboratorio.financas.ARG.domain.NOMENaoEncontradoException;
import com.laboratorio.financas.ARG.domain.NOMERepository;
import java.util.UUID;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class DeletarNOMEUseCase {
private final NOMERepository repository;
public DeletarNOMEUseCase(NOMERepository repository) {
this.repository = repository;
}
@Transactional
public void executar(UUID id) {
repository.buscarPorId(id)
.orElseThrow(() -> new NOMENaoEncontradoException(id));
repository.deletar(id);
}
}Arquivo 8: src/main/java/com/laboratorio/financas/ARG/infrastructure/persistence/NOMEEntity.java
package com.laboratorio.financas.ARG.infrastructure.persistence;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import java.time.Instant;
import java.util.UUID;
@Entity
@Table(name = "ARG")
public class NOMEEntity {
@Id
@Column(name = "id", columnDefinition = "uuid", nullable = false, updatable = false)
private UUID id;
@NotNull
@Column(name = "user_id", columnDefinition = "uuid", nullable = false)
private UUID userId;
@NotNull
@Column(name = "nome", nullable = false, length = 100)
private String nome;
@NotNull
@Column(name = "ativo", nullable = false)
private boolean ativo;
@NotNull
@Column(name = "criado_em", nullable = false, updatable = false)
private Instant criadoEm;
@NotNull
@Column(name = "atualizado_em", nullable = false)
private Instant atualizadoEm;
protected NOMEEntity() {
// Construtor protected exigido pelo JPA.
}
public NOMEEntity(UUID id, UUID userId, String nome,
boolean ativo, Instant criadoEm, Instant atualizadoEm) {
this.id = id;
this.userId = userId;
this.nome = nome;
this.ativo = ativo;
this.criadoEm = criadoEm;
this.atualizadoEm = atualizadoEm;
}
public UUID getId() {
return id;
}
public UUID getUserId() {
return userId;
}
public String getNome() {
return nome;
}
public boolean isAtivo() {
return ativo;
}
public Instant getCriadoEm() {
return criadoEm;
}
public Instant getAtualizadoEm() {
return atualizadoEm;
}
}Arquivo 9: src/main/java/com/laboratorio/financas/ARG/infrastructure/persistence/NOMEJpaRepository.java
package com.laboratorio.financas.ARG.infrastructure.persistence;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
public interface NOMEJpaRepository extends JpaRepository<NOMEEntity, UUID> {
List<NOMEEntity> findByUserId(UUID userId);
}Arquivo 10: src/main/java/com/laboratorio/financas/ARG/infrastructure/persistence/NOMEMapper.java
package com.laboratorio.financas.ARG.infrastructure.persistence;
import com.laboratorio.financas.ARG.domain.NOME;
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public interface NOMEMapper {
default NOMEEntity toEntity(NOME domain) {
if (domain == null) {
return null;
}
return new NOMEEntity(
domain.getId(),
domain.getUserId(),
domain.getNome(),
domain.isAtivo(),
domain.getCriadoEm(),
domain.getAtualizadoEm()
);
}
default NOME toDomain(NOMEEntity entity) {
if (entity == null) {
return null;
}
return new NOME(
entity.getId(),
entity.getUserId(),
entity.getNome(),
entity.isAtivo(),
entity.getCriadoEm(),
entity.getAtualizadoEm()
);
}
}Arquivo 11: src/main/java/com/laboratorio/financas/ARG/infrastructure/persistence/NOMERepositoryImpl.java
package com.laboratorio.financas.ARG.infrastructure.persistence;
import com.laboratorio.financas.ARG.domain.NOME;
import com.laboratorio.financas.ARG.domain.NOMERepository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.stereotype.Component;
@Component
public class NOMERepositoryImpl implements NOMERepository {
private final NOMEJpaRepository jpaRepository;
private final NOMEMapper mapper;
public NOMERepositoryImpl(NOMEJpaRepository jpaRepository, NOMEMapper mapper) {
this.jpaRepository = jpaRepository;
this.mapper = mapper;
}
@Override
public NOME salvar(NOME entidade) {
return mapper.toDomain(jpaRepository.save(mapper.toEntity(entidade)));
}
@Override
public Optional<NOME> buscarPorId(UUID id) {
return jpaRepository.findById(id).map(mapper::toDomain);
}
@Override
public List<NOME> listarPorUserId(UUID userId) {
return jpaRepository.findByUserId(userId).stream()
.map(mapper::toDomain)
.toList();
}
@Override
public NOME atualizar(NOME entidade) {
return mapper.toDomain(jpaRepository.save(mapper.toEntity(entidade)));
}
@Override
public void deletar(UUID id) {
jpaRepository.deleteById(id);
}
}Arquivo 12: src/main/java/com/laboratorio/financas/ARG/interfaces/NOMEController.java
package com.laboratorio.financas.ARG.interfaces;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.laboratorio.financas.auditlog.domain.AuditAction;
import com.laboratorio.financas.auditlog.domain.AuditEvent;
import com.laboratorio.financas.auditlog.infrastructure.AuditPublisher;
import com.laboratorio.financas.ARG.application.AtualizarNOMEUseCase;
import com.laboratorio.financas.ARG.application.BuscarNOMEPorIdUseCase;
import com.laboratorio.financas.ARG.application.CriarNOMEUseCase;
import com.laboratorio.financas.ARG.application.DeletarNOMEUseCase;
import com.laboratorio.financas.ARG.application.ListarNOMEsUseCase;
import com.laboratorio.financas.ARG.domain.NOME;
import com.laboratorio.financas.ARG.interfaces.dto.AtualizarNOMERequest;
import com.laboratorio.financas.ARG.interfaces.dto.CriarNOMERequest;
import com.laboratorio.financas.ARG.interfaces.dto.NOMEResponse;
import com.laboratorio.financas.usuario.domain.UsuarioRepository;
import jakarta.validation.Valid;
import java.util.List;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
// TODO: ajustar /api/nomes para plural correto pt-BR se nao for o padrao default.
@RestController
@RequestMapping("/api/nomes")
public class NOMEController {
private static final Logger LOG = LoggerFactory.getLogger(NOMEController.class);
private static final String ENTITY_TYPE = "ARG";
private final CriarNOMEUseCase criarUseCase;
private final ListarNOMEsUseCase listarUseCase;
private final BuscarNOMEPorIdUseCase buscarPorIdUseCase;
private final AtualizarNOMEUseCase atualizarUseCase;
private final DeletarNOMEUseCase deletarUseCase;
private final UsuarioRepository usuarioRepository;
private final AuditPublisher auditPublisher;
private final ObjectMapper objectMapper;
public NOMEController(
CriarNOMEUseCase criarUseCase,
ListarNOMEsUseCase listarUseCase,
BuscarNOMEPorIdUseCase buscarPorIdUseCase,
AtualizarNOMEUseCase atualizarUseCase,
DeletarNOMEUseCase deletarUseCase,
UsuarioRepository usuarioRepository,
AuditPublisher auditPublisher,
ObjectMapper objectMapper) {
this.criarUseCase = criarUseCase;
this.listarUseCase = listarUseCase;
this.buscarPorIdUseCase = buscarPorIdUseCase;
this.atualizarUseCase = atualizarUseCase;
this.deletarUseCase = deletarUseCase;
this.usuarioRepository = usuarioRepository;
this.auditPublisher = auditPublisher;
this.objectMapper = objectMapper;
}
@GetMapping
public List<NOMEResponse> listar(Authentication authentication) {
UUID userId = resolverUserId(authentication);
return listarUseCase.executar(userId).stream()
.map(NOMEResponse::fromDomain)
.toList();
}
@GetMapping("/{id}")
public NOMEResponse buscar(
@PathVariable UUID id,
Authentication authentication) {
resolverUserId(authentication);
return NOMEResponse.fromDomain(buscarPorIdUseCase.executar(id));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public NOMEResponse criar(
@Valid @RequestBody CriarNOMERequest request,
Authentication authentication,
@RequestHeader(value = "X-Screen-Code", required = false) String screenCode) {
UUID userId = resolverUserId(authentication);
CriarNOMEUseCase.Comando comando = new CriarNOMEUseCase.Comando(userId, request.nome());
NOMEResponse response = NOMEResponse.fromDomain(criarUseCase.executar(comando));
auditPublisher.publish(new AuditEvent(
ENTITY_TYPE, response.id(), AuditAction.CREATE,
userEmail(authentication), screenCode, null, toJson(response)));
return response;
}
@PutMapping("/{id}")
public NOMEResponse atualizar(
@PathVariable UUID id,
@Valid @RequestBody AtualizarNOMERequest request,
Authentication authentication,
@RequestHeader(value = "X-Screen-Code", required = false) String screenCode) {
// Chamar sem atribuir -- so valida o usuario autenticado. NAO fazer
// `UUID userId = resolverUserId(...)` aqui: userId nao e usado em
// atualizar() (so o id do PathVariable importa) e SpotBugs rejeita
// como DLS_DEAD_LOCAL_STORE.
resolverUserId(authentication);
AtualizarNOMEUseCase.Comando comando = new AtualizarNOMEUseCase.Comando(id, request.nome());
NOMEResponse response = NOMEResponse.fromDomain(atualizarUseCase.executar(comando));
auditPublisher.publish(new AuditEvent(
ENTITY_TYPE, id, AuditAction.UPDATE,
userEmail(authentication), screenCode, null, toJson(response)));
return response;
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deletar(
@PathVariable UUID id,
Authentication authentication,
@RequestHeader(value = "X-Screen-Code", required = false) String screenCode) {
// Mesma regra do atualizar(): chamar SEM atribuir. SpotBugs DLS_DEAD_LOCAL_STORE.
resolverUserId(authentication);
deletarUseCase.executar(id);
auditPublisher.publish(new AuditEvent(
ENTITY_TYPE, id, AuditAction.DELETE,
userEmail(authentication), screenCode, null, null));
}
private UUID resolverUserId(Authentication authentication) {
String email = authentication.getName();
return usuarioRepository.buscarPorEmail(email)
.orElseThrow(() -> new IllegalStateException(
"Usuario autenticado nao encontrado: " + email))
.getId();
}
private String userEmail(Authentication authentication) {
return (authentication != null) ? authentication.getName() : null;
}
private String toJson(Object obj) {
if (obj == null) {
return null;
}
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException ex) {
LOG.warn("Falha ao serializar payload de audit log para {}", ENTITY_TYPE, ex);
return null;
}
}
}Arquivo 13: src/main/java/com/laboratorio/financas/ARG/interfaces/dto/CriarNOMERequest.java
package com.laboratorio.financas.ARG.interfaces.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record CriarNOMERequest(
@NotBlank @Size(max = 100) String nome
) { }Arquivo 14: src/main/java/com/laboratorio/financas/ARG/interfaces/dto/AtualizarNOMERequest.java
package com.laboratorio.financas.ARG.interfaces.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record AtualizarNOMERequest(
@NotBlank @Size(max = 100) String nome
) { }Arquivo 15: src/main/java/com/laboratorio/financas/ARG/interfaces/dto/NOMEResponse.java
package com.laboratorio.financas.ARG.interfaces.dto;
import com.laboratorio.financas.ARG.domain.NOME;
import java.util.UUID;
public record NOMEResponse(
UUID id,
UUID userId,
String nome,
boolean ativo,
String criadoEm,
String atualizadoEm
) {
public static NOMEResponse fromDomain(NOME domain) {
return new NOMEResponse(
domain.getId(),
domain.getUserId(),
domain.getNome(),
domain.isAtivo(),
domain.getCriadoEm().toString(),
domain.getAtualizadoEm().toString()
);
}
}Arquivo 16: src/test/java/com/laboratorio/financas/ARG/domain/NOMETest.java
package com.laboratorio.financas.ARG.domain;
import static org.assertj.core.api.Assertions.*;
import java.time.Instant;
import java.util.UUID;
import org.junit.jupiter.api.Test;
class NOMETest {
private static final UUID USER_ID = UUID.randomUUID();
@Test
void construtorCriacaoComArgumentosValidosCriaEntidade() {
Instant antes = Instant.now();
NOME entidade = new NOME(USER_ID, "Nome Valido");
Instant depois = Instant.now();
assertThat(entidade.getId()).isNotNull();
assertThat(entidade.getUserId()).isEqualTo(USER_ID);
assertThat(entidade.getNome()).isEqualTo("Nome Valido");
assertThat(entidade.isAtivo()).isTrue();
assertThat(entidade.getCriadoEm()).isBetween(antes, depois);
}
@Test
void construtorCriacaoComUserIdNuloLancaNullPointerException() {
assertThatNullPointerException()
.isThrownBy(() -> new NOME(null, "Nome"))
.withMessageContaining("userId");
}
@Test
void construtorCriacaoComNomeNuloLancaNullPointerException() {
assertThatNullPointerException()
.isThrownBy(() -> new NOME(USER_ID, null))
.withMessageContaining("nome");
}
@Test
void construtorCriacaoComNomeBlankLancaIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new NOME(USER_ID, " "))
.withMessageContaining("nome");
}
@Test
void desativarMudaAtivoParaFalseEAtualizaTimestamp() {
NOME entidade = new NOME(USER_ID, "Nome");
Instant antes = entidade.getAtualizadoEm();
entidade.desativar();
assertThat(entidade.isAtivo()).isFalse();
assertThat(entidade.getAtualizadoEm()).isAfterOrEqualTo(antes);
}
@Test
void atualizarMudaNomeEAtualizaTimestamp() {
NOME entidade = new NOME(USER_ID, "Nome Antigo");
entidade.atualizar("Nome Novo");
assertThat(entidade.getNome()).isEqualTo("Nome Novo");
}
}Arquivo 17: src/test/java/com/laboratorio/financas/ARG/application/CriarNOMEUseCaseTest.java
package com.laboratorio.financas.ARG.application;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import com.laboratorio.financas.ARG.domain.NOME;
import com.laboratorio.financas.ARG.domain.NOMERepository;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
class CriarNOMEUseCaseTest {
private static final UUID USER_ID = UUID.fromString("00000000-0000-0000-0000-000000000099");
private NOMERepository repository;
private CriarNOMEUseCase useCase;
@BeforeEach
void setUp() {
repository = Mockito.mock(NOMERepository.class);
useCase = new CriarNOMEUseCase(repository);
}
@Test
void executarCaminhoFelizSalvaERetornaEntidade() {
NOME salvo = new NOME(USER_ID, "Teste");
when(repository.salvar(any(NOME.class))).thenReturn(salvo);
CriarNOMEUseCase.Comando cmd = new CriarNOMEUseCase.Comando(USER_ID, "Teste");
NOME resultado = useCase.executar(cmd);
assertThat(resultado).isNotNull();
assertThat(resultado.getNome()).isEqualTo("Teste");
verify(repository, times(1)).salvar(any(NOME.class));
}
}Arquivo 18: src/test/java/com/laboratorio/financas/ARG/application/ListarNOMEsUseCaseTest.java
package com.laboratorio.financas.ARG.application;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.when;
import com.laboratorio.financas.ARG.domain.NOME;
import com.laboratorio.financas.ARG.domain.NOMERepository;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
class ListarNOMEsUseCaseTest {
private static final UUID USER_ID = UUID.fromString("00000000-0000-0000-0000-000000000099");
private NOMERepository repository;
private ListarNOMEsUseCase useCase;
@BeforeEach
void setUp() {
repository = Mockito.mock(NOMERepository.class);
useCase = new ListarNOMEsUseCase(repository);
}
@Test
void executarRetornaListaDoRepository() {
when(repository.listarPorUserId(USER_ID))
.thenReturn(List.of(new NOME(USER_ID, "A"), new NOME(USER_ID, "B")));
List<NOME> resultado = useCase.executar(USER_ID);
assertThat(resultado).hasSize(2);
assertThat(resultado).allMatch(n -> n.getUserId().equals(USER_ID));
}
}Arquivo 18b: src/test/java/com/laboratorio/financas/ARG/application/BuscarNOMEPorIdUseCaseTest.java
package com.laboratorio.financas.ARG.application;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.when;
import com.laboratorio.financas.ARG.domain.NOME;
import com.laboratorio.financas.ARG.domain.NOMENaoEncontradoException;
import com.laboratorio.financas.ARG.domain.NOMERepository;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
class BuscarNOMEPorIdUseCaseTest {
private static final UUID USER_ID = UUID.fromString("00000000-0000-0000-0000-000000000099");
private NOMERepository repository;
private BuscarNOMEPorIdUseCase useCase;
@BeforeEach
void setUp() {
repository = Mockito.mock(NOMERepository.class);
useCase = new BuscarNOMEPorIdUseCase(repository);
}
@Test
void executarComIdExistenteRetornaEntidade() {
NOME existente = new NOME(USER_ID, "Teste");
when(repository.buscarPorId(existente.getId())).thenReturn(Optional.of(existente));
NOME resultado = useCase.executar(existente.getId());
assertThat(resultado).isNotNull();
assertThat(resultado.getNome()).isEqualTo("Teste");
}
@Test
void executarComIdInexistenteLancaNOMENaoEncontradoException() {
UUID id = UUID.randomUUID();
when(repository.buscarPorId(id)).thenReturn(Optional.empty());
assertThatThrownBy(() -> useCase.executar(id))
.isInstanceOf(NOMENaoEncontradoException.class);
}
}Arquivo 19: src/test/java/com/laboratorio/financas/ARG/application/AtualizarNOMEUseCaseTest.java
package com.laboratorio.financas.ARG.application;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import com.laboratorio.financas.ARG.domain.NOME;
import com.laboratorio.financas.ARG.domain.NOMENaoEncontradoException;
import com.laboratorio.financas.ARG.domain.NOMERepository;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
class AtualizarNOMEUseCaseTest {
private static final UUID USER_ID = UUID.fromString("00000000-0000-0000-0000-000000000099");
private NOMERepository repository;
private AtualizarNOMEUseCase useCase;
@BeforeEach
void setUp() {
repository = Mockito.mock(NOMERepository.class);
useCase = new AtualizarNOMEUseCase(repository);
}
@Test
void executarComIdExistenteAtualizaNome() {
NOME existente = new NOME(USER_ID, "Antigo");
when(repository.buscarPorId(existente.getId())).thenReturn(Optional.of(existente));
when(repository.atualizar(any(NOME.class))).thenAnswer(inv -> inv.getArgument(0));
NOME resultado = useCase.executar(new AtualizarNOMEUseCase.Comando(existente.getId(), "Novo"));
assertThat(resultado.getNome()).isEqualTo("Novo");
verify(repository).atualizar(any(NOME.class));
}
@Test
void executarComIdInexistenteLancaNOMENaoEncontradoException() {
UUID id = UUID.randomUUID();
when(repository.buscarPorId(id)).thenReturn(Optional.empty());
assertThatThrownBy(() -> useCase.executar(new AtualizarNOMEUseCase.Comando(id, "X")))
.isInstanceOf(NOMENaoEncontradoException.class);
verify(repository, never()).atualizar(any());
}
}Arquivo 20: src/test/java/com/laboratorio/financas/ARG/application/DeletarNOMEUseCaseTest.java
package com.laboratorio.financas.ARG.application;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import com.laboratorio.financas.ARG.domain.NOME;
import com.laboratorio.financas.ARG.domain.NOMENaoEncontradoException;
import com.laboratorio.financas.ARG.domain.NOMERepository;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
class DeletarNOMEUseCaseTest {
private static final UUID USER_ID = UUID.fromString("00000000-0000-0000-0000-000000000099");
private NOMERepository repository;
private DeletarNOMEUseCase useCase;
@BeforeEach
void setUp() {
repository = Mockito.mock(NOMERepository.class);
useCase = new DeletarNOMEUseCase(repository);
}
@Test
void executarComIdExistenteDeleta() {
UUID id = UUID.randomUUID();
when(repository.buscarPorId(id)).thenReturn(Optional.of(new NOME(USER_ID, "A")));
useCase.executar(id);
verify(repository).deletar(id);
}
@Test
void executarComIdInexistenteLancaExcecaoENaoDeleta() {
UUID id = UUID.randomUUID();
when(repository.buscarPorId(id)).thenReturn(Optional.empty());
assertThatThrownBy(() -> useCase.executar(id))
.isInstanceOf(NOMENaoEncontradoException.class);
verify(repository, never()).deletar(any());
}
}Arquivo 21: src/test/java/com/laboratorio/financas/ARG/infrastructure/persistence/NOMERepositoryImplTest.java
package com.laboratorio.financas.ARG.infrastructure.persistence;
import static org.assertj.core.api.Assertions.*;
import com.laboratorio.financas.ARG.domain.NOME;
import com.laboratorio.financas.shared.AbstractIntegrationTest;
import com.laboratorio.financas.usuario.infrastructure.persistence.UsuarioEntity;
import com.laboratorio.financas.usuario.infrastructure.persistence.UsuarioJpaRepository;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
class NOMERepositoryImplTest extends AbstractIntegrationTest {
@Autowired
private NOMERepositoryImpl repository;
@Autowired
private NOMEJpaRepository jpaRepository;
@Autowired
private UsuarioJpaRepository usuarioJpaRepository;
private UUID userId;
@BeforeEach
void setUp() {
jpaRepository.deleteAll();
usuarioJpaRepository.deleteAll();
userId = criarUsuarioPersistido();
}
@AfterEach
void limpar() {
jpaRepository.deleteAll();
usuarioJpaRepository.deleteAll();
}
private UUID criarUsuarioPersistido() {
UUID id = UUID.randomUUID();
usuarioJpaRepository.save(new UsuarioEntity(
id, "teste+" + id + "@test.com", "hash", true,
Instant.now(), null, Instant.now()));
return id;
}
@Test
void salvarEBuscarPorIdRetornaEntidadePersistida() {
NOME entidade = new NOME(userId, "Teste");
repository.salvar(entidade);
Optional<NOME> resultado = repository.buscarPorId(entidade.getId());
assertThat(resultado).isPresent();
assertThat(resultado.get().getNome()).isEqualTo("Teste");
}
@Test
void listarPorUserIdRetornaSomenteDoUsuario() {
repository.salvar(new NOME(userId, "A"));
repository.salvar(new NOME(userId, "B"));
UUID outroUserId = criarUsuarioPersistido();
repository.salvar(new NOME(outroUserId, "Outro"));
List<NOME> resultado = repository.listarPorUserId(userId);
assertThat(resultado).hasSize(2);
assertThat(resultado).allMatch(n -> n.getUserId().equals(userId));
}
@Test
void deletarRemoveEntidade() {
NOME entidade = new NOME(userId, "Para deletar");
repository.salvar(entidade);
repository.deletar(entidade.getId());
assertThat(repository.buscarPorId(entidade.getId())).isEmpty();
}
}Arquivo 22: src/test/java/com/laboratorio/financas/ARG/interfaces/NOMEControllerTest.java
package com.laboratorio.financas.ARG.interfaces;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.laboratorio.financas.ARG.infrastructure.persistence.NOMEJpaRepository;
import com.laboratorio.financas.shared.AbstractAuthenticatedIntegrationTest;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
class NOMEControllerTest extends AbstractAuthenticatedIntegrationTest {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private NOMEJpaRepository jpaRepository;
@BeforeEach
void setUp() {
jpaRepository.deleteAll();
}
@AfterEach
void limpar() {
jpaRepository.deleteAll();
}
@Test
void postValidoRetorna201() throws Exception {
Map<String, Object> body = Map.of("nome", "Teste");
mockMvc.perform(comAuth(post("/api/nomes")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(body))))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").isNotEmpty())
.andExpect(jsonPath("$.nome").value("Teste"));
}
@Test
void postNomeBlankRetorna400() throws Exception {
Map<String, Object> body = Map.of("nome", " ");
mockMvc.perform(comAuth(post("/api/nomes")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(body))))
.andExpect(status().isBadRequest());
}
@Test
void getListaRetornaArray() throws Exception {
mockMvc.perform(comAuth(post("/api/nomes")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(Map.of("nome", "A")))));
mockMvc.perform(comAuth(get("/api/nomes")))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray());
}
@Test
void getPorIdExistenteRetorna200() throws Exception {
MvcResult r = mockMvc.perform(comAuth(post("/api/nomes")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(Map.of("nome", "Buscavel")))))
.andReturn();
String id = objectMapper.readTree(r.getResponse().getContentAsString()).get("id").asText();
mockMvc.perform(comAuth(get("/api/nomes/" + id)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.nome").value("Buscavel"));
}
@Test
void getPorIdInexistenteRetorna404() throws Exception {
mockMvc.perform(comAuth(get("/api/nomes/" + UUID.randomUUID())))
.andExpect(status().isNotFound());
}
@Test
void putExistenteRetorna200ComNomeAtualizado() throws Exception {
MvcResult r = mockMvc.perform(comAuth(post("/api/nomes")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(Map.of("nome", "Antigo")))))
.andReturn();
String id = objectMapper.readTree(r.getResponse().getContentAsString()).get("id").asText();
mockMvc.perform(comAuth(put("/api/nomes/" + id)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(Map.of("nome", "Novo")))))
.andExpect(status().isOk())
.andExpect(jsonPath("$.nome").value("Novo"));
}
@Test
void putInexistenteRetorna404() throws Exception {
mockMvc.perform(comAuth(put("/api/nomes/" + UUID.randomUUID())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(Map.of("nome", "X")))))
.andExpect(status().isNotFound());
}
@Test
void deleteExistenteRetorna204() throws Exception {
MvcResult r = mockMvc.perform(comAuth(post("/api/nomes")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(Map.of("nome", "Apagar")))))
.andReturn();
String id = objectMapper.readTree(r.getResponse().getContentAsString()).get("id").asText();
mockMvc.perform(comAuth(delete("/api/nomes/" + id)))
.andExpect(status().isNoContent());
}
@Test
void semAuthRetorna401() throws Exception {
mockMvc.perform(get("/api/nomes")).andExpect(status().isUnauthorized());
}
}Passo 3 -- Verificar criacao
Get-ChildItem -Recurse "src/main/java/com/laboratorio/financas/ARG/" -File | Select-Object FullName
Get-ChildItem -Recurse "src/test/java/com/laboratorio/financas/ARG/" -File | Select-Object FullNameConfirme que os 24 arquivos existem (16 producao + 8 testes). Se algum ausente: reporte qual falta e nao emita o relatorio de sucesso.
Passo 4 -- Guia de adaptacao (para o executor seguir DEPOIS desta skill)
Os 24 arquivos sao gerados com nome: String como campo placeholder. O dominio
real (Emprestimo, Lembrete, Investimento, etc.) tem campos especificos diferentes.
A adaptacao e trabalho real, mas SEGUIR A ORDEM ABAIXO reduz Updates redundantes
(observado nos smokes: cada arquivo "Updated" depois de "Written" custa tool
calls; planejar bem evita ate ~30% dessas iteracoes).
Ordem sistematica de adaptacao (top-down, fonte-de-verdade primeiro):
- domain/NOME.java -- definir TODOS os campos finais aqui. Validacoes, construtores, getters, equals. ESTA e a fonte de verdade do dominio.
- infrastructure/persistence/NOMEEntity.java -- espelhar campos de NOME.java com @Column, @Embedded, @Enumerated. Cada campo do domain tem uma coluna correspondente.
- infrastructure/persistence/NOMEMapper.java -- mapear os novos campos em toEntity() e toDomain(). Cada campo novo no domain precisa de uma linha aqui.
- interfaces/dto/CriarNOMERequest.java + AtualizarNOMERequest.java -- campos correspondentes aos do domain (sem id/timestamps/userId que vem do contexto), com @NotNull/@Size/@NotBlank conforme regra do dominio.
- interfaces/dto/NOMEResponse.java -- campos a expor + fromDomain() mapeia todos.
- application/Criar/Atualizar*UseCase.java -- atualizar o
record Comandocom os novos campos e oexecutar()para passa-los ao construtor do dominio. - interfaces/NOMEController.java -- passar os novos campos do Request ao Comando.
- migration V
<N>__cria_tabela_NOME.sql -- colunas espelhando NOMEEntity (precision Money = 19,2; tabelas em portugues singular; FK usuarioREFERENCES usuario(id)). - Tests (8 arquivos) -- adaptar assertions para os novos campos. Os tests
gerados testam
nome; substituir/adicionar testes para cada campo do dominio com validacao especifica (positivo, max, enum).
Regra de ouro: adapte um arquivo COMPLETAMENTE antes de ir para o proximo. NUNCA edite parcialmente e volte. Se descobrir que precisa de um campo novo, volte para o passo 1 (domain) e refaca a cascata. Voltar e re-editar parcialmente e o que causa os Updates redundantes.
Anti-pattern observado nos smokes (NAO fazer):
- Editar dominio com 2 campos, depois Entity, depois lembrar de 1 campo a mais, voltar para dominio, voltar para Entity = 4 Updates onde poderia haver 2.
- Editar Mapper antes de fechar Entity = re-edita Mapper quando Entity muda.
Adapter de uma vez todos os arquivos relacionados a um campo conceitual e
preferivel a adaptar todos os campos em um arquivo de cada vez. Ou seja,
quando adicionar o campo valor (Money), faca dominio + Entity + Mapper + DTOs
- Response para esse campo em sequencia. Nao misture com outros campos no meio.
Passo 5 -- Relatorio final
Bounded context 'ARG' criado (baseline completo, 24 arquivos = 16 producao + 8 testes).
Producao (16):
domain/NOME.java
domain/NOMERepository.java
domain/NOMENaoEncontradoException.java
application/CriarNOMEUseCase.java
application/ListarNOMEsUseCase.java
application/BuscarNOMEPorIdUseCase.java
application/AtualizarNOMEUseCase.java
application/DeletarNOMEUseCase.java
infrastructure/persistence/NOMEEntity.java
infrastructure/persistence/NOMEJpaRepository.java
infrastructure/persistence/NOMEMapper.java
infrastructure/persistence/NOMERepositoryImpl.java
interfaces/NOMEController.java (com auditoria + GET /{id})
interfaces/dto/CriarNOMERequest.java
interfaces/dto/AtualizarNOMERequest.java
interfaces/dto/NOMEResponse.java
Testes (8, cobrindo os 4 niveis -- secao 6 de crud-patterns):
domain/NOMETest.java (unit, JUnit + AssertJ)
application/CriarNOMEUseCaseTest.java (Mockito)
application/ListarNOMEsUseCaseTest.java (Mockito)
application/BuscarNOMEPorIdUseCaseTest.java (Mockito)
application/AtualizarNOMEUseCaseTest.java (Mockito)
application/DeletarNOMEUseCaseTest.java (Mockito)
infrastructure/persistence/NOMERepositoryImplTest.java (Testcontainers)
interfaces/NOMEControllerTest.java (MockMvc E2E, 7 cenarios)
PROXIMOS PASSOS (responsabilidade do executor):
1. Adaptar campos especificos do dominio (Money, enum, FK, M:N, soft-delete,
state machine etc) seguindo as secoes correspondentes de docs/crud-patterns.md
(1.2, 1.3, 1.6, 1.7, 5.2.1, 10.x). NAO ler outros bounded contexts.
IMPORTANTE: ao renomear/adicionar campos no NOME.java/NOMEEntity.java, ATUALIZAR
os testes correspondentes (NOMETest, *UseCaseTest, *RepositoryImplTest, *ControllerTest)
para refletir os novos campos. Os testes gerados ja cobrem o baseline -- expanda;
nao recrie.
2. Criar migration Flyway V<N>__cria_tabela_ARG.sql (numero reservado no prompt da task).
FK para usuario usa `REFERENCES usuario(id)` -- NAO `users(id)`.
3. Ajustar /api/nomes para plural pt-BR se necessario (ex: /api/lembretes)
4. Adicionar handler de NOMENaoEncontradoException no GlobalExceptionHandler
(secao 3 de crud-patterns.md -- ProblemDetail, NAO void)
5. Registrar ENTITY_TYPE 'ARG' no middleware de auditoria via /add-entity-to-audit
6. NAO invocar /write-test para os 4 niveis de teste backend -- ja foram criados.
/write-test so e necessario se a expansao do dominio (passo 1) adicionar um arquivo
novo de producao que merece teste proprio (ex: um value object com regra propria).Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

