# ADR-004 — Grep-friendly: prefixo de domínio único em todos os artefatos

| Campo | Valor |
|---|---|
| **Status** | Aceito (2026-10-16) |
| **Data** | 2026-10-14 |
| **Plano de origem** | `wiki/plan/01-analise-e-decisoes.md` |
| **Relacionadas** | ADR-013, ADR-017 |

## Contexto

No `sucesu-cms`/`prefeituras_v5`, rastrear uma feature exigia conhecer dezenas de nomes
diferentes. O `prefeituras_v5/.agents/rules/domain_isolation.md` mostra o valor de um
**token canônico**: uma única busca retorna a árvore completa da feature. Isso ajuda humanos e
agentes de IA (economia de contexto, menos alucinação).

## Decisão

Toda feature/módulo tem um **token canônico** (kebab-case, ex.: `diario-oficial`) e **todos** os
artefatos derivam dele, em cada camada e estilo de caso. Uma busca
(`grep -riE "diario[-_]?oficial"`) retorna a árvore completa — do banco à interface.

| Artefato | Estilo | Exemplo |
|---|---|---|
| Token / log `module` | kebab | `diario-oficial` |
| Pasta/namespace | Pascal | `src/modules/DiarioOficial/` |
| Classes (Model/Controller/Action/DTO/Request/Resource/Policy) | Pascal | `DiarioOficialPublicarAction` |
| Eventos | Pascal | `DiarioOficialPublicado` |
| Funções/variáveis | camel | `diarioOficialStore()`, `fn diarioOficialClean()` |
| Views/componentes | kebab | `diario-oficial-edit.blade.php` |
| Rotas | kebab/snake | `portal.diario-oficial.show` |
| Migrations/tabelas/colunas | snake | `diario_oficial_logs` |
| Testes (PHPUnit / E2E) | Pascal / kebab | `DiarioOficialPublicarActionTest`, `diario-oficial-publicacao.spec.ts` |
| Slots / permissões / assets | dot-kebab / snake-kebab | `diario-oficial.after-main`, `diario_oficial.publicar` |

**Lado público (Portal):** sufixo `Portal` (`DiarioOficialPortalController`,
`diario-oficial-portal-show.blade.php`, rota `portal.diario-oficial.show`).
**Abrangência:** todo o projeto (`modules`, `services`, `layout`, `core`).
**Exceções:** integração com legado (ex.: `User`) documentada no código.

## Emenda 2026-10-20 — identidade Pascal, formas derivadas

A implementação usa a **pasta/namespace PascalCase** (`DadosAbertos`, `Noticias`) como identidade
canônica; o `tools/name-check` e o `Adr004GrepFriendlyTest` normalizam separadores e caixa. Para não
criar um segundo nome "criativo", a decisão fica explícita:

| Forma | Uso | Exemplo |
|---|---|---|
| **Identidade canônica** | pasta, namespace, classe | `DadosAbertos` |
| **Token de busca** | `grep`/`name-check` (sem separador, minúsculo) | `dadosabertos` |
| **Externo/log/rota** | kebab (URL, evento, span) | `dados-abertos`, `dados-abertos.save` |
| **PHP/Lang/permissão** | snake (namespace i18n, tabela, permissão) | `dados_abertos`, `dados_abertos.ver` |

`Str::snake()` em `loadViewsFrom/loadTranslationsFrom` e em `Permission` é **derivação intencional**
(namespace de tradução/permissão), não um token divergente. Logs/spans usam **kebab**; um
`grep -riE "dados[-_]?abertos"` alcança as quatro formas. Um novo módulo segue a mesma derivação
(o gerador grava o rótulo do grupo nos 4 locales).

## Justificativa (alternativas descartadas)

- **Sem convenção** — descartada: foi o que tornou o legado não rastreável.
- **Nomes "naturais" por artefato** — descartada: quebra a busca única.

## Consequências

**Positivas**
- Uma busca acha tudo; onboarding e revisão mais rápidos; menos contexto para a IA.

**Custos / negativas**
- Disciplina de nomes; renomeações exigem consistência.

## Exemplo mínimo (comparativo)

```bash
# ❌ Nomes dispersos (difícil rastrear)
NoticiaController, noticias_index.blade.php, tb_news, LastNews

# ✅ Token canônico
noticiaController, noticia-index.blade.php, noticia, NoticiaUltimas
grep -riE "noticia" src/  # retorna a árvore completa
```

## Verificação

- `make check term=diario-oficial` retorna ≥1 arquivo por camada esperada.
- `tools/name-check` falha em arquivo de módulo sem o token.
- `tests/Feature/Adr004GrepFriendlyTest` (PHPUnit, roda no `make check`) garante, em **todo** o
projeto: token Pascal **único** e nome dos artefatos para as **features de domínio** (módulos e
services — tabela da ADR, com `@name-check-exception` documentado) e **localidade por namespace** +
**nomes de rota** para os concerns de **`core`/`layout`** (o objetivo é a busca única; aqui as
entidades — `Role`/`Permission`, `PanelMenu`, `JsonFormatter` — não derivam do nome do diretório).

## Referências

- `prefeituras_v5/.agents/rules/domain_isolation.md`; `wiki/specs/09-grep-friendly.md`.
