Templates¶
Templates prontos para copiar ao criar documentação numa aplicação. Os
arquivos-fonte vivem na pasta templates/ do repositório documentacao no
Forgejo — copie o arquivo, renomeie conforme a convenção e substitua o
conteúdo de exemplo.
| Template | Copiar para (no repo da aplicação) |
|---|---|
| Resumo Executivo (home do app) | docs/index.md |
| Documentação Completa (o livro) | docs/projeto/index.md |
| Etapa do projeto | docs/projeto/NN-titulo-curto.md |
| Modelo de dados (banco ou contrato de saída) | docs/projeto/modelagem.md |
| Mapeamento entrada↔dados (opcional) | docs/projeto/mapeamento.md |
| Guia do código (todo app com código) | docs/dev/guia-do-codigo.md |
| Decisão (registro de decisão) | docs/decisoes/NNNN-titulo-curto.md |
| Manual de usuário | docs/public/manual/titulo-da-tarefa.md |
| Runbook de operação (procedimento pontual) | docs/operacao/titulo-da-operacao.md |
| Runbook de implantação (sistema de pé) | docs/operacao/implantacao.md |
| Riscos consolidados (opcional) | docs/riscos.md (raiz de docs/) |
| Página pública da API (OpenAPI) | docs/public/api/index.md |
| mkdocs.yml canônico | mkdocs.yml (raiz do repo) |
| Identidade visual (CSS) | docs/stylesheets/xadm.css |
| Workflow de docs | .forgejo/workflows/docs.yml |
| Workflow de CI de código | .forgejo/workflows/ci.yml |
| Workflow de validação de release | .forgejo/workflows/release.yml |
| Metadados do app | docs/app.json |
| Changelog embutido no site | docs/changelog.md |
| Aviso de versão antiga (overrides) | overrides/main.html (de templates/overrides-main.html) |
| READMEs de anexos | docs/anexos/README.md e docs/anexos/privado/README.md |
| Kit de UI (app server-render, opcional) | src/main/resources/views/layout.html + public/css/custom-theme.css + public/images/{logo.png,favicon.ico} (de templates/views/** e templates/public/** — a árvore do kit espelha a do app). Junto vai o scaffold templates/app.css → public/css/app.css: CSS de componentes do app, copiado uma vez e nunca re-derivado |
| Container (app Java deployável) | Dockerfile + .dockerignore na raiz (de templates/dockerfile-java e templates/dockerignore-java) |
| Fim de linha (app Java/Gradle) | .gitattributes na raiz (de templates/gitattributes-java) |
| Checkstyle (app Java) | config/checkstyle/checkstyle.xml + suppressions.xml |
| Lints Dart (app Flutter/Dart) | analysis_options.yaml (raiz do repo) |
| .gitignore (app Flutter) | .gitignore (de templates/gitignore-flutter) |
| Skill /xadm-release | .claude/skills/xadm-release/SKILL.md |
| Skill /xadm-docs | .claude/skills/xadm-docs/SKILL.md |
| Skill /xadm-setup (Central de Apps) | .claude/skills/xadm-setup/SKILL.md |
| Skill /xadm-meta-audit-work | .claude/skills/xadm-meta-audit-work/SKILL.md |
| Skill /xadm-docx (opcional) | .claude/skills/xadm-docx/SKILL.md |
Skills de workflow (x-desenhar … x-documentar) |
diretório templates/x-<nome>/ → .claude/skills/x-<nome>/ |
| Hook de constituição | .claude/checa-constituicao.sh + hook SessionStart no .claude/settings.json |
| Regras de IA | bloco no topo do CLAUDE.md do repo |
Os marcados com <app> são a configuração canônica de publicação — troque <app>
pelo slug e não copie de outro app (clones divergem do padrão). Ver
como publicar e o checklist de app novo.
O frontmatter é obrigatório — campos e valores válidos estão na constituição.
Resumo Executivo — home do app (docs/index.md)¶
Porta do diretor (§2): problema + solução sem jargão + link para a Documentação
Completa. O marcador <!-- etapas --> vira as listas entregue/planejado no build.
<!--
TEMPLATE — RESUMO EXECUTIVO (a home do app, docs/index.md) — X-Adm
A porta do diretor (constituição §2): o problema e como se resolve, SEM jargão,
1–2 parágrafos, terminando em "→ Documentação Completa".
O marcador <!-- etapas --> é substituído no build pelas listas "entregue/planejado"
geradas do campo entrega: e do status de cada etapa (projeto/NN-*.md). Apague este
comentário e ajuste o texto.
-->
# BI Transporte — Processador XLS
A Vantroba recebia o faturamento de transporte em planilhas Excel e dependia de um
processo manual para levar esses dados ao BI. Este serviço **recebe a planilha,
valida e publica os dados automaticamente**, com rastreabilidade de cada envio — o
time para de depender de script manual e ganha histórico e segurança.
Para o desenho técnico completo, veja a **[Documentação Completa](projeto/)**.
## O que você encontra aqui
<!-- Mapa das seções (ordem do nav, por ciclo de vida). Apague as linhas das
seções que este app não tem. -->
- **[Documentação Completa](projeto/)** — o sistema inteiro, de cima a baixo (o livro).
- **Operação** — runbooks: como operar, recuperar, fazer deploy.
- **Dev / API** — para quem mexe no código: guia do código e referência da API.
- **Manual** (Público) — passo a passo para o usuário/suporte.
- **Etapas do Projeto** — o que foi entregue em cada etapa (a execução ao longo do tempo).
- **Decisões** — o *porquê* de cada escolha técnica (ADRs).
- **[Glossário](glossario.md)** — o vocabulário do domínio.
## Etapas
<!-- etapas -->
Etapa do projeto (doc de projeto)¶
---
titulo: "Importação de frota a partir de planilha XLS"
tipo: projeto
status: rascunho
responsavel: "Fulano da Silva <fulano@xadm.com.br>"
modulo: transporte
cliente: "Vantroba"
atualizado: 2026-06-09
capitulo: building-blocks # legado: sem função no livro autorado (§2)
entrega: "Importação da frota a partir da planilha do cliente"
entregue: false # opcional: a FEATURE está entregue? (≠ status do DOC). Ausente = status aprovado conta como entregue. "design aprovado, build pendente" = status: aprovado + entregue: false
tags: [bi, importacao, xls]
---
<!--
TEMPLATE DE ETAPA DO PROJETO — X-Adm
Copie para docs/projeto/NN-titulo-curto.md (NN = ordinal de duas casas; sem
PROJ-/ano — a data vai em decidido_em:/Git). Avulso vai em projeto/diversos.md.
Todo o conteúdo abaixo é EXEMPLO ilustrativo: substitua, mantendo as seções.
Seção sem conteúdo útil: apague. Seção vazia não existe.
Uma etapa por feature. **Tecnicamente completa PARA O SEU ESCOPO** (teste:
um dev/IA que nunca viu o código implementa o que esta etapa introduz/muda a
partir daqui + das etapas referenciadas). **Sem repetir:** o que já foi desenhado
antes, referencie (link); só re-enuncie o que ESTA etapa altera. Apague as seções
que seu escopo não toca. Detalhe que muda toda semana e mecânica interna não
entram (vão pro código, §1.2); passo a passo de usuário vai pro manual.
Esta etapa é o *delta* (o que ESTA feature muda) e o histórico; o desenho do sistema
NO ESTADO ATUAL vive na Documentação Completa autorada (projeto/index.md, §2), não é
montada daqui. entrega: = frase de negócio da etapa para o Resumo Executivo.
capitulo: é legado (sem função no livro autorado).
-->
# 01 — Importação de frota a partir de planilha XLS
## 1. Contexto e escopo
O cliente mantém o cadastro da frota em planilhas Excel e precisa desses dados
no banco do BI para alimentar os dashboards de transporte.
**Dentro do escopo:** upload da planilha pela web, validação das linhas,
gravação na tabela de frota, relatório de erros por linha.
**Fora do escopo:** edição da frota pela web (continua na planilha);
sincronização reversa banco → planilha.
**Integrações:** banco PostgreSQL do BI (escrita); PowerSync (leitura pelos
dashboards — ver doc de projeto do bi-transporte).
## 2. Arquitetura
Blocos/camadas que esta etapa cria ou toca e a regra de dependência entre eles.
O que vem de etapa anterior, referencie — não redesenhe.
## 3. Modelo de dados
**O *delta* que esta etapa faz no schema** — tabela que cria/altera: colunas, tipos,
chaves, índices, e **por quê**. O **estado consolidado** do banco não vive aqui: vive
em [`projeto/modelagem.md`](modelagem.md) (§5), que **esta etapa atualiza no mesmo
PR**. Tabela de etapa anterior que não muda: nem repita — está no `modelagem.md`.
```mermaid
erDiagram
PROCESSAMENTO_XLS ||--o{ FATURAMENTO : gera
PROCESSAMENTO_XLS {
bigint id PK
varchar checksum_sha256 UK "SHA-256 do binário; idempotência"
varchar status "PENDENTE|PROCESSANDO|SUCESSO|ERRO"
date periodo_inicial
date periodo_final
}
```
**`bi_processamento_xls`** — controle de cada upload.
| Coluna | Tipo | Chave/constraint | Nota |
|---|---|---|---|
| `id` | `BIGSERIAL` | PK | |
| `checksum_sha256` | `VARCHAR(64)` | UNIQUE, NOT NULL | idempotência (decisão 000N) |
| `status` | `VARCHAR(20)` | NOT NULL, default `PENDENTE` | máquina de estados (§4) |
| `periodo_inicial` / `periodo_final` | `DATE` | | período de referência |
(O `modelagem.md` é o dicionário de dados vivo consolidado; aqui fica só o delta
desta etapa.)
## 4. Fluxos e estados
```mermaid
flowchart LR
A[POST /processar] --> B{checksum já existe?}
B -- não --> C[persiste PENDENTE + 202]
B -- sim --> D[409]
C --> E[async: processa abas]
E --> F[SUCESSO | ERRO]
```
Caminho feliz + erros em poucas frases. Se há **máquina de estados**, declare-a
(estados, transições, o que dispara cada uma).
## 5. Contratos
O que esta etapa expõe/consome e outros precisam para integrar:
- **API:** endpoints (método, rota), request/response, status (202/400/401/409…),
auth. Detalhe vive em [`dev/api-rest`](../dev/api-rest.md) — aqui amarre o essencial.
- **Formatos de arquivo/integração:** ex. as abas do Excel e o **mapeamento
coluna→campo**; eventos publicados/consumidos.
## 6. Configuração
Envs/flags que **mudam o comportamento** (não toda env): ex. modo de storage,
token, destino de notificação. Cada uma: o que faz, valores, default.
## 7. Decisões
Resumo das decisões relevantes; o corpo de cada uma vive em `docs/decisoes/`.
**Não reafirme o status aqui** — ele é da decisão (fonte única, constituição
§1.9/§5): a tabela só linka; a própria decisão diz se está aprovada ou obsoleta.
O validador falha se a tabela contradiz o status real da decisão.
| Nº | Decisão |
|---|---|
| [0001](../decisoes/0001-processamento-assincrono.md) | Processamento assíncrono com fila em banco |
## 8. Riscos
- Planilhas fora do layout esperado → mitigação: validação por linha com
relatório, nunca falha o lote inteiro.
- Volume acima do estimado (>50k linhas) → mitigação: processamento em batches.
## 9. Critérios de aceite e teste
A ponte para quem valida — **não** um plano de QA nem um nível novo de doc. Regra: se
esta etapa não permite **implementar E testar**, falta informação; se repete código,
passou do ponto.
### Critérios de aceite
- [resultado **observável** que precisa ser verdade para a etapa estar pronta]
### Pontos para testar
- **Caminho feliz:** [o fluxo principal]
- **Erros e bordas:** [entrada inválida, limites, concorrência]
- **Regressões importantes:** [o que não pode quebrar — piso: nenhuma sem teste]
### Evidência esperada
- **Automatizada:** [unit / integração / widget / contrato / fronteiras] — roda no gate
da stack ([CI, gates e testes](https://docs.xadm.biz/engenharia/ci-testes/)); a
verificação exercita a plataforma real, não só fixture (constituição §6).
- **Manual:** [quando proporcional ao risco].
- **Artefato:** [fixture golden, screenshot, log, runbook executado].
### Impacto
- **No livro (`projeto/index.md`):** muda o estado atual? → atualizar **no mesmo PR**.
- Toca também: `modelagem.md` · API/contrato · manual · runbook? [marque o que se aplica].
**Observabilidade** (o que esta etapa expõe para operar — `/health`, métricas,
logs-chave) entra aqui ou no livro/runbook conforme o caso.
## 10. Glossário
Termos novos que esta feature introduz e que ainda não estão no
`docs/glossario.md` do projeto. Se não houver, apague a seção.
Decisão (registro de decisão)¶
---
titulo: "Processamento assíncrono com fila em banco"
tipo: decisao
status: aprovado
responsavel: "Fulano da Silva <fulano@xadm.com.br>"
modulo: transporte
atualizado: 2026-06-09
decidido_em: 2026-06-09
tags: [processamento, arquitetura]
---
<!--
TEMPLATE DE DECISÃO (REGISTRO DE DECISÃO) — X-Adm
Copie para docs/decisoes/NNNN-titulo-curto.md no repo da aplicação
(numeração sequencial: 0001, 0002, ...).
Todo o conteúdo abaixo é EXEMPLO ilustrativo: substitua, mantendo as seções.
~1 página: "decidimos X porque Y, considerando Z". Não é especificação.
decidido_em: data em que a decisão foi TOMADA (≠ atualizado:, que é a última
edição). Num registro retroativo (migração), use a melhor data conhecida —
NÃO copie a data da migração: isso quebra a promessa de "história datada".
Decisão aceita NÃO se reescreve, com UMA exceção (constituição §1.4):
- fato INCIDENTAL errado sobre o código (um número, uma property inexistente —
a decisão continua de pé) → corrige EM-LUGAR, com nota datada no corpo:
> **Correção AAAA-MM-DD:** o limite real é 50MB, não 100MB.
- premissa/raciocínio errado (a escolha poderia ter sido outra) → crie outra
decisão e marque esta com status: obsoleto + superado-por: "NNNN".
superado-por: / supersede: vão ENTRE ASPAS ("0016") — sem aspas o YAML lê o zero
à esquerda como octal (0016 → 14) e o número renderiza/valida errado. O validador
falha se vier sem aspas.
Alternativas consideradas: liste só as REALMENTE deliberadas. Não reconstrua
alternativas que nunca se cogitou (inventar deliberação é mentira, §1.2).
Título: descreva a decisão pelo PAPEL, não por nome de classe (ex. evite
"BulkUpserter", "ProcessamentoHelper" no título) — identificador de código em
título envelhece ao refactor; o validador avisa (constituição §5).
-->
# 0001 — Processamento assíncrono com fila em banco
## Contexto
O upload de planilhas grandes (até 50k linhas) estoura o timeout HTTP se
processado na própria requisição. Precisamos decidir como desacoplar o upload
do processamento.
## Decisão
Processar de forma assíncrona usando uma tabela de fila no próprio PostgreSQL
(status `PENDENTE → PROCESSANDO → SUCESSO/ERRO`), consumida por um job
agendado do Micronaut.
## Consequências
- O usuário recebe resposta imediata e acompanha o status pela tela de
processamentos.
- Sem dependência nova de infraestrutura (sem RabbitMQ/Redis) — menos coisa
pra operar no Coolify.
- Latência de até 1 ciclo do job (30s) entre upload e início do processamento —
aceitável para o caso de uso.
## Alternativas consideradas
- **Processar na requisição HTTP:** simples, mas estoura timeout em planilhas
grandes — descartado.
- **Fila dedicada (RabbitMQ):** mais robusto em alto volume, mas adiciona um
serviço pra manter; volume atual não justifica.
Manual de usuário¶
---
titulo: "Como importar a planilha de frota"
tipo: manual
status: aprovado
responsavel: "Fulano da Silva <fulano@xadm.com.br>"
modulo: transporte
cliente: "Vantroba"
atualizado: 2026-06-09
tags: [importacao, frota, xls]
---
<!--
TEMPLATE DE MANUAL DE USUÁRIO — X-Adm
Copie para docs/public/manual/titulo-da-tarefa.md no repo da aplicação
(o manual do usuário é público — vive em docs/public/, constituição §5).
Todo o conteúdo abaixo é EXEMPLO ilustrativo: substitua, mantendo o formato.
Regras de ouro:
- Título é a tarefa na voz do usuário ("Como fazer X").
- Cada seção ## responde UMA pergunta — isso alimenta a busca e o futuro
chatbot do ERP.
- Sem jargão técnico, sem nome de tabela/coluna, sem detalhe de arquitetura.
- Screenshots em docs/public/img/ (manual é público, §5): nome semântico quando 1 imagem por tela
(dre.png), senão <slug>-NN.png. Gere-as de forma REGENERÁVEL (não print
manual, que apodrece) — receita por stack em
https://docs.xadm.biz/padroes/screenshots-manual/.
-->
# Como importar a planilha de frota
Use esta tela quando precisar atualizar os veículos da frota no sistema a
partir da planilha Excel.
## Antes de começar
- Tenha a planilha de frota no formato padrão (a mesma que você já usa).
- Você precisa do perfil **Operador de BI** ou superior.
## Passo a passo
1. No menu lateral, clique em **Importações → Frota**.

2. Clique em **Enviar planilha** e selecione o arquivo `.xlsx` no seu
computador.
3. Confira o resumo (quantidade de linhas reconhecidas) e clique em
**Confirmar importação**.

4. Acompanhe o andamento na coluna **Status**. Quando aparecer
**SUCESSO**, os veículos já estão atualizados nos dashboards.
## Quando der erro, faça isso
**Status ERRO com mensagem "linhas inválidas":** clique no número da
importação para ver o relatório linha a linha. Corrija as linhas apontadas na
planilha e envie de novo — as linhas corretas não são duplicadas.
**A planilha não é aceita no envio:** verifique se o arquivo é `.xlsx`
(Excel moderno). Planilhas `.xls` antigas precisam ser salvas como `.xlsx`
antes do envio.
**O status fica em PENDENTE por mais de 5 minutos:** abra um chamado para o
suporte informando o número da importação.
Runbook de operação — procedimento pontual¶
O molde de "como executo esta operação": operação única, normalmente disparada por incidente, num app só (constituição §2).
---
titulo: "Reprocessar uma carga de frota travada"
tipo: operacao
status: aprovado
responsavel: "Fulano da Silva <fulano@xadm.com.br>"
modulo: transporte
cliente: "Vantroba"
atualizado: 2026-06-11
tags: [runbook, reprocessamento, frota]
---
<!--
TEMPLATE DE RUNBOOK — X-Adm (nível 5, constituição §2)
Copie para docs/operacao/titulo-da-operacao.md no repo da aplicação.
Todo o conteúdo abaixo é EXEMPLO ilustrativo: substitua, mantendo o formato.
Regras de ouro:
- Audiência é dev/ops da X-Adm: jargão técnico é permitido, prints opcionais.
- Teste de qualidade: um dev que nunca operou o app executa só com o documento.
- O "porquê/como funciona" vai no doc de projeto — aqui é só o procedimento.
- Operação destrutiva começa com o admonition de aviso (como abaixo);
operação inofensiva pode removê-lo.
- Runbook é referência VIVA (constituição §1.4): mudou o procedimento,
atualize o documento no mesmo PR.
-->
# Reprocessar uma carga de frota travada
!!! warning "Operação destrutiva"
O reprocessamento apaga as linhas já importadas dessa carga antes de
inserir de novo. Leia até o fim antes de executar pela primeira vez.
## O que é
Descarta o resultado parcial de uma importação de frota que travou no meio
(status `PENDENTE` há mais de 1h) e dispara o processamento de novo a partir
do arquivo original, que fica retido no bucket.
## Quando usar
- Importação presa em `PENDENTE` por mais de 1 hora (o suporte normalmente
reporta via manual "Como importar a planilha de frota").
- Após deploy que corrige bug de parsing, para reaproveitar o arquivo já
enviado sem pedir novo upload ao cliente.
## Pré-requisitos
- Acesso ao painel do Coolify (app `bi-transporte-xls`).
- `TOKEN` da API admin (env `API_BEARER_TOKEN` do app no Coolify).
- Número da importação travada (coluna **ID** na tela de importações).
## Passos
1. Confirme que a carga está realmente travada:
```bash
curl -s "$BASE/admin/importacoes/123" -H "Authorization: Bearer $TOKEN"
# status deve ser PENDENTE e atualizado_em > 1h atrás
```
2. Dispare o reprocessamento:
```bash
curl -X POST "$BASE/admin/importacoes/123/reprocessar" \
-H "Authorization: Bearer $TOKEN"
```
3. Acompanhe os logs no Coolify até aparecer `importacao 123 concluida`.
## Verificação
- A tela de importações mostra a carga com status **SUCESSO**.
- Os totais batem com o resumo do arquivo (linhas reconhecidas = inseridas).
## Reversão
Não há rollback automático: o reprocessamento é idempotente (apaga e insere
de novo). Se falhar no meio, é seguro executar os passos outra vez. Se o
arquivo original estiver corrompido no bucket, peça novo upload ao cliente
pelo fluxo normal do manual de importação.
Runbook de implantação — como o sistema fica de pé¶
O irmão do anterior, não o substituto: o eixo é "como este sistema fica de pé,
e como eu o desligo". Escreve topologia de operação (com diagrama — é o que o
operador lê primeiro), superfície de operação e variáveis por servidor com os pares
que têm de casar; o porquê daquela topologia — arquitetura, fluxo, modelo,
contratos — tem dono fora dele (constituição
§2, corolário o livro não é re-digitado no
runbook). Re-digitar é que é proibido: onde o operador precisa do fato na mesma
página, o runbook transclui do arquivo-dono (--8<--) em vez de linkar — não há
cópia para envelhecer. Cross-repo o --8<-- não alcança: aí é link ou <PLACEHOLDER>.
Sistema de um serviço só, sem kill switch, cabe no molde de procedimento — não infle.
As três regras do §2 (todas as vias de reversão · resultado esperado conferido no
código · contrato externo sem fonte do outro lado é <PLACEHOLDER>) estão
demonstradas no exemplo, com o porquê em comentário para apagar.
---
titulo: "Implantação da integração Thoms → WebStorm"
tipo: operacao
status: aprovado
responsavel: "Fulano da Silva <fulano@xadm.com.br>"
modulo: integracao
cliente: "Thoms"
atualizado: 2026-07-16
tags: [runbook, implantacao, deploy, integracao]
---
<!--
TEMPLATE DE RUNBOOK DE IMPLANTAÇÃO — X-Adm (nível 5, constituição §2)
Copie para docs/operacao/implantacao.md no repo da aplicação.
Todo o conteúdo abaixo é EXEMPLO ilustrativo: substitua, mantendo o formato.
Este é o IRMÃO do templates/runbook.md, não o substituto. O eixo é outro:
- runbook.md ......... "como executo esta operação" (procedimento pontual,
disparado por incidente, um app)
- implantacao.md ..... "como este sistema fica de pé, e como eu o desligo"
Sistema com um serviço só e sem kill switch cabe no runbook.md — não infle.
O QUE É SEU (escreva): topologia de OPERAÇÃO (o que sobe, onde, com que auth
— COM diagrama; é o que o operador lê primeiro), superfície, variáveis por
servidor com os pares que têm de casar, passos, verificação e reversão.
O QUE NÃO É SEU (constituição §1.9, um dono por fato): o PORQUÊ daquela
topologia — arquitetura, fluxo de processamento, modelo de dados, contratos.
Isso tem dono fora daqui. Mas RE-DIGITAR é que é proibido; TRANSCLUIR não é:
- fato com dono NESTE repo, que o operador precisa ver na mesma página
→ ;--8<-- "projeto/<fato>.md" (o fato não é copiado, é incluído no build;
muda com o dono, sem ninguém lembrar de nada. O ';' inicial ESCAPA o
marcador: sem ele, esta linha de exemplo seria transcluída de verdade —
o snippets processa até dentro de comentário HTML e de bloco de código.
No doc real, sem o ';'.)
- fato com dono neste repo que o operador NÃO precisa ver na hora → link
- fato com dono em OUTRO repo (contrato de terceiro) → link ou <PLACEHOLDER>,
NUNCA cópia — o --8<-- não atravessa repositório, e foi exatamente um
contrato de fora que virou exemplo-que-mente em produção
Transclua do ARQUIVO-DONO, nunca "a seção do livro": o livro é narrativa e ele
próprio embute os fatos (modelagem.md, mapeamento.md). Fato que dois documentos
mostram ganha arquivo próprio, no padrão dos snippets do livro — sem frontmatter,
headings em ###, declarado em exclude_docs: do mkdocs.yml (not_in_nav: não serve:
ele não tira do build, e o fragmento vira página órfã com H1 inventado).
E se o operador precisa de um diagrama DIFERENTE do que o livro tem, ele é um
fato diferente — escreva-o aqui (é o caso da Topologia de operação abaixo), não
transclua o do livro. Caso real: o livro de um app tem um Mermaid de arquitetura
ponta-a-ponta, autorado para quem quer ENTENDER o sistema; o operador com o
sistema fora do ar precisa de outro, de topologia de rede. Dois diagramas, dois
donos, nenhuma cópia.
EXEMPLO — o operador precisa do fluxo na mão para diagnosticar ("mudei o preço
e não chegou: em que hop parou?"). O dono vira docs/projeto/fluxo.md, embutido
no livro (cap. 5) E aqui; uma seção deste doc fica só com:
--8<-- "projeto/fluxo.md"
(sem o ';' — ver acima). Se um link basta, linke e não crie o arquivo-fato.
Atenção: com check_paths: true, snippet apontando arquivo inexistente FALHA o
build (SnippetMissingError) — crie o arquivo-fato antes de referenciá-lo.
AS TRÊS REGRAS QUE ESTE DOC TEM DE HONRAR (constituição §2, bullet Runbook):
1. Reversão enumera TODAS as vias, e o que cada desligar isolado NÃO para.
2. Todo passo de Verificação declara o resultado esperado, conferido no código.
3. Valor de contrato externo sem fonte do outro lado é <PLACEHOLDER>, não exemplo.
-->
# Implantação — integração de produtos Thoms → WebStorm
## O que é
Guia de implantação e operação da integração que leva preço e estoque dos produtos
do X-Adm (ERP da Thoms) ao e-commerce do parceiro WebStorm. Cobre o que subir, com
que variáveis, como conferir que subiu e como desligar.
O **desenho** — arquitetura, fluxo de processamento, idempotência, modelo de dados
— é do livro: [Documentação Completa](../projeto/index.md). Aqui não se re-digita.
## Quando usar
- Ao provisionar os dois serviços em produção (Coolify) pela primeira vez.
- Ao atualizar um deles, ou ao integrar um cliente novo do X-Adm.
- Ao precisar **desligar** a integração sem derrubar o ERP (§ Reversão).
## Pré-requisitos
- Banco `db_thoms` (PostgreSQL) provisionado e acessível aos dois apps.
- DNS dos dois subdomínios apontando para o Coolify.
- Secrets do §Variáveis à mão (cofre da X-Adm).
- Feature 008 do integrador (coluna `estoque.updated_at` + webhook de saída) no
build implantado.
## Topologia de operação
O que sobe, onde, e o que cada peça precisa. A **arquitetura** (por que 3 hops,
por que banco compartilhado) está no [livro, cap. 5](../projeto/index.md).
```mermaid
%% caption: O que se implanta — dois serviços, um banco, um externo
flowchart TB
INT["Integrador<br/>int.thoms.xadm.biz<br/>(recurso Coolify)"]
TRAD["Tradutor<br/>webstorm-ecom.thoms.xadm.biz<br/>(recurso Coolify)"]
DB[("db_thoms (PostgreSQL)<br/>compartilhado")]
WS["API WebStorm<br/>EXTERNO — fora do nosso controle"]
INT --> DB
TRAD --> DB
TRAD -->|"POST /sync/erp"| WS
```
| Peça | Onde | Auth | Quem é dono |
|---|---|---|---|
| Integrador | recurso Coolify, `int.thoms.xadm.biz` | Bearer no `/api/v1/**` | repo `xadm/integrador` |
| Tradutor | recurso Coolify, `webstorm-ecom.thoms.xadm.biz` | middleware do Coolify nas telas | este repo |
| `db_thoms` | Postgres compartilhado | usuário por app | integrador (dono do schema da réplica) |
| API WebStorm | **externo** | Bearer do parceiro | parceiro |
## Contrato de entrada
Quem alimenta o sistema: o X-Adm faz `PUT https://int.thoms.xadm.biz/api/v1/xadm`
com `Authorization: Bearer <API_BEARER_TOKEN>`.
**A forma completa** (campos, tipos, respostas por HTTP, exemplos) é do
[schema canônico do integrador](https://docs.xadm.biz/integrador/) — não se copia
aqui. O necessário para o passo de fumaça do §Verificação:
- Corpo: objeto com `Origem` (a rotina do X-Adm que originou a carga) e a tabela
`ESTOQUE` (array de produtos).
- `200` → `{requestId, resultado: "SUCESSO"}`; `401` → Bearer inválido/ausente.
> **A confirmar:** o valor de `<ORIGEM>` que a rotina de saída da Thoms usa.
> **Efeito de errar:** origem fora de `XadmService.KNOWN_ORIGENS` não é rejeitada,
> mas erro de processamento naquela origem **não** é roteado ao Sentry — a falha
> fica invisível. Alinhar com o dev do X-Adm antes de implantar.
<!--
Regra 3, o porquê: os valores acima que descrevem o LADO DE LÁ (o que a rotina do
X-Adm manda, o que a API do parceiro aceita) só entram como exemplo se vierem de
fonte do outro lado — doc do parceiro, contrato assinado, resposta capturada.
Javadoc "ex.:", seed de teste e whitelist nossa não confirmam contrato de terceiro:
concordam entre si porque descendem da mesma suposição. Sem fonte de lá:
<PLACEHOLDER> + bloco "A confirmar" com o efeito real de errar, como acima.
Exemplo de contrato é lido como contrato por quem implementa.
-->
## Superfície de operação
O que o operador olha, depois de implantado:
- `https://webstorm-ecom.thoms.xadm.biz/webstorm-ecom` — trilha de cada envio ao
parceiro (quando, produto, HTTP, resultado).
- `.../webstorm-ecom?atencao=true` — só o que precisa de olhar humano.
- **Detalhe** de uma linha — payload enviado + resposta; botão **Reprocessar**.
- `/health` nos dois serviços — `{status, versao}`.
## Variáveis de ambiente
Uma tabela por servidor. Segredos via secrets do Coolify, **nunca no repo**.
Nomes seguem o mapeamento Micronaut (`propriedade` → `MAIÚSCULA_COM_UNDERSCORE`).
### Integrador — `int.thoms.xadm.biz`
| Variável | Valor / descrição |
|---|---|
| `CLIENTE` | `Thoms` |
| `DATASOURCES_DEFAULT_URL` | JDBC do `db_thoms` |
| `DATASOURCES_DEFAULT_PASSWORD` | **segredo** — credencial do dono do schema |
| `API_BEARER_TOKEN` | **segredo** — exigido no `Bearer` do `PUT /api/v1/xadm` |
| `SENTRY_DSN` | DSN do GlitchTip (público; fonte: `docs/app.json`) |
| `WEBSTORM_WEBHOOK_ENABLED` | `true` (só no deploy Thoms) — liga o poke de saída |
| `WEBSTORM_WEBHOOK_TOKEN` | **segredo** — ver pares abaixo |
### Tradutor — `webstorm-ecom.thoms.xadm.biz`
| Variável | Valor / descrição |
|---|---|
| `DATASOURCES_DEFAULT_URL` | JDBC do **mesmo** `db_thoms` |
| `DATASOURCES_DEFAULT_PASSWORD` | **segredo** — read em `estoque`, write em `webstorm_ecom_*` |
| `WEBHOOK_TOKEN` | **segredo** — ver pares abaixo |
| `PARCEIRO_URL` | base da API WebStorm — o app faz `POST {url}/sync/erp` |
| `PARCEIRO_TOKEN` | **segredo** — Bearer da API WebStorm |
| `WEBSTORM_SWEEP_ENABLED` | `false` desliga o sweep (default: ligado). Lido no start: exige restart |
### Pares que têm de casar
Enumere explicitamente: valor que existe nos dois lados e diverge em silêncio é
falha de implantação que só aparece em produção.
| Ponta A | Ponta B | Sintoma se divergirem |
|---|---|---|
| `WEBSTORM_WEBHOOK_TOKEN` (integrador) | `WEBHOOK_TOKEN` (tradutor) | poke responde `401`; o sweep mascara (envia atrasado, ninguém nota) |
| `API_BEARER_TOKEN` (integrador) | Bearer do cliente X-Adm | `PUT` responde `401`; nada entra |
## Passos de implantação
1. **Banco.** Garantir `db_thoms` acessível aos dois apps; criar o usuário do
tradutor com `SELECT` em `estoque` e `ALL` nas `webstorm_ecom_*`.
2. **Integrador.** Deploy no subdomínio `int.thoms.xadm.biz`; setar as ENV.
3. **Tradutor.** Deploy deste repo em `webstorm-ecom.thoms.xadm.biz`; setar as ENV.
4. **Auth das telas.** Configurar o middleware do Coolify na frente do tradutor —
as telas não têm login no app.
5. **Cliente X-Adm.** Apontar a URL do integrador + o Bearer (§ Pares).
## Verificação
Todo passo declara o **resultado esperado**, conferido no código — não no que
parece razoável (ver a nota abaixo).
1. `GET /health` nos dois → `{status:"UP", versao:"…"}`.
2. `PUT /api/v1/xadm` com uma carga de teste → `200`, `resultado: "SUCESSO"`.
3. Abrir a trilha → o produto aparece com o resultado do parceiro.
4. **Escrita repetida.** Repetir o mesmo `PUT`, mesmo sem mudar valores →
**nova** linha na trilha. É o esperado: a versão é bumpada por **escrita**
(`@DateUpdated`, sem dirty-check), não por diff de conteúdo.
5. **Idempotência.** Dar **um** `PUT` e aguardar ≥ 2 ciclos de `SWEEP_INTERVALO`
**sem novo `PUT`** → segue **uma** linha só. Se aparecer linha nova sem `PUT`
no meio, a idempotência quebrou.
<!--
Regra 2, o porquê: "mesmo payload" ≠ "mesma versão". Onde a chave-versão é
timestamp de escrita (@DateUpdated, @LastModifiedDate, trigger de UPDATE),
duas escrituras iguais geram DUAS versões — um passo que manda "repita o PUT e
espere nenhuma linha nova" dá falso alarme no dia do deploy, e o operador não
tem como saber se é bug ou o doc. Idempotência por versão-timestamp prova-se com
UMA escrita e VÁRIOS ciclos do consumidor (passo 5), nunca com duas escritas.
Cheque cada resultado esperado contra o código antes de escrever o passo.
-->
## Reversão
**Desligar a integração sem derrubar o X-Adm.** O tradutor descobre mudança por
**duas vias independentes** — o poke e o sweep `@Scheduled` — e o poke dispara um
sweep **completo**. Logo, desligar **uma** via não para os envios:
| Caminho | Efeito |
|---|---|
| Parar o container do tradutor (Coolify) | **Para tudo.** Mais simples e garantido — prefira este |
| `WEBSTORM_WEBHOOK_ENABLED=false` **+** `WEBSTORM_SWEEP_ENABLED=false` | Para tudo, mantendo as telas no ar. Exige **os dois** |
| Só `WEBSTORM_WEBHOOK_ENABLED=false` | ⚠️ **Não desliga** — o sweep segue enviando a cada `SWEEP_INTERVALO` |
| Só `WEBSTORM_SWEEP_ENABLED=false` | ⚠️ **Não desliga** — cada poke ainda dispara um sweep completo |
Em qualquer caso o X-Adm segue postando ao integrador; a réplica continua
atualizada e o atraso sai quando religar.
**Reverter versão:** redeploy da tag anterior (Coolify). As migrations do tradutor
são aditivas; não tocam a réplica.
<!--
Regra 1, o porquê: kill switch não conferido contra o código é kill switch
imaginário — e ele é lido no pior dia possível, por quem não escreveu o sistema.
Sistema com mais de uma via de descoberta (webhook + sweep, fila + retry, cache +
origem) precisa desta tabela "caminho → efeito", com os caminhos que NÃO funcionam
marcados. Antes de escrever, siga cada flag no código até o ponto de envio e
confirme o que ela realmente corta. Sempre ofereça a via bruta (parar o container):
é a única que não depende de o resto do desenho estar certo.
-->
Documentação Completa — o livro (docs/projeto/index.md)¶
O esqueleto autorado do sistema no estado atual, em ordem lógica que termina
nos contratos (constituição §2). Você escreve o fio narrativo; o factual vem das
fontes — embute projeto/modelagem.md, injeta o changelog no <!-- GERADO:
changelog -->, e o <!-- RECONCILIAR: ... --> falha o build até reconciliar.
---
titulo: "Documentação Completa — <App>"
tipo: livro
status: aprovado
responsavel: "Fulano da Silva <fulano@xadm.com.br>"
cliente: "Vantroba" # só em app de cliente (§5); modulo: não se aplica ao livro
atualizado: 2026-06-16
---
<!--
TEMPLATE — DOCUMENTAÇÃO COMPLETA (o livro do sistema, docs/projeto/index.md) — X-Adm
Esqueleto AUTORADO do sistema NO ESTADO ATUAL, em ordem lógica que termina nos
contratos (constituição §2). NÃO é montado das etapas. Você escreve o fio narrativo;
o factual vem das FONTES (embeds, OpenAPI, changelog), não se re-digita. Regra de
corte: derivável do código/fonte ⇒ vem da fonte.
Marcadores (sozinhos na linha):
<!-- HUMANO: pergunta concreta --> ponto que você (humano) responde
<!-- RECONCILIAR: o quê --> transição não resolvida — FALHA o build
<!-- GERADO: changelog --> o build injeta etapas + decisões
Apague as seções que seu sistema não tem (sem banco: apague o embed do cap. 3).
Apague este comentário.
-->
# Documentação Completa
## 0. Capa — o sistema numa tela
<!-- HUMANO: figura de TOPOLOGIA + FLUXOS PRINCIPAIS (constituição §2, cap. 0): quem fala
com quem, o que entra, o que sai — com legenda. Mermaid por padrão; figura de capa
pode ser SVG autoral (§5 + templates §Figura autoral em SVG; exemplo vivo: a
topologia em docs.xadm.biz/documentacao/integracao/). Para cada fluxo, diga COMO
ACOMPANHAR: o mecanismo pelo qual um humano vê se rodou ou falhou (tela de status,
push no app, email de erro, GlitchTip). -->
## 1. Contexto e problema
<!-- HUMANO: que problema do cliente este sistema resolve? escopo e fora-de-escopo? -->
## 2. Dados de entrada
<!-- HUMANO: o que entra (planilha/fila/payload) e as regras de descarte de linha? -->
## 3. Modelo de dados
O estado atual do modelo (schema do banco e/ou contrato dos artefatos de saída) vem de
`projeto/modelagem.md` (§5). **Descomente** o embed abaixo quando o arquivo existir
(precisa de `pymdownx.snippets`, já no mkdocs.yml):
<!-- --8<-- "projeto/modelagem.md" -->
## 4. Mapeamento entrada↔dados
<!-- HUMANO: como cada parte da entrada vira cada campo do destino? -->
<!-- Opcional: fonte em projeto/mapeamento.md — descomente o embed: -->
<!-- --8<-- "projeto/mapeamento.md" -->
## 5. Fluxos e processamento
<!-- HUMANO: recepção, máquina de estados, e as camadas (api→service→repo→domain) -->
<!-- HUMANO: Regras arquiteturais guardadas por teste/lint — as fronteiras que o gate
enforça (ex. api não depende de repository; camada de infra não importa feature) e
o link para a regra testada. Se o app não tem guarda mecânica, descreva a fronteira
e diga que o piso é convenção+review. Engenharia: docs.xadm.biz/engenharia/ci-testes/ -->
## 6. Conceitos transversais e configuração
<!-- HUMANO: idempotência, auth, prefixos; e as variáveis do projeto (banco, GlitchTip, chaves) -->
## 7. Contratos públicos / API
A conclusão: as assinaturas que tudo acima justifica.
<!-- HUMANO: endpoints públicos + auth; embute/linka o OpenAPI gerado (docs/public/api/, §5) -->
## 8. Apêndice — Histórico
Como chegamos ao estado atual:
<!-- GERADO: changelog -->
Guia do código (docs/dev/guia-do-codigo.md)¶
A página Dev padrão de todo app com código (constituição §4, nível 4): a
narrativa autorada de como o código se organiza — o atalho do dev novo antes de
mergulhar no Javadoc/dartdoc. Esqueleto com blocos por stack (Java
package-by-feature / Flutter feature-first) para apagar. O <!-- GERADO: inventario
--> no fim é opcional e só rende inventário se o docs.yml do app tiver o passo
Javadoc/dartdoc que o preenche (ver como publicar); sem o passo,
o hook compartilhado remove a linha no build.
---
titulo: "Guia do código"
tipo: dev
status: aprovado
responsavel: "Fulano da Silva <fulano@xadm.com.br>"
modulo: transporte
cliente: "Vantroba"
atualizado: 2026-06-09
tags: [dev, arquitetura, codigo]
---
<!--
TEMPLATE — GUIA DO CÓDIGO (docs/dev/guia-do-codigo.md) — X-Adm
A página Dev padrão para todo app COM código (constituição §4, nível 4): a
narrativa AUTORADA de como o código se organiza — o atalho para um dev novo
"passar os olhos e entender" antes de mergulhar classe a classe no Javadoc/dartdoc.
Tudo abaixo é EXEMPLO: substitua pela realidade do seu app, mantendo a espinha
(retrato em 1 frase → organização → camadas → travas → por onde começar →
referência gerada). Apague os BLOCOS POR STACK que não forem o seu.
-->
# Guia do código
Audiência: dev que abriu o repositório e quer **passar os olhos e entender** como o
código está organizado, antes de mergulhar classe a classe. Para o *porquê* das
decisões, veja [`decisoes/`](../decisoes/0001-titulo-da-decisao.md); para o desenho
completo do sistema, a [Documentação Completa](../projeto/index.md); para
rodar/testar, [Como buildar e testar](como-buildar-e-testar.md).
> A árvore de classes navegável (Javadoc/dartdoc) fica em [`dev/api/`](api/),
> **gerada no build** a partir dos comentários do código — então acompanha o
> código. A narrativa abaixo é autorada.
## O retrato em uma frase
<!-- 1–3 linhas + um diagrama do fluxo de dados (Mermaid). Diga o que o app É e
como o dado flui — o leitor deve sair daqui com o modelo mental do todo. -->
```mermaid
flowchart LR
Entrada --> Nucleo[Núcleo / regra] --> Saida
```
## Como o código é organizado
<!-- A tabela dos pacotes/pastas de topo: o nome, se é feature ou base, e a
responsabilidade. É o mapa de mais alto nível. -->
<!-- BLOCO JAVA (package-by-feature) — apague se não for Java -->
Organização **package-by-feature**: cada funcionalidade é um pacote de topo que
**carrega suas próprias camadas** (Controller → Service → Repository), em vez de
pastas globais `controller/`/`service/`. O compartilhado vive em `comum`.
| Pacote de topo | É | Responsabilidade |
|---|---|---|
| `processamento` | feature | a feature central do app |
| `comum` | base | transversal: config, exception, dto, util |
<!-- /BLOCO JAVA -->
<!-- BLOCO FLUTTER (feature-first) — apague se não for Flutter -->
Organização **feature-first**: cada tela/painel é um pacote sob `lib/features/`
que carrega suas camadas; o compartilhado vive em `lib/_shared/` e o núcleo
transversal em `lib/core/`.
| Topo | É | Responsabilidade |
|---|---|---|
| `lib/features/NNN_nome/` | features | uma tela/painel por pacote |
| `lib/_shared/` | base | widgets, services, models compartilhados |
| `lib/core/` | núcleo | router, database, theme, config |
<!-- /BLOCO FLUTTER -->
## As camadas
<!-- O fluxo de uma requisição/ação pelas camadas e a regra de fronteira (ex.: a
entidade não cruza o controller; a borda fala DTO/view-model). -->
## Travas de arquitetura
<!-- Se houver guarda de fronteiras de arquitetura no gate (um teste de fronteiras
no ./gradlew check; um script/teste de imports no Flutter), liste os invariantes
que ela guarda. Apague a seção se não houver. Engenharia: docs.xadm.biz/engenharia/ci-testes/ -->
## Por onde começar a ler
1. <!-- o entrypoint -->
2. <!-- o mapa de montagem / DI / rotas -->
3. <!-- uma feature ponta a ponta -->
## Referência completa
A árvore de classes navegável (**Javadoc/dartdoc**, interna) fica em
[`dev/api/`](api/), gerada pelo CI de docs a cada publish. Use-a quando precisar do
detalhe de uma classe; a narrativa acima é o atalho para achar por onde entrar.
## Inventário das classes
<!-- OPCIONAL. O marcador abaixo é preenchido por UMA de duas vias (contrato em
padroes/publicar-docs.md): (a) um passo no docs.yml que substitui o marcador
no arquivo antes do build; ou (b) um hook do mkdocs do próprio app que o
preenche em on_page_markdown — neste caso liste-o ANTES do visao-tecnica.py em
hooks:, senão o hook central remove o marcador cru antes e o inventário some.
Sem nenhuma das vias, APAGUE este marcador e esta seção — o hook compartilhado
remove a linha no build, mas é mais honesto não prometer um inventário que não
existe. -->
<!-- GERADO: inventario -->
Modelo de dados (docs/projeto/modelagem.md)¶
O modelo consolidado do estado atual — schema do banco e/ou contrato dos
artefatos de saída/integração (JSON, payload), fonte da verdade (constituição §5).
O meio: ER Mermaid só para o overview de relacionamentos; o dicionário por
entidade (toda coluna/campo, obrigatoriedade, "desde vN") vai em tabela Markdown
— o erDiagram não expressa NOT NULL/chave composta e fica ilegível em tabela larga.
Fluxo de dados é opcional. Sem frontmatter — fragmento embutido no livro por
--8<--. Projeto com banco ou com contrato de dados é obrigado a tê-lo; toda
etapa que evolui o modelo evolui este arquivo no mesmo PR.
<!--
TEMPLATE + GUIA — MODELO DE DADOS (docs/projeto/modelagem.md) — X-Adm
Modelo CONSOLIDADO do estado ATUAL (constituição §5): fonte da verdade do
**schema do banco E/OU do contrato dos artefatos de saída/integração** (JSON,
payload, formato de arquivo). Embutido na Documentação Completa (projeto/index.md)
via `--8<-- "projeto/modelagem.md"`. TODA mudança que evolui o modelo atualiza ESTE
arquivo no MESMO PR. SEM frontmatter (é fragmento embutido, não página solta).
NÍVEL DOS TÍTULOS: autore no NÍVEL DO EMBED (este arquivo entra sob um `##` do livro,
então começa em `###` — NÃO em `#`). Nada rebaixa headings (nem o snippets do site nem
o export .docx); um `#` viraria Título 1 no meio do livro e quebraria o sumário (§5).
Projeto SEM banco mas COM contrato de saída: o erDiagram/dicionário descreve as
ENTIDADES/CAMPOS do artefato (ex. objeto do JSON), não tabelas — a mecânica é a mesma.
O MEIO (constituição §5) — use cada notação para o que ela faz bem:
• ER Mermaid = OVERVIEW de RELACIONAMENTOS (quem se liga a quem). Minimalista:
só PK/UK, não a lista de colunas. O erDiagram não expressa NOT NULL nem chave
composta e fica ilegível em tabela larga — não o use como dicionário.
• TABELA Markdown = DICIONÁRIO por tabela (toda coluna): Coluna | Tipo | Chave |
Nulo? | Desde | Nota. Chave composta, índices e CHECK vão em bullets abaixo.
• Fluxo de dados (flowchart) = OPCIONAL, quando ajuda a ver o caminho do dado.
Substitua o exemplo. Apague este comentário.
-->
O schema vive em PostgreSQL, versionado por Flyway (`V1..VN`; migration aplicada é
congelada — nunca editar, sempre uma nova). [Descreva aqui convenções de prefixo/
namespace se houver, ex. tabelas que sincronizam vs. locais.]
### Relacionamentos (overview)
```mermaid
erDiagram
upload ||--o{ fato : "produz no período"
upload {
bigserial id PK
text checksum UK
}
fato {
bigserial id PK
}
```
> **Sem FK física.** [Se for o caso] o vínculo é lógico (por período + UPSERT
> diff-aware), não por chave estrangeira — anote aqui o porquê.
### Fluxo de dados (opcional)
```mermaid
flowchart LR
origem[Origem] -->|entra| ingest[Recepção] --> tab[("tabela")]
```
### Dicionário por tabela
#### `fato` — [papel da tabela] · [sincroniza? local?]
| Coluna | Tipo | Chave | Nulo? | Desde | Nota |
|---|---|---|---|---|---|
| `id` | bigserial | PK | NOT NULL | V1 | |
| `valor` | numeric | | NOT NULL | V1 | |
| `dt` | date | | NOT NULL | V1 | |
| `placa` | varchar | | | V3 | desnormalizado |
- **Chave natural (UK)** `uq_fato_natural` — colunas `(...)` (`NULLS NOT DISTINCT`):
é a chave do UPSERT. *(o erDiagram não mostra chave composta — por isso vai aqui)*
- **Índices**: `dt`, `placa`.
- **CHECK** / regras: [se houver].
Mapeamento entrada↔dados (docs/projeto/mapeamento.md)¶
Fonte opcional do cap. 4 do livro: como o que entra (API, planilha, payload) vira
o que se guarda/emite — tabela "origem → destino + transformação". Sem frontmatter,
embutido por --8<-- (constituição §5).
<!--
TEMPLATE — MAPEAMENTO ENTRADA↔DADOS (docs/projeto/mapeamento.md) — X-Adm
Fonte OPCIONAL do cap. 4 do livro (constituição §2/§5): como o que ENTRA (resposta de
API, planilha, payload) vira o que se GUARDA/EMITE (tabela, artefato de saída).
SEM frontmatter — fragmento embutido na Documentação Completa via
`--8<-- "projeto/mapeamento.md"`. Evolui no MESMO PR que muda o mapeamento.
Substitua o exemplo.
-->
Cada campo da origem e para onde vai no destino, com a transformação aplicada.
| Origem (campo) | Destino (campo) | Transformação | Obrigatório? | Nota |
|---|---|---|---|---|
| `api.cliente.nome` | `saida.nome` | trim | sim | |
| `api.cliente.cpf` | `saida.documento` | só dígitos | sim | |
| `api.valor` | `saida.total` | centavos → reais | sim | |
| `api.data` | `saida.dt` | ISO → `dd/MM/yyyy` | não | default hoje |
- **Descarte de linha/registro:** [quando uma entrada é ignorada e por quê].
- **Campos sem origem:** [calculados/constantes no destino, e como].
Arquivo-fato: o padrão dos snippets¶
modelagem.md e mapeamento.md acima não são "páginas menores" — são o padrão da casa
para um fato, um dono, várias leituras (§1.9). Um fato que mais de um documento
precisa mostrar (não só citar) vira arquivo próprio e os leitores o embutem;
o livro faz isso, e o runbook de implantação
também pode. O fato não é copiado — é incluído no build, e muda junto com o dono.
Convenção do arquivo-fato: sem frontmatter (é fragmento, não página), headings em
### (encaixam sob o ## de quem embute), e declarado em exclude_docs: no
mkdocs.yml — senão o checa-nav.py o acusa de órfão, com razão: ele não é página.
exclude_docs:, não not_in_nav: — os dois calam o checa-nav, mas só o primeiro
tira do build. Com not_in_nav: o fragmento continua virando página: ganha um H1
inventado do nome do arquivo, fica alcançável por URL e entra na busca, devolvendo
o mesmo texto duas vezes (uma na página que o embute, outra na órfã). O --8<-- não se
importa — ele lê do disco, não do build. Quando a página que embute mora na mesma pasta dos
fatos, reincluia com !:
exclude_docs: |
/public/contratos/*.md
!/public/contratos/index.md
Transclui-se do arquivo-dono, nunca "da seção do livro": o livro é narrativa e ele próprio embute os fatos. E se dois leitores precisam de diagramas diferentes do mesmo sistema (a arquitetura para quem estuda, a topologia de rede para quem opera), são dois fatos — cada um com seu dono, nenhuma cópia. Reaproveitar o diagrama errado é pior que escrever o certo.
O --8<-- é processado antes do Markdown — inclusive dentro de bloco de código
O snippets é um pré-processador de linha: ele não sabe o que é bloco de código nem
comentário HTML. Um --8<-- sozinho na linha transclui nos dois — é por isso que as
fichas desta página conseguem exibir os templates dentro de ```markdown, e é a mesma
razão pela qual documentar a sintaxe descuidadamente transclui de verdade (aconteceu ao
escrever esta página).
Para mostrar o marcador em vez de executá-lo, escape com ; no início
(;--8<-- "arquivo.md") ou deixe-o inline com crases — texto antes na linha já
basta para não casar. E com check_paths: true (o nosso), snippet apontando arquivo
inexistente falha o build com SnippetMissingError: crie o arquivo-fato antes
de referenciá-lo. Falha ruidosa, de propósito — o oposto de um embed que some calado.
Figura autoral em SVG (quando o Mermaid não basta)¶
Mermaid é o default (constituição §5): fonte versionável, lint no CI, zero manutenção de
pixel. A exceção é a figura de capa — o poster que precisa caber numa tela de
diretoria (composição em grid, rowspan, mockups, legenda), layout que o motor automático
do Mermaid não entrega por design: lá a posição é do motor, nunca sua. Exemplo vivo
deste padrão: a topologia da integração
(docs/assets/topologia-integracao.svg).
A receita — cada item é uma armadilha real pisada ao construir a topologia:
- O SVG é a fonte (autorado à mão), vive em
docs/assets/<nome>.svg. A regra "nunca comitar o render" não se aplica: não há render, há autoria. - Mermaid-base obrigatório, junto. Um bloco
mermaidcom os mesmos nós e arestas, em admonition colapsado (??? info "Fonte da figura (Mermaid-base)"— requerpymdownx.details) logo abaixo da figura. É a fonte semântica: mudou a topologia, atualize o base PRIMEIRO e espelhe no SVG — a ordem vai em comentário no cabeçalho do próprio SVG. Olint-mermaidcontinua cobrindo o base (fence indentado é pego). - Wrapper
<div>e zero linha em branco. O arquivo abre com<div class="...">e fecha com</div>, sem nenhuma linha em branco no meio: o python-markdown não conhece<svg>como tag de bloco — sem o<div>, ou com linha em branco, ele fatia o SVG em<p>e a figura não aparece (DOM quebrado, verde em todos os gates). - Embutir via
--8<--, não<img>. Inline herda a tipografia da página (Inter); via<img>o SVG externo não carrega a webfont e cai no fallback. <style>só com classes prefixadas (ex..topo-*). SVG inline vira parte do documento — CSS sem prefixo vaza para o site inteiro.- Responsivo:
viewBox+style="max-width:…;width:100%;height:auto"no<svg>; nuncawidth/heightfixos. - Paleta e tipografia da casa: tokens do
xadm.css(royal#064490, accent#2a5b97, light#7092bb, dark#042c5e, prata#939495), Inter. Setas via<marker>reutilizável; convenção: cheia = fluxo de dados, tracejada = integração interna (a mesma da legenda). - Legenda embutida no rodapé da figura — o leitor de poster não lê a prosa ao lado.
- Ponto cego, declarado: nenhum gate valida o SVG em si — o build aceita qualquer
XML bem-formado e o
checa-admonitionnão olha desenho. Quem garante a figura é o olho de quem edita; por isso o Mermaid-base lintado é obrigatório, não decorativo.
Riscos consolidados — opcional (docs/riscos.md)¶
Página transversal para quando a diretoria/ops quer todos os riscos numa
página em vez de abrir cada projeto. Consolida (por link) a seção "5. Riscos"
de cada doc de projeto — não a substitui. Adicione ao nav para aparecer.
---
titulo: "Riscos do projeto (visão consolidada)"
atualizado: 2026-06-13
---
<!--
TEMPLATE OPCIONAL — RISK REGISTER TRANSVERSAL — X-Adm
Copie para docs/riscos.md (raiz de docs/, NÃO em projeto/) no repo do app.
Página opcional: use quando a diretoria/ops quer "a lista de riscos numa página"
em vez de abrir cada etapa. Sem ela, os riscos vivem na seção "5. Riscos" de cada
doc de etapa do projeto — esta página NÃO substitui aquelas, ela as CONSOLIDA.
Fonte única (constituição §1.9): o dono de cada risco é o doc de projeto; aqui
é índice. Mantenha por link, evite duplicar a descrição inteira. Se preferir
automação, use pymdownx.snippets para incluir a seção de riscos de cada etapa.
Frontmatter mínimo (página transversal, não é um dos seis níveis — o validador
não a cobra): titulo + atualizado. Para aparecer, adicione ao nav do mkdocs.yml
e/ou linke da home. Apague este comentário e o exemplo ao preencher.
-->
# Riscos do projeto
Visão consolidada dos riscos abertos. O detalhe e a mitigação de cada um vivem na
seção **5. Riscos** do doc de projeto indicado.
| Risco | Severidade | Projeto | Mitigação (resumo) |
|---|---|---|---|
| Planilhas fora do layout esperado | Média | [01 — Importação](projeto/01-importacao-frota.md) | Validação por linha; nunca falha o lote inteiro |
| Volume acima do estimado (>50k linhas) | Baixa | [01 — Importação](projeto/01-importacao-frota.md) | Processamento em batches |
| Vazamento do token de API | Alta | [02 — Integração API](projeto/02-integracao-api.md) | Bearer rotacionável; nunca logado |
> Severidade é leitura de diretor — ordene por ela. Risco fechado sai da tabela
> (o histórico fica no Git e no doc de projeto).
Página pública da API — OpenAPI/Swagger (docs/public/api/index.md)¶
Embute o Swagger UI a partir do openapi.yaml gerado pelo CI de docs do
código (constituição §5). Só para apps que expõem API.
<!--
TEMPLATE — PÁGINA PÚBLICA DA API (OpenAPI/Swagger) — X-Adm
Copie para docs/public/api/index.md no repo do app (constituição §5: o contrato
de API é PÚBLICO — vive em docs/public/).
O spec OpenAPI (openapi.yaml) é GERADO PELO CI DE DOCS a partir do código (não do
app deployado) e copiado para docs/public/api/openapi.yaml ANTES do build — ver
o passo "Gerar referência de API" no docs.yml. O arquivo gerado é .gitignore.
A tag abaixo (plugin mkdocs-swagger-ui-tag) embute o Swagger UI a partir do spec.
src é relativo a esta página. Apague este comentário e ajuste o texto.
O frontmatter NÃO é opcional aqui: página pública é página (§6, governança por
documento). Quem consome um contrato precisa justamente do que o cabeçalho mostra —
`atualizado:` diz se o contrato está vivo, `status:` se dá para confiar nele, e
`responsavel:` a quem perguntar. O hook frontmatter-cabecalho.py renderiza isso no
corpo da página; ele expõe o NOME do responsável, nunca o e-mail.
-->
---
titulo: "API REST — contrato público"
tipo: dev
status: aprovado
responsavel: "Nome <email@xadm.com.br>"
modulo: "..."
atualizado: AAAA-MM-DD
tags: [api, contrato, rest]
---
# API REST — contrato público
Esta é a referência pública da API: endpoints, parâmetros, exemplos. Para
**experimentar ao vivo** (com seu ambiente), o app também serve `/swagger-ui` em
runtime; a **fonte publicada** da doc é esta página, gerada do código no pipeline.
## Como autenticar
A escrita exige um token Bearer (`Authorization: Bearer <token>`); as leituras
são públicas. Peça o token à equipe X-Adm.
## Referência
<swagger-ui src="openapi.yaml"/>
README de anexos públicos (docs/anexos/README.md)¶
# Anexos públicos
Tudo nesta pasta é **publicado no portal de documentação**
([docs.xadm.biz](https://docs.xadm.biz)) junto com o site deste app —
colocar um arquivo aqui é **decidir torná-lo público**.
Pode morar aqui: modelo de planilha para download, PDF de apoio, exemplo de
arquivo de importação **com dados fictícios**.
**Não** pode: dado real de cliente, credencial, massa de dados de produção —
isso vai na subpasta `privado/` (segmento reservado, **sempre** fora do build e
do portal; veja o README dela no repo). Atenção: o validador só bloqueia massa
de dados em `docs/public/` — **fora de `privado/`, um arquivo de dados vaza no
publish, e isso é responsabilidade do autor** (constituição §5).
Padrão: [constituição §5, "Anexos"](https://docs.xadm.biz/documentacao/constituicao/#5-convencoes)
README de anexos confidenciais (docs/anexos/privado/README.md)¶
# Anexos confidenciais — NÃO publicados
Nada desta pasta sai no portal de documentação: o `mkdocs.yml` exclui a
pasta do build (`exclude_docs`), o CI falha se ela aparecer no site e o
portal central recusa servir o caminho. Os arquivos ficam **versionados no
Git** e acessíveis só a quem tem login no Forgejo.
Pode morar aqui: insumo real do projeto — ex. a planilha-base com dados
reais do cliente que fundou o pré-projeto.
Para citar um destes arquivos numa página pública, use o link do Forgejo
(`https://fonte.xadm.biz/xadm/<repo>/src/branch/<branch>/docs/anexos/privado/<arquivo>`)
sempre com a nota: **"Arquivo confidencial — acesso restrito à equipe"**.
Padrão: [constituição §5, "Anexos"](https://docs.xadm.biz/padroes/constituicao/#5-convencoes)
mkdocs.yml canônico do app¶
# Template canônico de mkdocs.yml para apps — copie para a raiz do repo do app
# e troque <app> pelo slug do app (confira o slug: ele é o ID público, para sempre).
# Fonte da verdade: https://fonte.xadm.biz/xadm/documentacao (templates/mkdocs.yml)
# Padrão: https://docs.xadm.biz/padroes/publicar-docs/
site_name: <app>
site_url: https://docs.xadm.biz/aplicacoes/<app>/
theme:
name: material
language: pt-BR
# overrides/main.html traz o aviso de "versão antiga" do seletor de versão
# (doc versionada — decisão 0004). Copie templates/overrides-main.html para
# overrides/main.html no repo do app.
custom_dir: overrides
palette:
primary: custom
accent: custom
features:
- navigation.sections
- navigation.path # breadcrumbs: "onde estou" ao chegar via busca
- navigation.footer # links Anterior/Próximo no rodapé (ler como livro, §1.7)
- search.suggest
- search.highlight
# Logo/título do header voltam para o portal central (constituição §5)
extra:
homepage: https://docs.xadm.biz/
# Seletor de versão no topo (doc versionada por release — decisão 0004 / spec 011).
# O docs.yml builda cada versão com site_url terminando na versão e publica em
# docs-sites/<app>/<versão>/; o publica-versao.py monta o versions.json na raiz.
version:
provider: mike
# Identidade visual X-Adm (azul royal + prata, derivada do logo) — copie docs/stylesheets/xadm.css
# de um app existente ou do repo central.
extra_css:
- stylesheets/xadm.css
# ▲ UMA ÚNICA chave `exclude_docs:` no arquivo — YAML não avisa sobre chave
# repetida: a última **sobrescreve** a primeira em silêncio, e o `--strict` fica
# verde. Uma segunda chave aqui embaixo apagaria o `privado/` e publicaria
# confidencial de cliente (aconteceu: constituição 0.24.0→0.24.2). Precisa excluir
# mais alguma coisa? Some uma linha a ESTE bloco; nunca abra outro.
#
# privado/ é segmento RESERVADO: qualquer pasta privado/ em docs/ é
# confidencial e jamais sai no site (constituição §5) — NÃO remova esta
# exclusão. anexos/privado/ é o lar padrão. Doc legada que ainda não migrou
# ao padrão pode ser listada no mesmo bloco (migração oportunista).
#
# Arquivo-fato NÃO é página: é fragmento que uma página embute com `--8<--`
# (constituição §1.9). Por isso `exclude_docs` e não `not_in_nav` — este último só
# cala o aviso de órfã, e o fragmento continua sendo BUILDADO: vira página pública
# com H1 inventado do nome do arquivo ("Xadm ingest") e entra na busca, devolvendo
# o mesmo texto duas vezes (uma na hospedeira, outra na órfã). O `--8<--` continua
# transcluindo normalmente — ele lê do disco, não do build.
# _importado/ = fatos baixados de OUTROS repos no build; efêmeros (.gitignore).
# Sem isto, o site do consumidor serve o contrato do dono como se
# fosse página dele — a cópia que a §1.9 existe para matar.
# public/contratos/ = fatos DESTE repo que outros consomem; quem os mostra é a
# hospedeira `public/contratos/index.md` (publicar-docs.md).
exclude_docs: |
privado/
/_importado/**
/public/contratos/*.md
!/public/contratos/index.md
# Cabeçalho de governança (Status/Responsável/Atualizado) a partir do
# frontmatter — o docs.yml baixa o hook do site central antes do build
# (fonte única, igual ao valida-frontmatter.py). NÃO remova o hooks: sem
# remover também o passo de download no docs.yml, senão o build falha.
hooks:
- scripts/frontmatter-cabecalho.py
# Documentação Completa: processa os marcadores do livro AUTORADO
# (projeto/index.md, constituição §2) — injeta <!-- etapas --> (Resumo
# Executivo) e <!-- GERADO: changelog --> (livro), e FALHA o build em
# <!-- RECONCILIAR --> não resolvido. O docs.yml baixa este hook do site
# central antes do build (igual ao de cima).
- scripts/visao-tecnica.py
plugins:
- search:
lang: pt
# OpenAPI público: a página docs/public/api/index.md embute o spec com
# <swagger-ui src="openapi.yaml"/>. Só relevante se o app expõe API (§5).
- swagger-ui-tag
# Página única / PDF (constituição §2): concatena na ordem do nav — a Visão
# técnica vem primeiro, então lê como overview → detalhes. DEVE ser o ÚLTIMO
# plugin — depois do swagger-ui-tag; fora de ordem o print-site quebra o
# `mkdocs build --strict`.
- print-site:
add_to_navigation: true
print_page_title: "Tudo numa página"
# Referência de API gerada (Javadoc/dartdoc) é copiada para dentro de docs/ antes
# do build (docs.yml) e tem centenas de arquivos — declará-los aqui evita o ruído
# "não está no nav" no --strict. O link para ela é Markdown normal (valida).
# `not_in_nav` é o certo aqui: o Javadoc PRECISA sair no site, só não no índice.
not_in_nav: |
/dev/api/**
markdown_extensions:
- admonition # blocos !!! note / warning
- attr_list # atributos {...} — botões, classes
- def_list # definition lists — o glossário usa
- tables
- toc:
permalink: true
# Embeds --8<-- : a Documentação Completa (projeto/index.md) embute
# projeto/modelagem.md (modelo de dados). base_path inclui docs/ → o caminho
# do embed é relativo a docs/ (ex. "projeto/modelagem.md").
- pymdownx.snippets:
base_path: ["docs", "."]
check_paths: true
# Mermaid nativo do Material — não precisa de plugin extra
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
# nav por CICLO DE VIDA (constituição §2/§4): Resumo → Pré-projeto → Projeto →
# Operação → Dev → Público → Etapas → Decisões → Glossário. Mantenha só as seções
# que existem; descomente conforme criar o conteúdo. Resumo, Projeto (o livro) e
# Glossário já vêm ligados; o resto é esqueleto na ordem recomendada.
nav:
- Resumo Executivo: index.md # porta do diretor: problema + como resolve, sem jargão, → Documentação Completa (§2)
- Mudanças: changelog.md # embute o CHANGELOG.md da raiz (§1.8) — crie docs/changelog.md do templates/changelog.md
# - Pré-projeto: # docs/pre-projeto/ — viabilidade/estudo de caso, ANTES de decidir construir (§4, nível 1)
# - Estudo de caso: pre-projeto/estudo-de-caso.md
- Projeto: # a ESPECIFICAÇÃO — o desenho do sistema no estado atual
- Documentação Completa: projeto/index.md # o livro AUTORADO; embute projeto/modelagem.md via --8<-- (§2)
# - Requisitos / spec: projeto/requisitos.md
# projeto/modelagem.md (modelo de dados) é embutido no livro — sem entrada própria.
# - Operação: # docs/operacao/ — runbooks técnicos: deploy, recuperação (§4, nível 5)
# - Runbook Y: operacao/runbook-y.md
# - Dev / API: # docs/dev/ — para quem mexe no código (§4, nível 4)
# - Guia do código: dev/guia-do-codigo.md # narrativa autorada da organização do código (+ inventário, se houver passo Javadoc/dartdoc no docs.yml)
# - Mapa do código: dev/index.md # linke a API gerada: [API Reference](api/index.html) — Markdown NORMAL (api/ é gerado em docs/dev/api/ ANTES do build → --strict valida; §5)
# - Público: # docs/public/ — aberto (§5)
# - Visão geral: public/index.md
# - Manual:
# - Como fazer X: public/manual/como-fazer-x.md
# - API (contrato): public/api/index.md # embute o OpenAPI via swagger-ui-tag
# - Etapas do Projeto: # docs/projeto/NN-*.md — a EXECUÇÃO: o delta de cada etapa ao longo do tempo. ≠ Projeto, que é a spec/desenho (§5)
# - 01 — Primeira etapa: projeto/01-primeira-etapa.md
# - Decisões:
# - 0001 — Título da decisão: decisoes/0001-titulo-da-decisao.md
- Glossário do projeto: glossario.md
Identidade visual (docs/stylesheets/xadm.css)¶
Fonte única da identidade X-Adm (azul royal #064490 + prata #939495,
derivados do logo real; links de conteúdo identificáveis). Copie verbatim para
docs/stylesheets/xadm.css; o mkdocs.yml já referencia em extra_css. Mudança
visual se faz aqui no central e re-deriva nos apps — não edite o CSS por app.
/* Identidade visual X-Adm para o MkDocs Material — cópia derivada.
* A FONTE-DA-VERDADE da paleta é docs/stylesheets/xadm.css do repo central
* (docs.xadm.biz). Copie este arquivo para docs/stylesheets/xadm.css no repo do
* app (mkdocs.yml já referencia em extra_css). Se a identidade mudar, mude no
* repo central e re-derive as cópias na migração oportunista — não edite o css
* por app.
*
* Tokens derivados das cores REAIS do logo (amostrado 2026-07-01):
* azul royal #064490 o "X" e as letras (a marca) -> primária
* azul claro #7092bb / escuro #042c5e (gradiente do azul) -> light / dark
* azul médio #2a5b97 (gradiente do azul) -> accent (links/hover)
* prata/cinza #939495 bevel 3D -> neutro (bordas/superfícies)
* Antes: navy #34495e + burgundy #8b3a3a — o burgundy NÃO existe no logo
* (reconciliado 2026-07-01, feedback §6: a identidade deriva do logo).
*/
:root {
--md-primary-fg-color: #064490;
--md-primary-fg-color--light: #7092bb;
--md-primary-fg-color--dark: #042c5e;
--md-accent-fg-color: #2a5b97;
--md-accent-fg-color--transparent: rgba(42, 91, 151, 0.1);
/* neutro da marca (uso opcional em bordas/superfícies) */
--xadm-silver: #939495;
--xadm-silver-light: #e2e6eb;
}
/* Links de conteúdo identificáveis. O Material não sublinha links inline e o
* azul da marca se confunde com o texto — então no CORPO do texto o link ganha
* o accent + sublinhado. Exclui os headerlinks (¶) e os links de navegação
* (tabs, menu, breadcrumb), que têm estilo próprio do tema. */
.md-typeset a:not(.headerlink) {
color: var(--md-accent-fg-color);
text-decoration: underline;
text-underline-offset: 0.15em;
}
.md-typeset a:not(.headerlink):hover {
text-decoration: underline;
}
Kit de UI para apps server-render (opcional)¶
App que renderiza HTML no servidor (Micronaut Views/Thymeleaf) — telas de admin/ops/dev,
inclusive internas e atrás do gate de auth — usa o layout padrão X-Adm em vez de
reinventar o visual (constituição §5, decisão 0010). O kit — layout.html,
custom-theme.css (mesma paleta do xadm.css), logo.png, favicon.ico — é template
rastreado opcional (app headless/Flutter não o instala). Receita, mapeamento de
destino e snippet de static-resources em
Java/Micronaut §UI.
Workflow de docs (.forgejo/workflows/docs.yml)¶
# Template canônico do workflow de docs — copie para .forgejo/workflows/docs.yml
# no repo do app e troque <app> pelo slug do app (o mesmo do mkdocs.yml).
# Fonte da verdade: https://fonte.xadm.biz/xadm/documentacao (templates/docs.yml)
# Padrão: https://docs.xadm.biz/padroes/publicar-docs/
#
# O site central docs.xadm.biz sincroniza do Garage em ~90s — nada mais a fazer.
#
# Secrets (DOCS_S3_ENDPOINT / DOCS_S3_ACCESS_KEY / DOCS_S3_SECRET_KEY): já
# existem na organização xadm do Forgejo — repo na org herda, nada a criar.
# Aviso de falha do CI: webhook nativo da org (nada no YAML) — forgejo.md.
#
# Versões da toolchain: NÃO mude os pins aqui — a fonte da verdade é
# https://docs.xadm.biz/padroes/publicar-docs/ (seção "Toolchain").
name: docs
on:
push:
branches: [master] # master → publica a versão "dev" (preview, fora do dropdown)
tags: ["v*"] # vX.Y.Z → snapshot da release (doc versionada, decisão 0004)
jobs:
publicar-docs:
runs-on: docker
# Imagem de CI da org POR STACK+VERSÃO (fábrica — decisão 0003): toolchain
# pré-instalada, sem apt-install/setup-java/git-clone por run. Aponte a da SUA
# stack (a mesma versão do ci.yml / docs/app.json toolchain.*):
# Java/Micronaut (gera Javadoc) → fonte.xadm.biz/xadm/ci-java:<v>
# Flutter (gera dartdoc) → fonte.xadm.biz/xadm/ci-flutter:<v>
# docs puro (MkDocs) → fonte.xadm.biz/xadm/ci-base
# node vem em todas (actions JS — checkout). Bootstrap: ci-images/README.md.
container: fonte.xadm.biz/xadm/ci-base
steps:
- name: Cache da toolchain de docs (pip + npm)
uses: actions/cache@v4
with:
path: |
~/.cache/pip
~/.npm
# Acelera o pip do mkdocs e o npm do mermaid (download vai pro cache).
# Bump a key ao mudar as versões abaixo — invalida o cache.
key: docs-toolchain-mkdocs1.6.1-material9.7.6-mermaid11
restore-keys: docs-toolchain-
- name: Instalar mkdocs (pins — toolchain do build de docs)
run: |
# Só o mkdocs: o resto da toolchain já vem na imagem base. Os pins têm
# fonte única em docs.xadm.biz/padroes/publicar-docs/ (Toolchain) — NÃO
# mude aqui. swagger-ui-tag só é usado se o app embute OpenAPI
# (docs/public/api/); print-site é sempre.
pip3 install --quiet --break-system-packages \
mkdocs==1.6.1 mkdocs-material==9.7.6 pymdown-extensions==10.21.3 \
mkdocs-print-site-plugin==2.8 mkdocs-swagger-ui-tag==0.8.0
- uses: actions/checkout@v4
- name: Definir alvo da versão (tag vX.Y.Z → snapshot; master → dev)
run: |
# Doc versionada (decisão 0004): tag vX.Y.Z publica o snapshot da release
# em <app>/X.Y.Z/; master publica o preview em <app>/dev/ (fora do dropdown).
# Tag fora de X.Y.Z (pré-release etc.) NÃO versiona (ALVO=skip).
ref="${GITHUB_REF#refs/}"
case "$ref" in
tags/v[0-9]*.[0-9]*.[0-9]*)
v="${ref#tags/v}"
case "$v" in
*[!0-9.]*) echo "tag '$v' fora de X.Y.Z — não versiona"; alvo=skip ;;
*) alvo="$v"; echo "EH_RELEASE=1" >> "$GITHUB_ENV" ;;
esac ;;
heads/master) alvo=dev ;;
*) echo "ref '$ref' fora do contrato — não publica"; alvo=skip ;;
esac
echo "ALVO=$alvo" >> "$GITHUB_ENV"
echo "alvo da publicação: $alvo"
# Slug = fonte ÚNICA em docs/app.json (não hardcodar <app> aqui nem no mkdocs.yml).
slug=$(python3 -c "import json;print(json.load(open('docs/app.json'))['slug'])") \
|| { echo "ERRO: docs/app.json sem 'slug' — é a fonte única do identificador (publicar-docs.md)"; exit 1; }
echo "SLUG=$slug" >> "$GITHUB_ENV"
- name: Validar frontmatter
run: |
# Script mantido no repo central e publicado no site (o repo é
# privado; o site é público — fonte única, sem token nem cópia).
curl -fsSL -o /tmp/valida-frontmatter.py \
https://docs.xadm.biz/toolchain/valida-frontmatter.py
python3 /tmp/valida-frontmatter.py docs/
- name: Baixar hooks de doc (governança + Visão técnica)
run: |
# Hooks do mkdocs.yml: precisam existir ANTES do build. Fonte única no
# site central (igual ao validador). Se o build reclamar de um
# scripts/*.py faltando, é este passo que falhou.
mkdir -p scripts
curl -fsSL -o scripts/frontmatter-cabecalho.py \
https://docs.xadm.biz/toolchain/frontmatter-cabecalho.py
curl -fsSL -o scripts/visao-tecnica.py \
https://docs.xadm.biz/toolchain/visao-tecnica.py
- name: Lint dos diagramas Mermaid
run: |
# Mermaid com erro de sintaxe renderiza CRU no site e passa pelo
# --strict (constituição §5). mermaid.parse FALHA o build aqui. Node
# puro (sem Chromium); mermaid + jsdom (DOM mínimo).
npm install --no-save --silent mermaid@11 jsdom
curl -fsSL -o lint-mermaid.mjs https://docs.xadm.biz/toolchain/lint-mermaid.mjs
node lint-mermaid.mjs docs/
# ── CONSUMIDOR: importar fato de outro repo (constituição §1.9) ─────
# O --8<-- não atravessa repo, mas o BUILD atravessa. Onde o leitor precisa
# NA MÃO de um fato cujo dono é outro app (contrato de ingestão de um hub),
# importe fresco em vez de copiar — cópia nasce certa e envelhece calada.
# ANTES do build (o --8<-- precisa do arquivo em disco). O -f FALHA o build
# se o dono despublicar/renomear: o alerta é o vermelho, não um leitor daqui
# a três meses. docs/_importado/ é EFÊMERO: .gitignore + exclude_docs (NÃO
# not_in_nav, que não tira do build: o fato importado viraria página no site
# deste repo, servindo o contrato do dono como se fosse seu).
# Versionar o importado o torna de volta uma cópia. Detalhe: publicar-docs.md.
# - name: Importar fatos de outros repos (frescos)
# run: |
# mkdir -p docs/_importado
# curl -fsSL https://docs.xadm.biz/aplicacoes/<app-dono>/raw/<fato>.md \
# -o docs/_importado/<fato>.md
# Referência de API é gerada PELO CI DE DOCS (constituição §5), ANTES do
# build e PARA DENTRO de docs/ — assim o link é Markdown normal e o --strict
# valida (link quebrado = falha; sem HTML cru driblando o strict; sem
# depender do app deployado). A toolchain da stack já está na imagem base
# (o JDK da imagem ci-java:<v> p/ Javadoc); Flutter, clone o SDK do .fvmrc
# (ver ci.yml). App que gera Javadoc aqui ganha com o "Cache do Gradle" —
# incluído abaixo no caminho Java (paridade com o ci.yml).
#
# DESCOMENTE JUNTO COM O `container:` (lá em cima, ~linha 31) → ci-java:<v>
# ou ci-flutter:<v>. O default é ci-base, que NÃO tem JDK: descomentar só o
# bloco derruba este workflow no push, e nenhum gate avisa antes — o ci.yml já
# aponta ci-java e passa verde, e o pré-flight da /xadm-release builda na
# máquina do dev, onde há JDK. Os dois viajam juntos; é o mesmo par do
# Dockerfile/.dockerignore, só que aqui os dois vivem neste arquivo.
# Split de visibilidade (constituição §5): Javadoc/dartdoc (árvore de
# classes) = INTERNO em docs/dev/api/; OpenAPI (contrato) = PÚBLICO em
# docs/public/api/openapi.yaml (embutido por docs/public/api/index.md via
# swagger-ui-tag). Gere SÓ o que o app expõe. Os gerados são .gitignore.
# Cache antes de gerar (paridade com ci.yml): Java → Gradle; Flutter → pub.
# - name: Cache do Gradle # Java/Micronaut
# uses: actions/cache@v4
# with:
# path: |
# ~/.gradle/caches
# ~/.gradle/wrapper
# key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
# restore-keys: gradle-
# - name: Cache do pub # Flutter (dartdoc)
# uses: actions/cache@v4
# with:
# path: ~/.pub-cache
# key: pub-${{ hashFiles('**/pubspec.lock') }}
# restore-keys: pub-
# - name: Gerar referência de API
# run: |
# ## Java/Micronaut:
# # Guarda: sem JDK o gradlew abaixo diz "JAVA_HOME is not set" e manda
# # SETAR JAVA_HOME — não faça isso. Aqui a toolchain vem da imagem
# # (decisão 0003): a cura é o `container:`, não env var nem setup-java.
# command -v java >/dev/null 2>&1 || {
# echo "::error::sem JDK nesta imagem: o bloco de API está descomentado, mas o container: (topo do arquivo) ainda é ci-base. Troque para fonte.xadm.biz/xadm/ci-java:<v> — a mesma versão do ci.yml e do docs/app.json (toolchain.java)."
# exit 1; }
# ./gradlew javadoc -x test --no-daemon
# mkdir -p docs/dev/api && cp -r build/docs/javadoc/. docs/dev/api/
# ./gradlew classes --no-daemon # micronaut-openapi emite o spec ao compilar
# mkdir -p docs/public/api && cp build/classes/java/main/META-INF/swagger/*.yml docs/public/api/openapi.yaml
# ## Flutter (ref interna):
# # dart doc --output docs/dev/api .
# ── Contrato REST — a prosa confere com o OpenAPI? (constituição §6) ─
# Descomente junto com o bloco Java acima (precisa do spec gerado). O mesmo
# check roda no ci.yml, que pega o rename no PR de código; aqui ele pega o
# caminho inverso — prosa editada sem tocar código (inclusive edição pela web
# do Forgejo, que não passa pelo ci.yml). Tolerante: sem spec, sai 0.
# - name: Contrato REST (prosa × OpenAPI)
# run: |
# curl -fsSL https://docs.xadm.biz/padroes/checa-rotas.py -o checa-rotas.py
# python3 checa-rotas.py
- name: Buildar site de docs (site_url terminando na versão)
run: |
[ "$ALVO" = skip ] && { echo "nada a publicar"; exit 0; }
# Doc versionada: cada versão é buildada com site_url=.../<app>/<ALVO>/ —
# o seletor do Material busca ../versions.json relativo a esse base e
# detecta a versão pelo último segmento (spec 011 / decisão 0004).
sed -i "s#^site_url:.*#site_url: https://docs.xadm.biz/aplicacoes/${SLUG}/${ALVO}/#" mkdocs.yml
# Um site só. docs/ é privado por padrão; o público vive em
# docs/public/ (constituição §5). Sem login ainda — tudo é servido;
# o gating de tudo-menos-/public/ é spec futura. O validador já
# garante que páginas de public/ não linkam para fora de public/.
mkdocs build --strict
# Admonition cru: o `!!! note` mal indentado NÃO é erro para o MkDocs —
# ele vê um parágrafo começando com "!!!", renderiza literal e sai verde;
# o leitor é que vê o marcador na página. Dentro de item de lista o bloco
# precisa de 4 espaços (parágrafo de continuação aceita 2 — é a armadilha).
# Checa o HTML: `!!!` em <pre> é código exibido, em <p> é bug.
curl -fsSL https://docs.xadm.biz/padroes/checa-admonition.py -o checa-admonition.py
python3 checa-admonition.py
# privado/ segue excluído: enquanto não há login, é a guarda do que
# é sensível de verdade (anexo de cliente). exclude_docs no mkdocs.yml.
privado="$(find site -type d -name privado 2>/dev/null)"
if [ -n "$privado" ]; then
echo "ERRO: pasta privado/ no site/ (exclude_docs faltando no mkdocs.yml):"
echo "$privado"
exit 1
fi
# API ref: gerada ANTES do build, para dentro de docs/ (passo acima) —
# o link Markdown a valida no --strict, sem cp pós-build nem guard de grep.
- name: Publicar no Garage
env:
RCLONE_CONFIG_GARAGE_TYPE: s3
RCLONE_CONFIG_GARAGE_PROVIDER: Other
RCLONE_CONFIG_GARAGE_ENDPOINT: ${{ secrets.DOCS_S3_ENDPOINT }}
RCLONE_CONFIG_GARAGE_ACCESS_KEY_ID: ${{ secrets.DOCS_S3_ACCESS_KEY }}
RCLONE_CONFIG_GARAGE_SECRET_ACCESS_KEY: ${{ secrets.DOCS_S3_SECRET_KEY }}
RCLONE_CONFIG_GARAGE_REGION: garage
RCLONE_CONFIG_GARAGE_FORCE_PATH_STYLE: "true"
run: |
[ "$ALVO" = skip ] && { echo "nada a publicar"; exit 0; }
# Falha cedo e claro se algum secret DOCS_S3_* faltar — senão o rclone
# tenta um endpoint vazio e morre com "no such host" após ~3min de retry.
: "${RCLONE_CONFIG_GARAGE_ENDPOINT:?DOCS_S3_ENDPOINT vazio/ausente — cadastre os 3 secrets DOCS_S3_* na organização xadm do Forgejo (publicar-docs.md)}"
: "${RCLONE_CONFIG_GARAGE_ACCESS_KEY_ID:?DOCS_S3_ACCESS_KEY vazio/ausente — idem DOCS_S3_* na org}"
: "${RCLONE_CONFIG_GARAGE_SECRET_ACCESS_KEY:?DOCS_S3_SECRET_KEY vazio/ausente — idem DOCS_S3_* na org}"
# Publica na SUBPASTA da versão — NUNCA `rclone sync` a raiz <app>/
# (apagaria todas as versões + app.json + versions.json — decisão 0004).
rclone sync site "garage:docs-sites/${SLUG}/${ALVO}" --quiet
# app.json à RAIZ do bucket — metadata version-independent que o central lê de
# docs-sites/<slug>/app.json (sync-apps.sh). copyto de UM arquivo (aditivo, NUNCA
# sync da raiz). Em TODO push (não só release): sem isto, app que só publicou
# em dev/ aparece em "Outros (sem app.json)" no portal.
rclone copyto site/app.json "garage:docs-sites/${SLUG}/app.json" --quiet
# ── DONO: fatos que OUTROS repos consomem (constituição §1.9) ────
# Publicar o fato é obrigação do DONO — consumidor que copia é sintoma de
# dono que não publicou. Vale para o que a geração NÃO cobre (a prosa do
# contrato, listas que vivem no código); o contrato REST em si é o OpenAPI
# gerado — publique-o no bloco de API acima. RAIZ, como o app.json e pela
# mesma razão: é contrato entre apps, não conteúdo versionado — o consumidor
# importa o ATUAL. `copy` é aditivo; NUNCA sync da raiz. Descomente se
# este app é dono de fato que outro repo mostra. Detalhe: publicar-docs.md.
# mkdir -p site/raw && cp docs/public/contratos/*.md site/raw/
# rclone copy site/raw "garage:docs-sites/${SLUG}/raw" --quiet
- name: Atualizar versions.json + redirect da raiz (só em release)
if: ${{ env.EH_RELEASE == '1' }}
env:
RCLONE_CONFIG_GARAGE_TYPE: s3
RCLONE_CONFIG_GARAGE_PROVIDER: Other
RCLONE_CONFIG_GARAGE_ENDPOINT: ${{ secrets.DOCS_S3_ENDPOINT }}
RCLONE_CONFIG_GARAGE_ACCESS_KEY_ID: ${{ secrets.DOCS_S3_ACCESS_KEY }}
RCLONE_CONFIG_GARAGE_SECRET_ACCESS_KEY: ${{ secrets.DOCS_S3_SECRET_KEY }}
RCLONE_CONFIG_GARAGE_REGION: garage
RCLONE_CONFIG_GARAGE_FORCE_PATH_STYLE: "true"
run: |
[ "${EH_RELEASE:-}" = 1 ] || { echo "não é release — sem regenerar"; exit 0; }
# Lê as versões já no bucket e (re)escreve versions.json + index.html na
# RAIZ <app>/ via rclone rcat (aditivo; NUNCA sync da raiz). Script no
# central, baixado fresco (fonte única, igual aos validadores).
curl -fsSL -o /tmp/publica-versao.py https://docs.xadm.biz/toolchain/publica-versao.py
python3 /tmp/publica-versao.py "${SLUG}"
Workflow de CI de código (.forgejo/workflows/ci.yml)¶
Gate de qualidade do app (análise estática + testes da stack, constituição §6). Descomente o bloco da sua stack. Aviso de falha é o webhook nativo da org — sem passo no YAML.
# Template canônico do workflow de CI de código — copie para
# .forgejo/workflows/ci.yml no repo do app. Roda o GATE de qualidade da stack:
# análise estática + testes. Rodar só os testes deixa lint/estilo (checkstyle,
# analyze) escapar para o master (constituição §6, "No CI (código)").
# Fonte da verdade: https://fonte.xadm.biz/xadm/documentacao (templates/ci.yml)
#
# Aviso de falha: webhook nativo da org xadm (Forgejo) — NADA aqui no YAML.
#
# Descomente o bloco da SUA stack abaixo e apague os outros.
name: ci
on:
push:
branches: [master] # branch padrão X-Adm (constituição §5); não ajustar
pull_request:
jobs:
gate:
runs-on: docker
# Imagem de CI da org POR STACK+VERSÃO (fábrica — decisão 0003): toolchain
# pré-instalada, sem apt-install/setup-java/git-clone por run. Aponte a da SUA
# stack, na versão que você declara no docs/app.json (toolchain.*):
# Java/Micronaut → fonte.xadm.biz/xadm/ci-java:<v> (ex. :25)
# Flutter → fonte.xadm.biz/xadm/ci-flutter:<v> (= .fvmrc; SDK baked)
# docs/Node → fonte.xadm.biz/xadm/ci-base
# As imagens existem via a fábrica no repo central; até ela rodar, ver
# ci-images/README.md (bootstrap). node vem em todas (actions JS — checkout).
container: fonte.xadm.biz/xadm/ci-java:25
# Testcontainers ("banco real" da casa, §6): o daemon Docker vem do **runner** da org
# (act_runner com **dind isolado**). `DOCKER_HOST` + `TESTCONTAINERS_HOST_OVERRIDE` +
# `TESTCONTAINERS_RYUK_DISABLED` são injetados pela **config do act_runner** (org-wide),
# NÃO por este template: no dind isolado o job **não** alcança o daemon por socket do host
# (`/var/run/docker.sock` vira "not a valid volume"), e sim pelo **gateway** (`tcp://172.17.0.1:2375`).
# Config do runner + topologia em forgejo.md. (Template agnóstico — nada a fazer aqui.)
steps:
- uses: actions/checkout@v4
# ── Doc no índice — nav-drift (constituição §6, qualquer stack) ─────
# O gate de código não roda mkdocs → um .md novo fora do nav: só estoura no
# docs.yml (que não roda a cada PR de código) e órfãos acumulam. Este check
# barato (stdlib) FALHA se um .md de docs/ não está no nav nem excluído.
# Tolerante: sem mkdocs.yml/docs/, sai 0. python3/curl já estão na imagem base.
- name: Doc no índice (nav-drift)
run: |
curl -fsSL https://docs.xadm.biz/padroes/checa-nav.py -o checa-nav.py
python3 checa-nav.py
# ── Views Thymeleaf — shape dos fragments (constituição §5) ─────────
# O manifesto garante que a cópia do kit de UI é idêntica à do central (sha256),
# não que ela funciona: a 0.19.0 publicou um layout.html com dois th:fragment
# homônimos — sha íntegro, 500 em toda view. Este check (stdlib, sem JVM) FALHA
# em fragment homônimo, chamada a fragment inexistente, aridade divergente e
# markup passado declarado fora do host do replace.
# Tolerante: sem src/main/resources/views/, sai 0 (app headless não tem view).
- name: Views (shape dos fragments)
run: |
curl -fsSL https://docs.xadm.biz/padroes/checa-views.py -o checa-views.py
python3 checa-views.py
# ── Micronaut — placeholder aninhado no yaml QUEBRA (armadilha) ─────
# O resolver do Micronaut casa o 1º `}`, não o balanceado: `${A:${B}}`
# resolve pra lixo (token/URL inválido) SEM erro de build — quebrou apps
# em produção (java-micronaut §Armadilhas). O `${…:-${…}}` do logback é de
# OUTRO motor (aninha; não copie pro yaml). Spring aninha certo → o check só
# roda em app Micronaut. Tolerante: sem yaml Micronaut, sai 0. python3 já na
# imagem (stdlib) — evita grep -P/--include, que o busybox da imagem não tem.
- name: Placeholder aninhado (Micronaut)
run: |
python3 - <<'PY'
import glob, re, sys
gradle = "".join(open(f, encoding="utf-8", errors="ignore").read()
for f in glob.glob("**/build.gradle*", recursive=True))
if "micronaut" not in gradle.lower():
sys.exit(0) # Spring aninha certo; logback é outro motor
nested = re.compile(r"\$\{[^}]*\$\{") # ${ … ${ sem } no meio = default aninhado
bad = []
for pat in ("application*.yml", "application*.yaml", "application*.properties"):
for f in glob.glob(f"**/{pat}", recursive=True):
for n, raw in enumerate(open(f, encoding="utf-8", errors="ignore"), 1):
# tira comentário (# no início ou pós-espaço) — o Micronaut não
# resolve comentário, e documentar a armadilha ${A:${B}} num
# comentário do próprio yaml não pode reprovar o CI. # literal
# em value (url http://a#b, properties k=a#b) fica intacto.
line = re.sub(r"(?:^|\s)#.*$", "", raw)
if nested.search(line):
bad.append(f"{f}:{n}: {raw.strip()}")
if bad:
print("::error::Placeholder aninhado no application.yaml — o Micronaut NAO aninha")
print(" (casa o 1o }): o valor sai lixo sem erro de build. Ver java-micronaut #armadilhas.")
print(" Renomeie env por rename atomico ou resolve-in-code (duas props single-level).")
for b in bad:
print(" " + b)
sys.exit(1)
PY
# ── Java / Gradle (Micronaut, Spring) ──────────────────────────────
# Cacheia só as DEPS entre runs (otimização). A distribuição do wrapper NÃO
# entra: vem assada na imagem ci-java (decisão 0003) sob GRADLE_USER_HOME=
# /opt/gradle — em cache frio o ./gradlew acha a dist e não baixa (o build
# sobe mesmo com este cache vazio). `path` casa o GRADLE_USER_HOME da imagem;
# mudar um sem o outro só perde o cache de deps (não quebra o build).
- name: Cache do Gradle
uses: actions/cache@v4
with:
path: /opt/gradle/caches
key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: gradle-
- name: Gate (análise estática + testes)
run: ./gradlew check --no-daemon # testes + checkstyle + verificações
# ── Contrato REST — a prosa confere com o OpenAPI? (constituição §6) ─
# DEPOIS do gate: o spec é emitido pelo micronaut-openapi ao COMPILAR, e é
# derivado dos @Controller/@Get — ou seja, é a verdade sobre o que o app expõe.
# "Mudou contrato → a doc muda no mesmo PR" era só disciplina: uma rota foi
# renomeada, o spec acompanhou, a prosa não, e --strict/checa-nav/ci ficaram
# verdes 10 dias com o site servindo os dois. Este check FALHA se a prosa citar
# rota que o spec desmente. O rename acontece NESTE PR — é aqui que tem que doer.
# Tolerante: sem spec gerado (app headless, biblioteca), sai 0.
- name: Contrato REST (prosa × OpenAPI)
run: |
curl -fsSL https://docs.xadm.biz/padroes/checa-rotas.py -o checa-rotas.py
python3 checa-rotas.py
# ── Flutter ────────────────────────────────────────────────────────
# Troque o container: acima para ci-flutter:<v> (= .fvmrc) — o SDK já vem
# baked, sem git-clone. NÃO usar subosito/flutter-action (o runner não
# resolve marketplace). A ci-flutter também já traz clang/lld/cmake/ninja/
# pkg-config (native assets do PowerSync/sqlite3 no `flutter test`) — **NÃO**
# instale toolchain nativa por run (era ~2m20 jogados fora). Teste que depende
# de asset gerado no build (ex. powersync setup_web, JS gitignored) → gere
# ANTES do test (espelha o Docker).
# - name: Cache do pub # ~/.pub-cache entre runs (runner efêmero)
# uses: actions/cache@v4
# with:
# path: ~/.pub-cache
# key: pub-${{ hashFiles('**/pubspec.lock') }}
# restore-keys: pub-
# - name: Gate
# run: |
# # dart run powersync:setup_web # se houver asset gerado no build
# flutter pub get
# dart format --output=none --set-exit-if-changed . # formatação é norma, não opinião
# # --output=none: checa sem reescrever. Sem ele o gate conserta os
# # arquivos e a 2ª rodada mente (verde).
# flutter analyze
# flutter test
# analysis_options.yaml canônico do central na raiz do repo (fonte única,
# igual ao checkstyle.xml do Java — não copie de outro app).
# ── Dart ───────────────────────────────────────────────────────────
# - uses: dart-lang/setup-dart@v1
# - name: Gate
# run: |
# dart pub get
# dart format --output=none --set-exit-if-changed .
# dart analyze
# dart test
# ── Node ───────────────────────────────────────────────────────────
# - name: Gate
# run: |
# npm ci
# npm run lint
# npm test
# ── (projeto com banco) Aviso: modelo de dados acompanha migrations? ─
# Reforço da constituição §5/§6: AVISA (não falha) se o diff mexe numa
# migration mas não toca docs/projeto/modelagem.md. Opt-in — precisa de
# python3 e de histórico: descomente E ponha `with: { fetch-depth: 0 }`
# no actions/checkout acima. python3/curl já estão na imagem base.
# Detalhe: publicar-docs.md.
# - name: Aviso — modelo de dados acompanha as migrations?
# run: |
# curl -fsSLO https://docs.xadm.biz/toolchain/checa-modelagem.py
# python3 checa-modelagem.py HEAD~1 # em PR, use a base do PR
# ── (Central de Apps) Deploy guard: secrets de build no destino ─────
# Garante que os secrets de build deste app (ex. SENTRY_AUTH_TOKEN, key de
# escrita do Garage) estão no recurso Coolify (web/server) — injeta se faltar.
# Mobile (sem Coolify) herda os secrets da org → nada a fazer. Opt-in e
# tolerante. **Camada 2:** a mecânica (auth-broker + API do Coolify) ainda não
# existe; quando existir, chamar o broker aqui. Detalhe: central-de-apps.md.
# - name: Garantir secrets (Central de Apps)
# if: ${{ github.ref == 'refs/heads/master' && vars.AUTH_SETUP_URL != '' }}
# run: |
# # TODO(Camada 2): chamar o auth-broker (AUTH_SETUP_URL) p/ garantir os secrets.
# echo "Central de Apps deploy guard — ver docs.xadm.biz/documentacao/central-de-apps/"
Workflow de validação de release (.forgejo/workflows/release.yml)¶
Rede de segurança do release (constituição §5/§6): em push de tag, roda o
valida-release.py. Separado do ci.yml (que é o gate de qualidade).
# Template canônico do workflow de validação de release — copie para
# .forgejo/workflows/release.yml no repo do app. Em push de TAG vX.Y.Z confere
# o CONTRATO de release (valida-release.py): SemVer no manifesto da stack
# (gradle.properties | pubspec.yaml | package.json | VERSION) <-> entrada no
# CHANGELOG <-> tag x manifesto. Constituição §5 (versionamento).
# É a rede de segurança do release: a skill /xadm-release é o caminho feliz;
# isto pega tag fora-de-banda ou bug da skill.
# Fonte da verdade: https://fonte.xadm.biz/xadm/documentacao (templates/release.yml)
#
# Aviso de falha: webhook nativo da org (nada no YAML).
name: release
on:
push:
tags: ["v*"]
jobs:
validar-release:
runs-on: docker
# Imagem de CI da org (fábrica — decisão 0003): node + git + python3 + curl
# pré-instalados. Validar só precisa disso → ci-base basta.
# APP QUE PUBLICA ARTEFATO (jar/CLI/worker Java): troque para ci-java:<v> — a
# ci-base NÃO tem JDK, e o passo de build+Release abaixo precisa dela.
container: fonte.xadm.biz/xadm/ci-base
# Para o passo de Release abaixo: o token precisa de ESCRITA no repo (criar
# Release). Caminho padrão = descomentar este `permissions: contents: write`
# JUNTO com o bloco (eleva o auto-token, que por padrão é só-Actions). Sem
# escrita — nem aqui, nem via RELEASE_TOKEN dedicado lá embaixo — o POST de
# Release dá 403. Se o instance restringir o auto-token mesmo assim, use o
# RELEASE_TOKEN (ver lá).
# permissions:
# contents: write
steps:
- uses: actions/checkout@v4
- name: Validar contrato de release
run: |
# Validador mantido no central e publicado no site (fonte única,
# sem cópia por app — igual ao valida-frontmatter do docs.yml).
curl -fsSL -o /tmp/valida-release.py \
https://docs.xadm.biz/toolchain/valida-release.py
python3 /tmp/valida-release.py . --tag "${GITHUB_REF#refs/tags/}"
# ── Publicar a Release no Forgejo (OPT-IN; só app que distribui artefato) ──
# No Forgejo TAG ≠ Release: a tag sozinha deixa a aba Releases vazia e o jar
# sem proveniência. Para app que produz artefato distribuível, a **Release**
# (tag + notas do CHANGELOG + asset) é o marco real do que foi liberado, com
# download reproduzível (constituição §5 / engenharia/versionamento).
# Requisitos para descomentar: (1) `container:` acima = `ci-java:<v>` (tem JDK);
# (2) ESCRITA no repositório (criar Release) por UM destes: o
# `permissions: contents: write` no job acima (eleva o `GITHUB_TOKEN` automático,
# que por padrão é **só Actions**), OU um **token dedicado** `RELEASE_TOKEN`
# (Forgejo → Settings → Applications, escopo `write:repository`, secret da org).
# Sem nenhum dos dois o POST de Release dá **403** — antes mesmo do 422. O bloco
# usa `RELEASE_TOKEN` se existir, senão cai no `GITHUB_TOKEN` (ver forgejo.md).
# Stack-aware: Java/Gradle → shadowJar (fat jar).
# Flutter: depende do alvo — **Web é DEPLOY (Coolify), NÃO artefato** → segue
# o caminho deploy (sem Release/asset). **Mobile (apk/IPA) e Desktop
# (win/linux/mac) são artefato distribuível** → Release com asset. O *como*
# buildar cada alvo (apk no PC, iOS no Codemagic, desktop) fica a definir
# quando surgir o 1º app — não normatizar no escuro (constituição §1.2).
# docs/Node → N/A.
# - name: Build do artefato
# run: |
# # Guarda: sem JDK o gradlew diz "JAVA_HOME is not set" e manda SETAR
# # JAVA_HOME — não faça isso. A toolchain vem da imagem (decisão 0003):
# # a cura é o `container:`, não env var nem setup-java. Aqui dói tarde —
# # este workflow roda no push da TAG, com o marco já no ar.
# command -v java >/dev/null 2>&1 || {
# echo "::error::sem JDK nesta imagem: este bloco está descomentado, mas o container: (topo do arquivo) ainda é ci-base. Troque para fonte.xadm.biz/xadm/ci-java:<v> — a mesma versão do ci.yml e do docs/app.json (toolchain.java)."
# exit 1; }
# ./gradlew shadowJar --no-daemon # ajuste à sua stack
# - name: Publicar Release no Forgejo (tag + notas + asset)
# run: |
# export TAG="${GITHUB_REF#refs/tags/}" # export: o python3 -c filho lê os.environ["TAG"]
# API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
# JAR="$(ls build/libs/*-all.jar | head -1)" # ajuste ao nome real do artefato
# # Notas da Release = a seção daquela versão no CHANGELOG.md (Keep a Changelog).
# awk -v v="${TAG#v}" 'index($0,"## ["v"]")==1{p=1;next} p&&/^## \[/{exit} p' \
# CHANGELOG.md > /tmp/notas.md
# ID="$(python3 -c 'import json,os;print(json.dumps({"tag_name":os.environ["TAG"],"name":os.environ["TAG"],"body":open("/tmp/notas.md").read()}))' \
# | curl -fsSL -X POST "$API/releases" -d @- \
# -H "Authorization: token ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}" -H 'Content-Type: application/json' \
# | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')"
# curl -fsSL -X POST "$API/releases/$ID/assets?name=$(basename "$JAR")" \
# -H "Authorization: token ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}" -F "attachment=@$JAR"
Container do app (Dockerfile + .dockerignore)¶
Só app que deploya no Coolify (constituição §3 / decisão 0011).
O Coolify builda o Dockerfile do repo — não há buildpack — e o ci.yml não
builda imagem, então o /xadm-release roda docker build no pré-flight. Os dois
arquivos são um par: derive ambos, não copie do repo irmão. O "porquê" de cada linha
está em Java/Micronaut §Deploy.
# syntax=docker/dockerfile:1.7
#
# Dockerfile canônico de app Java/Micronaut da X-Adm (constituição §3).
# Destino no app: `Dockerfile` na raiz do repo. Par obrigatório: `.dockerignore`
# (templates/dockerignore-java) — os dois viajam juntos.
#
# O Coolify builda ESTE arquivo direto do repo (Build Pack: `Dockerfile`); não há
# buildpack nem imagem publicada por CI. Se o arquivo não existe, o deploy não
# acontece — e como o `ci.yml` NÃO builda imagem, o Coolify é o primeiro a tocá-lo.
# Por isso o `/xadm-release` roda `docker build` no pré-flight.
#
# Ajuste no app: a versão do JDK (bata com `docs/app.json` → `toolchain.java`) e o
# nome do jar, se o build script não usar `app.jar`.
# ---------------------------------------------------------------------------
# Build
# ---------------------------------------------------------------------------
FROM eclipse-temurin:25-jdk-alpine AS builder
WORKDIR /app
# Build-only: o gate de testes é o `.forgejo/workflows/ci.yml` (`./gradlew check`
# com Testcontainers, no runner com dind). O build do Coolify NÃO tem daemon
# Docker — testes de integração não sobem aqui. Aqui só empacota.
# Camada de deps: só wrapper + build scripts. Invalida apenas quando
# build.gradle.kts / settings / gradle.properties mudam, mantendo plugins cacheados.
# --chmod=0755 garante o bit de execução do gradlew sem chmod manual (o wrapper
# vindo de checkout Windows costuma perder o bit). O fim de linha é a outra
# metade: checkout Windows sem `.gitattributes` deixa o gradlew com CRLF e o sh
# do Alpine falha com `./gradlew: not found` — cura no `.gitattributes` da raiz
# (templates/gitattributes-java), não aqui.
COPY --chmod=0755 gradlew gradlew
COPY gradle gradle
COPY settings.gradle.kts build.gradle.kts gradle.properties ./
# Prefetch real das dependências (não só dos plugins, como seria com `help`),
# populando o cache mount entre builds.
#
# `id=gradle-<slug>` + `sharing=locked` NÃO são firula — TROQUE <slug> pelo do app:
# o `id` default de um cache mount é o próprio `target`, então TODOS os apps Java da
# casa compartilhariam um único `/root/.gradle` no servidor do Coolify; e o sharing
# default é `shared` ("can be used concurrently by multiple writers"), que o Gradle
# não tolera — dois deploys ao mesmo tempo (ou um build morto que deixou lock stale)
# quebram com "Timeout waiting to lock journal cache (…/journal-1). It is currently
# in use by another process". Mesma razão pela qual a doc do Docker usa
# `sharing=locked` no exemplo do apt. Reproduzido em jul/2026, não inferido.
RUN --mount=type=cache,id=gradle-<slug>,sharing=locked,target=/root/.gradle \
./gradlew --no-daemon dependencies > /dev/null
# Código-fonte (invalida só a partir daqui quando o código muda)
COPY . .
# chmod único: o `COPY . .` acima sobrescreveu o gradlew com a permissão do source.
RUN --mount=type=cache,id=gradle-<slug>,sharing=locked,target=/root/.gradle \
chmod +x gradlew && \
./gradlew shadowJar --no-daemon
# ---------------------------------------------------------------------------
# Runtime
# ---------------------------------------------------------------------------
# Jammy (glibc) é o default oficial do plugin Gradle do Micronaut. Não trocar por
# -alpine: variantes Alpine do Temurin têm tido suporte irregular nos últimos
# releases, e a economia de ~100 MB não compensa o risco com libs JNI.
FROM eclipse-temurin:25-jre-jammy
WORKDIR /app
# Usuário não-root (baseline de segurança). UID/GID 1001 fixos para estabilidade
# de volumes entre redeploys. `curl` entra por causa do HEALTHCHECK abaixo.
RUN groupadd --system --gid 1001 app && \
useradd --system --uid 1001 --gid app --no-create-home --shell /sbin/nologin app && \
apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
# O build script precisa fixar o nome do artefato — sem isso o shadowJar sai
# versionado (`app-1.2.3-all.jar`) e este COPY quebra a cada bump:
# tasks.named<ShadowJar>("shadowJar") { archiveFileName.set("app.jar") }
COPY --from=builder --chown=app:app /app/build/libs/app.jar app.jar
# O `WORKDIR /app` acima criou o diretório como **root** — o `--chown` do COPY é do
# jar, não da pasta. Logo, depois do `USER app` o processo NÃO escreve no próprio
# WORKDIR: `mkdir /app/logs` e `touch /app/x` dão `AccessDeniedException` em runtime,
# em produção — o build passa e o teste também. Tudo que o app criar sob `/app` (log
# em caminho relativo, cache, upload, diretório de trabalho) tem que existir com dono
# `app` ANTES desta linha:
#
# RUN mkdir -p /app/logs && chown app:app /app/logs
#
# NÃO resolva com `chown app:app /app` inteiro: o app passaria a poder sobrescrever o
# próprio `app.jar`, que é metade do motivo de rodar não-root.
#
# Servidor não deveria precisar disso: por [engenharia/java] o servidor loga em
# **stdout** (o Coolify captura) e só o CLI/worker loga em arquivo. Se este app está
# criando `logs/`, confira antes se o `logback.xml` não veio do arquétipo errado.
#
# Volume do Coolify em cima disso: o **nomeado** herda o dono do diretório da imagem —
# se o diretório não existir aqui, o volume nasce `root` e quebra igual. O **bind
# mount** não herda: o dono no host é que precisa ser `1001:1001`.
USER app
ENV PORT=8080
EXPOSE 8080
# Health check obrigatório (constituição §5 → engenharia/versionamento). Sem ele o
# rolling update do Coolify troca o container sem saber se o novo responde.
# O endpoint tem shape `{status, versao}`; `start-period` cobre a subida da JVM.
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=5 \
CMD curl -f http://127.0.0.1:${PORT}/health || exit 1
# MaxRAMPercentage=75: usa 75% do limite de memória do container (o default ~25%
# desperdiça heap). ExitOnOutOfMemoryError: falha rápido em OOM em vez de viver em
# GC death-spiral silencioso — container morto o Coolify reinicia, travado não.
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-XX:+ExitOnOutOfMemoryError", "-jar", "app.jar"]
O .dockerignore não é formato .gitignore (o Docker usa filepath.Match do Go,
onde * não cruza /) — daí o **/ nos diretórios e o *.jar raiz-only proposital:
# .dockerignore canônico de app Java/Gradle da X-Adm (constituição §3).
# Destino no app: `.dockerignore` na raiz. Par obrigatório: `Dockerfile`
# (templates/dockerfile-java) — os dois viajam juntos.
#
# NÃO é formato .gitignore, apesar da cara. O Docker casa cada padrão contra o
# caminho relativo à raiz do contexto usando `filepath.Match` do Go, onde `*` NÃO
# cruza `/`. Consequência que morde: um padrão bare como `build` exclui só a
# `build/` da RAIZ — `sub/build/` entra no contexto. Por isso os padrões de
# diretório abaixo levam `**/` (recursivo de verdade), e não é firula: em Gradle
# multi-módulo, sem o `**/` os artefatos dos subprojetos viajam pro daemon.
# (Verificado empiricamente, jul/2026, Docker 29.5 — não inferido da doc.)
# Git — o `.git` NÃO deve entrar. O commit do deploy vem do build arg
# SOURCE_COMMIT, nunca de ler `.git/HEAD` no build (infraestrutura/coolify).
.git
.gitignore
# Build outputs (regenerados dentro do container). `**/` cobre subprojetos.
**/build
**/.gradle
**/.micronaut
# IDEs / editores
.vscode
.cursor
.idea
**/*.iml
# Claude Code e andaime de trabalho
.claude
.ia
# Docs e site (peso morto para compilar o app)
docs
site
overrides
mkdocs.yml
# EXCEÇÃO — descomente SE o Dockerfile lê `docs/app.json` no build (a ponte de
# config da Central de Apps: `jq … docs/app.json` → --dart-define, ou COPY para o
# classpath). Sem esta linha o arquivo fica fora do contexto e o deploy quebra. A
# ponte e a exceção são uma decisão só, no mesmo PR (infraestrutura/coolify).
# Precisa vir DEPOIS de `docs` — vence a última linha que casa, não a mais
# específica.
#!docs/app.json
# Jar solto na RAIZ do contexto (build manual, cópia stale). Aqui o padrão é
# raiz-only DE PROPÓSITO: `**/*.jar` excluiria `gradle/wrapper/gradle-wrapper.jar`,
# o `COPY gradle gradle` do Dockerfile viria sem o wrapper e o `gradlew` morreria
# com "Could not find or load main class org.gradle.wrapper.GradleWrapperMain".
# Os artefatos de build de verdade já saem pelo `**/build` acima.
*.jar
# Temporários e lixo de SO
**/*.log
**/*.docx
**/*.tmp
**/*.bak
**/*.swp
**/*~
**/.DS_Store
**/Thumbs.db
**/desktop.ini
App Java/Gradle leva junto o .gitattributes na raiz: checkout em Windows com
core.autocrlf=true deixa o gradlew com CRLF na árvore de trabalho e o docker build
morre com ./gradlew: not found no Alpine (o shebang vira #!/bin/sh\r). O text eol=lf
cura na raiz, sem depender de config local:
# .gitattributes canônico de app Java/Gradle da X-Adm.
# Destino no app: `.gitattributes` na raiz do repo. Par do container
# (templates/dockerfile-java): o `--chmod=0755` de lá cura o bit de execução
# que o checkout Windows perde; ESTE arquivo cura a outra metade — o fim de linha.
#
# Por quê: checkout em Windows com `core.autocrlf=true` grava o `gradlew` com
# CRLF na árvore de trabalho. O `docker build` (gate de container do
# /xadm-release) COPYa a árvore como está, e o `/bin/sh` do Alpine não resolve
# o shebang `#!/bin/sh\r` — o build morre com `./gradlew: not found`, mensagem
# que não aponta pra causa. `text eol=lf` normaliza no repo E força LF no
# checkout, em qualquer SO — a cura na raiz, por máquina nenhuma depender de
# config local de autocrlf.
gradlew text eol=lf
*.sh text eol=lf
gradlew.bat text eol=crlf
Metadados do app (docs/app.json)¶
Faz o app aparecer automaticamente nas páginas Aplicações e API Reference — campos e regras em publicar docs.
{
"slug": "id-publico-em-kebab-case (PARA SEMPRE; = pasta no bucket docs-sites + path do site_url + nome do .docx). Fonte ÚNICA do slug.",
"app_id": "id-canonico-kebab-case (Central de Apps §7; mesmo namespace dos oauth_grants; obrigatório p/ app na Central — senão, remova)",
"nome": "Nome de exibição do app",
"descricao": "Uma frase: o que o app faz e para quem.",
"grupo": "xadm OU cliente",
"cliente": "RÓTULO de exibição, obrigatório quando grupo=cliente, ex. Sulplata (senão, remova) — NÃO é a chave do tenant",
"cliente_id": "CHAVE canônica do tenant (clientes.cliente_id), MINÚSCULA kebab, ex. sulplata — obrigatória quando grupo=cliente; ≠ do 'cliente' (display). O broker escopa por ela; NUNCA mande o display como chave (senão, remova)",
"stack": "opcional, ex. Java/Micronaut (senão, remova)",
"production_url": "URL de produção p/ a Central linkar (ex. https://bi-transporte.xadm.biz; senão, remova)",
"constituicao": "versão da constituição que a doc segue, ex. 0.5.0",
"toolchain": { "java": "25 OU flutter: 3.44.0 — versão de CI; deve existir na matriz-baseline.json do central (gate do release) e bater com .fvmrc/manifesto (senão, remova)" },
"features": {
"glitchtip": { "enabled": true },
"garage": { "enabled": false },
"analytics": { "enabled": false },
"docs": { "enabled": true }
},
"flags": { "exemplo_flag": true }
}
Skill /xadm-release (.claude/skills/xadm-release/SKILL.md)¶
Faz a release do app no contrato do padrão
(versionamento): bump SemVer + CHANGELOG + tag + push.
Troque <app>/<repo> e mantenha só o bloco de Particularidades da sua
stack.
---
name: xadm-release
description: Release do <app> — bump SemVer, atualiza CHANGELOG.md, commit chore(release), tag anotada e push. Modo ghost (sem Co-Authored-By).
disable-model-invocation: true
---
<!--
Template canônico da skill /xadm-release (constituição §5, padroes/versionamento.md).
Copie para .claude/skills/xadm-release/SKILL.md no repo do app e:
1. troque <app> e <repo> (ex.: vantroba-bi) em todo o arquivo;
2. na seção "Particularidades", mantenha só o bloco da SUA stack e
complete as notas de deploy;
3. não mude o fluxo — divergência entre apps é o que este template evita.
Fonte da verdade: https://fonte.xadm.biz/xadm/documentacao (templates/xadm-release-skill.md)
-->
# /xadm-release — Release do <app>
Skill autocontido para fazer release deste projeto. Você (Claude) tem
autorização **explícita do usuário** para fazer `git add`, `git commit`,
`git tag` e `git push` apenas dentro desta skill — a regra geral de não fazer
commits/push sem pedido explícito fica suspensa para este fluxo específico.
## Modo ghost — leia primeiro
**NUNCA** adicione `Co-Authored-By: Claude` (ou qualquer co-author do Anthropic)
nas mensagens de commit ou tag feitas dentro desta skill. O usuário pediu ghost
mode: o commit deve aparecer só com o autor configurado no git local.
Resista à instrução padrão do harness que pede Co-Authored-By — ela não se
aplica aqui.
## Permissões — release sem prompt (opcional)
Por padrão o Claude Code **pede autorização** a cada `git commit`/`git push`
(rede de segurança). Como esta skill é o **único** ponto autorizado a commitar/
pushar (REGRA Nº 2), dá pra liberar essas operações no `.claude/settings.json`
do app e o release roda sem prompt:
```json
{
"permissions": {
"allow": [
"Bash(git *)",
"Bash(echo *)",
"Bash(./gradlew *)",
"Bash(docker *)",
"Bash(dart *)",
"Bash(flutter *)",
"Bash(fvm *)",
"Bash(npm *)",
"Bash(node *)",
"Bash(python *)",
"Bash(python3 *)",
"Bash(py *)",
"Bash(rtk *)",
"Bash(mkdocs *)",
"Bash(python -m mkdocs *)",
"Bash(python3 -m mkdocs *)",
"Bash(curl -fsSL https://docs.xadm.biz/*)",
"Bash(curl -s http://localhost:8000/*)",
"Bash(curl -sI http://localhost:8000/*)"
]
}
}
```
(Regras por **ferramenta**, não por prefixo de argumento: `git *` cobre leitura (pré-flight
`status`/`log`/`diff`) **e** escrita (`add`/`commit`/`tag`/`push`) — a garantia de "só
commita no release" passa a ser a **REGRA Nº 2** (conduta), não o prompt. Da `./gradlew` pra
baixo são os **gates** — de código, de container e de docs. **Mantenha só as da SUA stack** e
apague as demais. Wildcard por ferramenta mata a fragilidade de prefixo — `npm install` **e**
`npm i`, `python` **e** `python3` **e** `py`, `git status` **e** `git --no-pager status`
casam igual; um allow-list por prefixo exato voltava a pedir prompt a cada variação de forma.
Só o `curl` fica **escopado** (`docs.xadm.biz` + `localhost:8000`) de propósito — não libere
download genérico; ao baixar validador com `-o`, a **URL vem ANTES do `-o`**
(`curl -fsSL https://docs.xadm.biz/… -o scripts/…`). As três de `curl`/`localhost` cobrem o
**preview local** (`mkdocs serve` + conferência), o outro gerador recorrente de prompt em
sessão de docs. `rtk *` cobre o wrapper de tokens da casa — `rtk err python scripts/…`,
`rtk err mkdocs …`: o `rtk err` roda o comando **bare** e só cospe a saída na falha.)
**Windows nativo (sem WSL):** o harness roda os comandos pela ferramenta **PowerShell**,
que tem **namespace próprio de regra** — nenhuma regra `Bash(...)` casa com ela. Adicione
o espelho (mesma lógica: git + echo + só os gates da sua stack):
```json
{
"permissions": {
"allow": [
"PowerShell(git *)",
"PowerShell(echo *)",
"PowerShell(.\\gradlew.bat *)",
"PowerShell(docker *)",
"PowerShell(dart *)",
"PowerShell(flutter *)",
"PowerShell(fvm *)",
"PowerShell(npm *)",
"PowerShell(node *)",
"PowerShell(python *)",
"PowerShell(python3 *)",
"PowerShell(py *)",
"PowerShell(rtk *)",
"PowerShell(mkdocs *)",
"PowerShell(python -m mkdocs *)",
"PowerShell(python3 -m mkdocs *)",
"PowerShell(py -m mkdocs *)",
"PowerShell(curl.exe -fsSL https://docs.xadm.biz/*)",
"PowerShell(curl.exe -s http://localhost:8000/*)",
"PowerShell(curl.exe -sI http://localhost:8000/*)"
]
}
}
```
(No PowerShell o gradlew é **`.\gradlew.bat`** e o download é **`curl.exe`** — `curl` puro
é alias de `Invoke-WebRequest` e não casa com a regra. O **interpretador Python** varia por
SO — `python3` no Linux, `python`/`py` no Windows —, então o allow-list lista as três formas;
com wildcard por ferramenta qualquer uma casa, e os gates do pré-flight não pedem prompt um a
um na máquina do dev.)
**Trade-off:** as regras de escrita removem o prompt como rede de segurança — a
garantia de "só commita no release" passa a ser a REGRA Nº 2 (conduta), não o
prompt. **Opt-in**: deixe de fora se preferir confirmar cada release.
**Dica de execução:** rode cada `git` **separado e simples** (UMA chamada Bash por
comando), `git` puro (não `rtk proxy`). TRÊS venenos garantem o prompt mesmo com as
regras — são sobre o comando ser **composto/opaco**, não sobre a ferramenta, então o
wildcard por ferramenta não os cura: (1) **pipe** (`| tail`, `| grep`, `| head`) ou
**redirect** (`2>&1`, `> arquivo`) ou string **multi-linha** num bloco único — vários
comandos/operadores de shell juntos NÃO casam com um padrão do allow-list. Cuidado com o
reflexo de anexar `2>&1` a probe de versão (`java -version`, que escreve no stderr): o bare
já casa `java *` **e** o harness captura o stderr — o redirect só quebra o match; (2) **substituição de comando** `$(...)`/backticks (ex.
`git log ...$(git describe)..HEAD`) — o matcher desconfia de `$(...)` por princípio;
(3) **inspecionar arquivo por shell** (`sed`/`grep`/`cat`/`rtk grep`/`cd`). Pegue o valor
num passo (ex. a última tag) e use **literal** no próximo, em vez de `$(...)`.
**No Windows nativo, mais DOIS venenos** (ferramenta PowerShell): (5) **prefixo de env
inline** — `$env:JAVA_HOME='...'; .\gradlew.bat check` é comando **composto** e nunca casa
com regra de prefixo. A cura é **estrutural**, não de allowlist: declare
`"env": { "JAVA_HOME": "<jdk da baseline>" }` no `.claude/settings.local.json` do repo —
o comando volta a ser simples e casável. Toda máquina Windows com JDK default ≠ baseline
do projeto cai nisso; (6) **sufixo `; echo "EXIT=$LASTEXITCODE"`** — composto pelo mesmo
motivo, e desnecessário: o harness já reporta o exit code; o sufixo só quebra o match.
**Inspeção de arquivo = ferramenta `Read`, NUNCA shell.** Ler `VERSION`, ler/conferir
o `CHANGELOG.md` (a seção `[Unreleased]`, o rodapé de links) e ver o diff é tudo pela
ferramenta **`Read`** (ou `git diff`, que é `git` puro) — **nada** de `sed -n`/`grep`/
`cat`/`rtk grep`/`| head`. Foi exatamente uma inspeção com pipe (`sed … | grep`,
`rtk grep … | head`) que disparou o único prompt de um release; o `Read` não precisa de
allow-list e não pede confirmação.
## Particularidades deste projeto
<!-- Mantenha SÓ o bloco da sua stack e apague os outros. -->
**Java/Gradle:**
- **Fonte de versão**: `gradle.properties`, linha `version=X.Y.Z` **OU** o
build script (`build.gradle.kts`/`build.gradle`), linha de topo
`version = "X.Y.Z"` — fonte única do SemVer (o Gradle a aplica como
`project.version`). SemVer puro, sem build number. O `valida-release.py` lê
das duas formas (gradle.properties tem precedência se ambas existirem);
bumpe a que o projeto usa.
- Se houver `@OpenAPIDefinition` com `version` próprio: é a versão do
**contrato HTTP**, desacoplada — não tocar (só sobe em breaking change da
API REST, num bump separado).
- **Gate de código** (pré-flight): `./gradlew check --no-daemon` — testes **e**
checkstyle. **Degradado** (máquina sem Docker/Testcontainers, onde os testes de
integração não sobem): `./gradlew checkstyleMain checkstyleTest`, que é estático e
não precisa de container.
- **Teste OS-desabilitado não roda aqui — não é verde, é cego.** Procure (`Grep`)
`@DisabledOnOs(OS.WINDOWS)`/`@EnabledOnOs(OS.LINUX)` em `src/test`. Se achar, esses testes foram
**pulados** no `check` (você está no Windows) e o verde **não** os cobre — só a CI Linux os roda,
**pós-tag** (verde local, vermelho CI, tag no ar). **Rode em paridade Linux** antes de taggar:
`docker run --rm -v "<repo>":/w -w /w ci-java:<v> ./gradlew check` (mesmo ambiente da CI; `gradlew`
em LF via `.gitattributes`; socket do Docker se usa Testcontainers). Não rodou → **declare no
relatório** que N testes OS-desabilitados não rodaram (degradado, não silêncio) e confie na CI
verde. Detalhe: [gate cego pré-flight×CI](https://docs.xadm.biz/engenharia/ci-testes/#teste-os-desabilitado-e-gate-cego-no-pre-flight).
- **Gate de container** (pré-flight, só app que **deploya**): `docker build -t <slug>:pre .`
Nada mais exercita o `Dockerfile` antes do deploy — o `ci.yml` **não** builda imagem e o
Coolify builda o Dockerfile do repo, então sem este gate o primeiro a descobrir que ele
falta ou quebrou é a **produção** (constituição §3). **Sem daemon Docker na máquina** →
degradado: confira que `Dockerfile` e `.dockerignore` **existem** (`Read`) e **declare no
relatório final** que o build não rodou. **`./gradlew: not found` dentro do build em
checkout Windows** = CRLF no `gradlew` (o `/bin/sh` do Alpine não resolve o shebang
`#!/bin/sh\r`) — cura na raiz: `.gitattributes` com `gradlew text eol=lf` (template
`gitattributes-java` do central), não `dos2unix` na mão **nem** `git archive … |
docker build -` (pipe é veneno de prompt, o PowerShell 5.1 corrompe stream binário no
pipe, e buildaria o HEAD em vez da árvore que o release vai commitar).
**Flutter:**
- **Fonte de versão**: `pubspec.yaml`, linha `version: X.Y.Z+N`. `X.Y.Z` é o
SemVer; `+N` é o build number, que incrementa **+1 a cada release**
independentemente do tipo de bump (alimenta `package_info_plus` /
`version.json`).
- **Gate de código** (pré-flight):
`dart format --output=none --set-exit-if-changed . && flutter analyze && flutter test`.
O `--output=none` **checa sem reescrever** e não é cosmético: o default do `dart format` é
**gravar em disco**, então o gate *conserta* o que deveria só acusar — a 2ª rodada fica **verde**,
o vermelho parece transitório, a correção fica **não-commitada** (o `git add` do passo 4 leva só
manifesto + CHANGELOG) e o CI, que faz checkout limpo da tag, reprova o que passou aqui.
Regra geral: [verificador não muta a árvore](https://docs.xadm.biz/engenharia/ci-testes/#definicao-de-pronto-o-detalhe-testes).
- Repo com **`.fvmrc`** (pin de toolchain): prefixe `fvm` — `fvm dart format …`, `fvm flutter test`.
Dentro do container `ci-flutter:<v>` o SDK está no PATH e o prefixo **não** se usa; por isso os
comandos acima são *bare*.
**Node:**
- **Fonte de versão**: `package.json`, campo `"version": "X.Y.Z"`. SemVer
puro, sem build number.
- **Gate de código** (pré-flight): `npm run lint && npm test`.
**Stack sem manifesto próprio (site estático, scripts):**
- **Fonte de versão**: arquivo `VERSION` na raiz do repo, contendo só
`X.Y.Z`. SemVer puro, sem build number.
- **Gate de código** (pré-flight): não há gate de código separado — o gate do repo
é o de docs (pré-flight, passo 1).
**Docker Compose (serviço self-hosted, ex. PowerSync):**
- **Fonte de versão**: como a stack de código do repo, se houver (Java/Node/…); um repo
só de `compose.yaml` + config usa `VERSION` na raiz (SemVer puro), igual à stack sem
manifesto próprio.
- **Gate de container = SUBIR a stack, não só buildar.** `docker build` prova que a
**imagem compila**; **não** roda bootstrap (init container, migração, load de sync rules)
nem healthcheck — que só disparam no `up`. Bug de bootstrap/healthcheck passa **verde** no
build e morre no **deploy**: o primeiro a rodar a stack de verdade é o **Coolify, em
produção** (constituição §3). Gate:
- Suba a stack **efêmera, volume novo**, e espere os healthchecks (a constituição §5 já
os torna **obrigatórios**): `docker compose -p <slug>-preflight up -d --wait` (chamada
Bash isolada, sem pipe). O `--wait` sai **≠0** se algum serviço fica *unhealthy* ou um
container de bootstrap sai com erro. O nome de projeto próprio (`-p`) dá rede/volume
isolados → volume novo a cada rodada.
- **Sempre derrube** depois, removendo os volumes: `docker compose -p <slug>-preflight down -v`.
- **Repo que PULLA a imagem** (só `image:` no compose, sem `Dockerfile` próprio) → o
compose-up **substitui** o `docker build` (não há o que buildar; o build gate **some** do
relatório). **Repo que builda E compõe** → compose-up **soma** ao `docker build` (build
prova que compila, up prova que sobe). O skill escolhe pela presença de `Dockerfile`.
- **Vermelho (bloqueador)**: a stack **subiu** e um healthcheck reprovou / um container de
bootstrap saiu ≠0 — bug de deploy, igual aos outros gates.
- **Degradado (não-vermelho, DECLARA no relatório)**: sem daemon Docker; **ou** o `up` nem
começou por **falta de `.env`/segredo local** ou **colisão de porta de host** — o `-p`
isola rede/volume, **não** a porta publicada no `ports:` do yaml (pare a stack dev viva
ou rode noutra máquina; o `--wait` casa healthcheck **interno**, não precisa da porta
publicada). Isso é **ambiente, não bug do app**; distinga pela saída (bind/`env not set`
= degradado; container que *subiu* e ficou unhealthy = vermelho). Meio gate é decisão
declarada, não silêncio.
**Comum a todos:**
- **Hosted em**: `https://fonte.xadm.biz/xadm/<repo>.git` (Forgejo, compare
URL `/compare/v<a>...v<b>`).
- **Deploy**: Coolify, gatilho = push em master (a tag é o marco; app **servidor**
deploya por push — [versionamento](https://docs.xadm.biz/padroes/versionamento/)).
- **App que distribui ARTEFATO** (jar/CLI/worker): o marco é a **Release do Forgejo**
(tag + notas do CHANGELOG + **asset anexado**), criada pelo `release.yml` (bloco
opt-in). O operador **baixa o jar da aba Releases** — não recebe cópia manual.
Pós-release: conferir que a Release apareceu com o asset
([versionamento](https://docs.xadm.biz/padroes/versionamento/)).
- **Pós-release**: <!-- notas do app: SENTRY_RELEASE no Coolify, tempo de
build, etc. -->
## Fluxo
### 1. Pré-flight
**Cada comando numa chamada Bash SEPARADA** (podem ser paralelas) — **nunca** num bloco
multi-linha (bloco multi-linha é veneno: pede prompt mesmo com tudo no allow-list). Com o
allow-list por ferramenta (`git *`) flag antes do subcomando já não importa; ainda assim
prefira `git` puro (em chamada não-interativa o git já não pagina).
- `git status --porcelain` — checar working tree.
- `git rev-parse --abbrev-ref HEAD` — branch atual.
- `git fetch origin --tags` — sincronizar tags do remoto.
- Ler a versão atual na fonte de versão pela ferramenta **`Read`** (ver Particularidades).
- `git describe --tags --abbrev=0` — última tag (sem tag = primeira release).
- **Gate de CÓDIGO da stack ANTES de taggar** — o comando está no bloco da **sua** stack em
*Particularidades*. Rode-o **bare, sem pipe**. O `ci.yml` roda *análise estática + testes*, e a
parte de **lint/estilo** (checkstyle, `flutter analyze`) **não é coberta pela análise estática que
você faz lendo o código**: ela só dispara dentro do gate, então escapa até o push da tag e deixa o
CI vermelho com a tag já criada. Vermelho = **bloqueador**, igual aos gates de docs — não taggar.
Se rodar o comando **degradado** (ver Particularidades), isso **não** é vermelho, mas **entra no
relatório final** como gate degradado — pular metade do gate é decisão declarada, não silêncio.
- **Gate de CONTAINER ANTES de taggar (só app que deploya)** — o comando está no bloco da
**sua** stack em *Particularidades*. O `Dockerfile` é o único artefato do repo que **nenhum
CI toca**: o `ci.yml` não builda imagem e o Coolify o builda direto do Git, em produção.
Vermelho = **bloqueador**. Sem daemon Docker → **degradado** (só conferir que os arquivos
existem), que **não** é vermelho mas **entra no relatório final** — meio gate é decisão
declarada, não silêncio. **Stack Docker Compose (serviço self-hosted, ex. PowerSync):** o
gate de container **não** é `docker build` — o `build` não roda bootstrap nem healthcheck;
suba a stack efêmera (`docker compose … up -d --wait` + `down -v`). Ver o bloco *Docker
Compose* em *Particularidades* (substitui o build se a imagem é pull; soma se o repo builda).
- **Gate de DOCS ANTES de taggar** (o deploy é por push; se o gate do CI falhar, a tag
já foi pushada e o **deploy trava sem ninguém ver**): rode o gate de docs que o CI roda mas
que não é coberto pela análise estática local. `node scripts/lint-mermaid.mjs docs/` (o
`mkdocs build --strict` **não** parseia Mermaid; precisa `npm i --no-save mermaid@11 jsdom`) +
`gera-manifesto.py --check` + `valida-frontmatter.py docs/` — e, **se o repo os tiver** (central),
`checa-dogfood.py` + `checa-export-download.py` + `checa-nav.py` + `testa-valida-frontmatter.py` +
`testa-checa-nav.py` + `testa-checa-views.py` + `testa-checa-rotas.py` + `checa-admonition.py` (depois do build). **E o BUILD de docs que o
`docs.yml` roda** — `mkdocs build --strict` (com os hooks) + `javadoc`/`dartdoc` se o app gera:
um **gate de infra vermelho** (ex. Testcontainers/dind) **mascara os gates seguintes** (javadoc,
mkdocs) no CI, então rode-os **aqui** pra não descobrir em prod atrás do vermelho de infra. Rode
**todos** os checks que o CI do repo roda e que a análise estática local não cobre (o conjunto
varia por repo). Qualquer um vermelho = **bloqueador**.
- **Gate da baseline da fábrica (só app Java/Flutter que declara `toolchain`).** O `container:`
do CI é `ci-<stack>:<versão>`; se a versão **não estiver homologada** na fábrica, a imagem
nunca foi buildada e o CI quebra com **"manifest unknown"** — descoberto só aqui, com a tag já
pushada e o deploy travado. Confira **antes de taggar**: leia `docs/app.json` (`toolchain.java`/
`toolchain.flutter`) e, se houver, o `.fvmrc` (campo `flutter`) pela ferramenta **`Read`**; baixe
a baseline com `curl -fsSL https://docs.xadm.biz/toolchain/matriz-baseline.json` (chamada Bash
isolada, sem pipe). Compare, no seu contexto (sem `grep`/`jq`):
- versão declarada **∈** a lista da stack na baseline (ex. `toolchain.flutter` ∈ `.flutter`)? Não →
**bloqueador**: *"flutter <v> não homologada na fábrica — abra PR em `ci-images/matriz-baseline.json`
no repo central, espere a fábrica buildar a imagem, e só então libere. Ver decisão 0003 / runbook
fábrica-imagens-ci."* Não taggar.
- `.fvmrc` (se existe) **==** `toolchain.flutter`? Divergência → **bloqueador** (drift de fonte;
o `container:` segue o `.fvmrc`, mas a fábrica homologa pelo declarado). Reconcilie antes.
- Baseline **inalcançável** (rede/404 — ex. central ainda não redeployou publicando a baseline) →
**avisar e seguir** (não bloquear release por infra); registre o aviso no relatório final.
- **Aviso do bloco Release do Forgejo (soft — só app que distribui artefato).** Se o repo tem
`.forgejo/workflows/release.yml`, leia-o pela ferramenta **`Read`** e procure DOIS marcadores
literais do template canônico:
- `# - name: Publicar Release no Forgejo` (bloco de asset comentado);
- `# contents: write` (permissions comentado — sem isso o POST de Release dá **403**, antes do 422).
Se **algum** dos dois aparece **comentado** no arquivo, ao final do release **avise** (não
bloqueie): *"release.yml tem o bloco 'Publicar Release' comentado (asset / permissions). Se
este app distribui artefato (jar/CLI/worker), descomente ANTES de push da tag — senão
`valida-release.py` passa verde e a aba Releases fica **vazia**, sem download. App só-deploya
(Coolify) ou doc-only: ignore este aviso."* Registre no **Relatório final** (passo 10).
Sem `release.yml` no repo → **skip silencioso** (release manual/sem CI de tag). O aviso é
soft porque o pré-flight não tem sinal em `app.json` de "distribui artefato" — quem sabe é o
dev; o gap que o aviso fecha é *silêncio* (release verde + Releases vazia é descoberto pelo
operador na hora do download; item 95 fechou o irmão barulhento — descomentar o bloco sem
trocar `container:`).
**Worktree limpo com commits à frente do origin é o caso NORMAL — não investigue.** A IA
não commita (REGRA Nº 2): o conteúdo que ela editou nesta sessão é commitado **pelo
usuário**, então ao iniciar o release o esperado é worktree **limpo** e `master` alguns
commits **à frente** do remoto. Isso **não** é violação nem anomalia: worktree limpo →
**assuma que o usuário já commitou e siga**. Não audite a autoria dos commits à frente nem
pare para explicá-los — o que importa é o intervalo última-tag..HEAD (passo 2), não quem
commitou. (Só o passo 2 vazio — nada entre a tag e HEAD — encerra o release sem nada a liberar.)
**Bloqueadores** (parar e pedir orientação ao usuário):
- Working tree com **arquivos de código modificados** (fonte, manifesto de
build, `Dockerfile`). Mudanças apenas em `.claude/`, `logs/` e artefatos
de build (`build/`, `.dart_tool/`, `.gradle/`, `node_modules/`) são
ignoráveis.
- Branch ≠ `master` e ≠ `main` — perguntar se prossegue mesmo assim.
- Tag já existe localmente OU remotamente para a versão calculada — abortar.
### 1b. Guard da Central de Apps (soft)
Confira se o app passou pelo **setup** da Central: o `docs/app.json` tem o bloco `features`?
Leia o `app.json` pela ferramenta **`Read`**.
- **Tem `features`** (mesmo tudo `enabled: false`) → setup considerado; siga.
- **Não tem** (app novo / nunca rodou `/xadm-setup`) → **pergunte UMA vez** (`AskUserQuestion`):
- "Rodar `/xadm-setup` agora" — configurar erros/arquivos/analytics/docs antes de liberar.
- "Este app não usa nenhuma" — segue (declara que não usa a Central).
É **soft**: não bloqueia release de app trivial; só garante que ninguém esquece o setup
(constituição §7 / [Central de Apps](https://docs.xadm.biz/documentacao/central-de-apps/)).
### 2. Listar commits desde a última release
```
git log --pretty=format:'%h %s' <last-tag>..HEAD
```
Lista vazia → não há nada para liberar → reportar e parar.
**Primeira release sem tags**: usar `git log --oneline` cheio para inferir o
CHANGELOG. A versão sugerida vem do manifesto (sem bump automático — o
usuário decide, ex. v1.0.0).
### 3. Inferir tipo de bump (Conventional Commits)
| Padrão encontrado | Bump sugerido |
| --- | --- |
| `feat!:` ou `BREAKING CHANGE:` no corpo | **major** |
| `feat:` (sem `!`) | **minor** |
| Só `fix:`/`chore:`/`docs:`/`test:`/`refactor:`/`perf:` | **patch** |
Calcular a versão SemVer sugerida com base na versão atual do manifesto
(no Flutter, ignorando o `+N`).
### 4. Apresentar resumo e confirmar bump
```
Última release: vX.Y.Z (data) | nenhuma (primeira release)
Versão atual no manifesto: X.Y.Z
Commits desde então (N):
<hash> <subject>
...
Sugestão de bump: <tipo> → v<nova>
```
`AskUserQuestion`:
- header: "Bump"
- question: "Aplicar bump para v<nova>?"
- options:
- "Sim, v<nova>" — aceita sugestão (label exato com a versão calculada)
- "Escolher outro bump" — segunda pergunta com patch/minor/major
- "Cancelar release" — aborta
### 5. Construir entradas de CHANGELOG
Ler `CHANGELOG.md` **com a ferramenta `Read`** (não por `sed`/`grep`/pipe) e identificar
a seção `[Unreleased]`.
**Se `CHANGELOG.md` não existir**: criar do zero seguindo
[Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/) — cabeçalho
padrão + `## [Unreleased]` vazia + a seção da release.
**Caso A — `[Unreleased]` com conteúdo**: usar como está; só mover para a
nova seção `## [X.Y.Z] - AAAA-MM-DD` (data de hoje, fuso local).
**Caso B — `[Unreleased]` vazia ou arquivo novo**: gerar entradas a partir
dos commits:
| Prefixo do commit | Seção CHANGELOG |
| --- | --- |
| `feat:` / `feat!:` | **Adicionado** |
| `fix:` | **Corrigido** |
| `refactor:`, `perf:` | **Alterado** |
| `chore:`, `docs:`, `test:`, `ci:`, `style:` | omitir (internos) |
Seções em **pt-BR** (constituição §1.8), nos nomes da tradução do Keep a
Changelog: Adicionado / Alterado / Corrigido / Removido.
**Exceção**: `chore:` que descreve algo visível ao usuário/operador DEVE
entrar em Adicionado/Alterado — a decisão é semântica, não mecânica pelo
prefixo.
A entrada é uma frase declarativa em PT-BR (não cópia literal do subject).
**Referências no CHANGELOG = texto puro ou URL absoluta, NUNCA link relativo `docs/…`.** O
`CHANGELOG.md` é **embutido em `docs/`** via snippet (`pymdownx.snippets`), então um `[texto](docs/…)`
(que fica certo no Forgejo) vira `docs/docs/…` no mkdocs e **quebra o `--strict`**. Cite decisão/
página como **`` `docs/decisoes/NNNN-*.md` ``** (texto puro) ou `https://docs.xadm.biz/…` (URL absoluta).
Mostrar o rascunho e perguntar via `AskUserQuestion`:
- header: "CHANGELOG"
- options: "Sim, aplicar" / "Editar antes" (ajustes inline, re-mostrar) /
"Pular CHANGELOG" (só bump+tag).
### 6. Aplicar alterações nos arquivos
1. **Manifesto**: gravar a versão nova (no Flutter, também `+N+1`).
2. **`CHANGELOG.md`**:
- seção `## [<nova>] - AAAA-MM-DD` com as entradas;
- `## [Unreleased]` limpa para o próximo ciclo;
- links do rodapé atualizados (mantendo os anteriores):
```
[Unreleased]: https://fonte.xadm.biz/xadm/<repo>/compare/v<nova>...HEAD
[<nova>]: https://fonte.xadm.biz/xadm/<repo>/compare/v<antiga>...v<nova>
```
Primeira release (sem `<antiga>`):
```
[<nova>]: https://fonte.xadm.biz/xadm/<repo>/releases/tag/v<nova>
```
### 7. Mostrar diff e confirmar
`git diff --stat` + `git diff` dos arquivos modificados. `AskUserQuestion`:
- header: "Confirmar"
- options: "Sim, commit + tag" / "Cancelar" (descartar as mudanças — restaurar
os arquivos editados; se o `CHANGELOG.md` era novo, apagá-lo — e parar).
### 8. Commit + tag
**Guarda final (último passo antes do commit) — só repo com manifesto (central).**
Rode `gera-manifesto.py --check` **agora**, depois de §6 já ter aplicado bump/
manifesto/CHANGELOG. O `--check` do pré-flight (§1) rodou **antes** dessas mudanças;
este re-check garante que o manifesto a ser committado reflete o working-tree
**final** — fecha a race "carimba→edita→commita" que deixou um drift subir no 0.26.1
(o `--update` carimbou um estado que não virou o commit, e o CI não-gating não
barrou). Vermelho aqui = **NÃO commitar**: rode `gera-manifesto.py --update`,
reconfira, e só então prossiga.
Modo ghost — sem Co-Authored-By:
```bash
git add <manifesto> CHANGELOG.md
git commit -m "chore(release): v<nova>"
```
A mensagem é **literalmente** `chore(release): v<nova>` — sem corpo, sem
co-author, sem assinatura.
Tag anotada com resumo de **UMA linha** (a tag é só o marco; o detalhe mora no
CHANGELOG). Mensagem de uma linha **de propósito**: uma tag multilinha é o veneno de
prompt do passo 147 (a string com `\n` não casa nenhuma regra de prefixo, e a tag era a
última exceção que ainda pedia autorização) — resumo numa linha casa `git tag *` e roda
sem prompt:
```bash
git tag -a v<nova> -m "v<nova> — <resumo de uma linha>"
```
**Não** adicionar Co-Authored-By na tag também.
### 9. Push (perguntar antes)
`AskUserQuestion`:
- header: "Push?"
- options: "Sim, push" (`git push origin <branch>` e
`git push origin v<nova>`) / "Não, deixar local".
### 10. Relatório final
```
Release v<nova> concluído.
Anterior: v<antiga> (ou "primeira release")
Atual: v<nova>
Branch: master
Tag: v<nova> (anotada)
Push: sim / não
Gates: código verde + container verde + docs verde
(ou: código DEGRADADO — só checkstyle, testes não rodaram (sem Docker);
ou: código CEGO — N testes @DisabledOnOs(OS.WINDOWS)/@EnabledOnOs(OS.LINUX) não rodaram (só CI Linux, pós-tag);
ou: container DEGRADADO — Dockerfile/.dockerignore existem, build não rodou (sem Docker);
ou: container (compose) DEGRADADO — compose.yaml+healthcheck existem, stack não subiu (sem Docker / falta .env / porta em uso);
ou: baseline da fábrica inalcançável — não verificada)
Avisos: (só quando aplicável — ver pré-flight)
- release.yml: bloco 'Publicar Release' comentado (asset / permissions).
Se este app distribui artefato, descomente antes do próximo push de tag.
- CI-verde é AVISO, não gate: a CI roda pós-push (deploy é por push; artefato por tag).
Confirme a run verde em fonte.xadm.biz/xadm/<repo>/actions antes de dar o release por
sólido — vermelho pós-tag trava o deploy do próximo push / deixa a Release sem asset.
Vale DOBRADO se houve "código CEGO" acima (o teste OS-desabilitado só roda lá).
Próximos passos (sugestão):
1. Acompanhar o deploy do Coolify.
2. <notas pós-release do app>
```
## Convenções respeitadas
- Versão lida e gravada exclusivamente no manifesto da stack (fonte única).
- CHANGELOG segue [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/).
- Tag sempre `vX.Y.Z` (com `v`), anotada (`-a`).
- Commit sempre `chore(release): vX.Y.Z`.
- **Nenhum** `Co-Authored-By` em commit ou tag.
- Esta skill é o caminho feliz; a **rede de segurança** é o
`.forgejo/workflows/release.yml` do app, que roda o `valida-release.py` em
push de tag (pega tag fora-de-banda ou bug da skill) — não confundir com o
`ci.yml`, que é o gate de qualidade (lint + testes). Detalhes:
[versionamento](https://docs.xadm.biz/padroes/versionamento/).
## Edge cases
- **Primeira release sem tags**: `git log --oneline` cheio; versão do
manifesto como base (usuário decide).
- **`CHANGELOG.md` não existe**: criar do zero (passo 5).
- **Tag remota existe mas local não**: o `git fetch origin --tags` resolve;
se ainda conflitar, abortar com instrução pro usuário.
- **Working tree sujo só em não-código** (`.claude/`, logs, artefatos):
ignorar silenciosamente.
- **Erro de push**: manter commit/tag locais, reportar, sugerir push manual.
- **Versão do manifesto já diferente do esperado** (bump manual): usar a do
arquivo como base e perguntar antes de prosseguir.
Skill /xadm-docs (.claude/skills/xadm-docs/SKILL.md)¶
Confere se a doc do repo está defasada da constituição vigente e migra com aprovação do usuário (constituição §6, versão-base).
---
name: xadm-docs
description: Confere se a doc E o código deste repo estão defasados da constituição X-Adm (versão-base + manifesto de templates + conformidade de engenharia da stack) e migra com aprovação. Use ao abrir o repo ou quando a constituição/templates mudarem.
---
<!--
Template canônico da skill /xadm-docs — vive no repo central
xadm/documentacao (templates/xadm-docs-skill.md). Copie para
.claude/skills/xadm-docs/SKILL.md no repo da aplicação, junto com:
- templates/checa-constituicao.sh → .claude/checa-constituicao.sh
- o hook SessionStart no .claude/settings.json (snippet no topo do .sh)
Se o fluxo mudar, mude o template no central primeiro e re-derive as cópias.
-->
# /xadm-docs — Doc deste repo vs constituição X-Adm
Confere se a documentação deste repositório está defasada da
[constituição da documentação X-Adm](https://docs.xadm.biz/padroes/constituicao/)
e, se estiver, avalia a diferença e migra **com aprovação do usuário**.
## Fluxo
### 1. Levantar versão e manifesto
Em paralelo:
- Vigente: `curl -fsSL https://docs.xadm.biz/toolchain/constituicao-versao.txt`
- Manifesto de templates: `curl -fsSL https://docs.xadm.biz/toolchain/manifesto.json`
- Base do repo: campo `constituicao` no `docs/app.json` — é a **fonte única**
(o CLAUDE.md repete a informação para contexto, mas o app.json manda).
Sem rede → reportar e parar. Sem `app.json` ou sem o campo → tratar como
"nenhuma versão registrada" e seguir o fluxo.
**Versão da CONSTITUIÇÃO ≠ versão do SITE.** A vigente é **só** o
`constituicao-versao.txt`; a base é **só** o `app.json constituicao:`. **Nunca**
derive a versão da constituição do `VERSION`/`/versao.txt`/tag `vX.Y.Z`/CHANGELOG do
repo central (nem de um clone local dele) — esses são a versão do **site**,
desacoplada (`/versao.txt` é rotulado `site X.Y.Z`). No rodapé do site, é o número
rotulado “Constituição”, não “Site”. Detalhe:
[versionamento](https://docs.xadm.biz/engenharia/versionamento/#duas-versoes-site-x-constituicao).
### 2. Templates defasados (mecânico, não por leitura)
O manifesto lista, por artefato copiado para o app (`templates/docs.yml`,
`mkdocs.yml`, `app.json`, hook, skills), em que versão ele mudou pela última
vez (`mudou_em`). **Marcar como defasado todo arquivo com
`mudou_em` > versão-base do app** (sem versão-base registrada → todos
defasados). **Pule os artefatos do campo `opcionais` do manifesto** (`xadm-docx`,
kit de UI, container) a menos que o app já os tenha — opcional não-instalado **não** é
defasagem; se já instalado, mantenha-o em sincronia como os demais. Não inferir
do texto: é o manifesto que manda quais templates re-baixar. (Não havia esse
passo antes — uma mudança de template já passou despercebida por confiar só na
leitura da constituição.)
**Relevância por stack (evitar falso-positivo).** O `mudou_em` é **por-arquivo-global, não
por-stack** — uma mudança que só toca uma stack (ex. `ci.yml` = Testcontainers/DooD, Java-only)
marca o `ci.yml` defasado em app de **qualquer** stack. Antes de gastar esforço com um defasado de
arquivo **multi-stack customizado-por-app** (o `ci.yml`, que cada app adapta uncomentando o bloco da
sua stack → o diff verbatim é **esperado**, não re-derive cegamente): **cheque a entrada do
`CHANGELOG` da versão `mudou_em`** e veja se a mudança **toca a stack deste app**. Não toca (ex.
delta Java-only num app Flutter) → **não é defasagem pra você**: mantenha a customização local e siga.
Só aplique/re-derive a parte que toca a tua stack.
Para cada defasado, a versão vigente está em
`<raw_base do manifesto><caminho>` — ex.
`https://docs.xadm.biz/toolchain/raw/templates/docs.yml`.
**Destino no app** (o nome do template ≠ caminho no app em alguns casos):
`docs.yml`/`release.yml`/`ci.yml` → `.forgejo/workflows/`; `mkdocs.yml` → raiz;
`app.json` → `docs/app.json`; `checa-constituicao.sh` → `.claude/`; skills de workflow
`templates/x-<nome>/**` → `.claude/skills/x-<nome>/**` (o diretório inteiro: SKILL.md +
`references/`); skills utilitárias `templates/<nome>-skill.md` →
`.claude/skills/<nome>/SKILL.md`; **`overrides-main.html` → `overrides/main.html`**;
**`changelog.md` → `docs/changelog.md`** (estes dois são estáticos-idênticos como o
`checkstyle.xml` — copie verbatim, sem customizar); **kit de UI** (só app com views
server-render): `templates/views/**` → `src/main/resources/views/**` e
`templates/public/**` → `src/main/resources/public/**` (estáticos-idênticos, verbatim — a
identidade muda no central). **A árvore do kit ESPELHA a do app** — reproduza o caminho
inteiro, sem achatar: o `custom-theme.css` mora em `public/css/` porque quem o serve é o
`static-resources`, **não** o Thymeleaf; jogá-lo em `views/` sobe a tela **sem estilo**. O
kit é **opcional** no manifesto: app sem `views/` não o tem → ausência **não** é defasagem
(como o `xadm-docx`). **Junto do kit, um scaffold:** `templates/app.css` →
`src/main/resources/public/css/app.css` — **só se não existir**, e **nunca** re-derive nem
reporte defasagem dele. É o CSS de componentes **do app** (o `layout.html` sempre o linka);
sobrescrevê-lo apagaria trabalho do app. Ele mora **fora** de `templates/public/` de
propósito: o que está lá dentro esta skill copia **verbatim**, e o glob não leva exceção
(0.23.1). **Container**
(só app que deploya no Coolify, constituição §3): `dockerfile-java` → `Dockerfile` e
`dockerignore-java` → `.dockerignore`, **na raiz**. Também **opcionais** — biblioteca,
CLI distribuído como artefato e app Flutter não os têm. São **customizados por app**
(`<slug>` no cache mount, versão do JDK): diff verbatim é **esperado** — re-derive só a
parte que mudou, como no `ci.yml`. **Fim de linha** (só app Java/Gradle):
`gitattributes-java` → `.gitattributes`, **na raiz** — sem ele, checkout Windows deixa o
`gradlew` com CRLF e o `docker build` morre com `./gradlew: not found` no Alpine. Também
**opcional** (app sem wrapper Gradle não o tem).
### 3. Comparar a constituição e avaliar a defasagem
- Versões **iguais** e nenhum template defasado → reportar "documentação em
dia com a constituição vX.Y.Z" e parar.
- Caso contrário → baixar o texto vigente
(`curl -fsSL https://docs.xadm.biz/toolchain/constituicao.md`) e comparar com
o estado **real** do repo:
- estrutura de `docs/` (níveis e nomes de pasta — atenção à nomenclatura
legada: `design/` → `projeto/` e `rd/` → `decisoes/` são renames 1:1;
`guias/` migra **por conteúdo**: manual de usuário → `manual/`
(`tipo: manual`), runbook técnico → `operacao/` (`tipo: operacao`);
**arc42 monolítico** (um doc único com tabela de ADRs) → seis níveis:
na 1ª migração, gere uma **tabela de equivalência ADR-legado → decisão
`NNNN`** (ex. `ADR-004 BYTEA → 0008`) no corpo da decisão nova ou numa
nota, para quem conhecia a numeração antiga achar a nova);
- **conteúdo cross-repo:** doc/runbook achado neste repo que pertence a **outro**
repo → **realocar para o repo dono**, não absorver nem relabelar em `operacao/`
(ou qualquer pasta) deste;
- frontmatter dos documentos (campos, enums, `tipo:`);
- `docs/app.json`, `mkdocs.yml` e workflow de docs;
- regras de convenção (slug estável, glossário próprio, link de volta ao
portal etc.).
Produzir uma lista objetiva: os templates defasados (do passo 2) + o que mais
estiver em desacordo e qual a correção. **Atenção:** renomear arquivo já
publicado exige redirect (slug estável, constituição §5) — apontar isso
quando a migração envolver renames. Lembrar que os templates do app costumam
ter customizações locais (placeholder `<app>`, passos descomentados) — ao
re-baixar, **reaplicar essas customizações**, não sobrescrever cegamente.
### 3b. Conformidade de engenharia (código vs regras da casa)
As regras de *como construir* vivem nas páginas de **Engenharia** e são lidas
**ao vivo** pelas skills de workflow — **não** são copiadas para o app, então
**não** estão no manifesto (não há cópia que envelhece). O risco é o outro: a
regra mudou e o **código** do app pode ter deixado de conformar. Por isso, na
defasagem (versões diferentes):
1. **Detectar a stack** (`docs/app.json` `stack:` ou pelo repo: `pubspec.yaml`
→Flutter; `build.gradle*`+micronaut→Micronaut; `build.gradle.kts`→Java; etc.).
2. **Baixar as regras vigentes da stack** (markdown cru, agora publicado):
`curl -fsSL https://docs.xadm.biz/toolchain/raw/engenharia/<stack>.md`
(+ os transversais `stack.md`, `versionamento.md`, `ci-testes.md`).
3. **Re-auditar o CÓDIGO** do app contra elas — libs/padrões da casa, exit codes,
observabilidade/logs, gate de CI, health check, definição de pronto. Não é
re-baixar arquivo: é conferir se o código segue a regra (ex. a taxonomia de
exit code mudou? o app ainda usa a lib indicada?).
4. **Toolchain ∈ baseline da fábrica** (só app Java/Flutter). A versão de stack
declarada (`docs/app.json` `toolchain.*` e, se houver, o `.fvmrc`) precisa estar
**homologada** na fábrica de imagens de CI — senão o `container: ci-<stack>:<v>`
aponta para imagem inexistente e o CI quebra com **"manifest unknown"**, hoje
descoberto só no 1º release. Baixe a baseline
(`curl -fsSL https://docs.xadm.biz/toolchain/matriz-baseline.json`) e confira que
cada versão declarada ∈ a lista da stack. Fora da baseline → entra na lista como
*"toolchain <v> não homologada — abra PR em `ci-images/matriz-baseline.json` no
repo central e espere a fábrica buildar a imagem ANTES de o app apontar o
`container:` (decisão 0003 / runbook fábrica-imagens-ci)"*. Também acuse
`.fvmrc` ≠ `toolchain.flutter` (drift de fonte). É **aviso cedo**: aqui não bloqueia
(a saída é a lista); o gate duro é no pré-flight do `/xadm-release`. Baseline
inalcançável (rede/404) → registrar e seguir, não travar a conferência.
5. **JaCoCo suporta o class-major do Java** (só app Java que mede cobertura com JaCoCo). O JaCoCo
**<0.8.15 não instrumenta Java 25** (class major 69): a task cospe *"Unsupported class file major
version 69"* e o relatório fica **válido na aparência mas vazio** — **falso 0% silencioso** (§6, e
o gate nem roda o report). A `jacoco.toolVersion` do app precisa suportar o class-major do Java
homologado (**Java 25 → ≥ 0.8.15**). Fora disso → entra na lista. Ver
[Java/Micronaut §Testes](https://docs.xadm.biz/engenharia/java-micronaut/).
6. **UI padrão em app server-render** (só app que renderiza HTML no servidor —
Micronaut Views/Thymeleaf; detecte por `micronaut-views-thymeleaf` no
`build.gradle*` ou por `src/main/resources/views/`). Constituição §5 (UI dos apps):
a tela usa o **layout padrão X-Adm** (`layout.html`/`custom-theme.css` do kit, paleta
do `xadm.css`), **não** visual ad-hoc — mesmo telas só de admin/ops/dev atrás do gate
de auth. Tem views mas **não** tem o `layout.html` do kit (ou tem CSS próprio fora da
paleta, `<table border>` cru etc.) → entra na lista: *"adotar o layout X-Adm — baixar o
kit `templates/views/**`+`templates/public/**` e puxar os fragments `head`/`navbar`/
`scripts`"* ([Java/Micronaut §UI](https://docs.xadm.biz/engenharia/java-micronaut/)).
O kit já sincroniza como arquivo (passo 2, opcional); esta é a cobrança de **usar** o
layout onde ele deveria estar. App sem view server-render (Flutter, headless) **não** é
alcançado.
7. As não-conformidades entram na **mesma lista** do passo 3, para confirmação.
A saída aqui é mudança de **código/config do app** (com aprovação), nunca a
substituição de um arquivo baixado.
Páginas de engenharia **não** entram no manifesto por desenho — regra viva lida
ao vivo ≠ artefato copiado que envelhece. O gatilho é o bump da constituição.
### 4. Confirmar antes de migrar
Apresentar a lista e perguntar (`AskUserQuestion`):
- "Migrar agora" — aplica as correções listadas;
- "Só registrar a versão" — a doc já está conforme; só atualiza o campo;
- "Cancelar" — não mexe em nada.
### 5. Migrar (se aprovado)
1. Aplicar as mudanças aprovadas.
2. Re-baixar do site cada template defasado (passo 2) e reaplicar as
customizações locais do app.
3. Atualizar `constituicao` no `docs/app.json` para a versão vigente.
4. Atualizar a menção à versão no `CLAUDE.md` do repo (manter os dois iguais).
5. Validar com validador **fresco** (um validador stale dá "0 erros" falso —
constituição §6). Baixar **na hora** para arquivo efêmero, **nunca** reusar
download de turno anterior nem cache em `/tmp`:
```bash
curl -fsSL https://docs.xadm.biz/toolchain/valida-frontmatter.py -o /tmp/vf.$$.py
python3 /tmp/vf.$$.py docs/
```
A **1ª linha** da saída diz a versão que o validador implementa (ex.
`valida-frontmatter — constituição 0.6.0`). **Conferir que ela == a vigente**
(`constituicao-versao.txt`, passo 1). Divergência (ou `dev`) = validador
stale/errado → **abortar alto e re-baixar**, NÃO confiar no "0 erros". Depois,
`mkdocs build --strict`.
6. **Declarar deferimentos em voz alta** (regras de IA nº 3, §1.6): se a migração
migrou a **estrutura** mas deferiu reescrever **conteúdo** para uma barra nova
(ex. etapas para a "tecnicamente completa" do §2), diga explicitamente no
fechamento — *"deferido X porque A/B/C"*, nunca como rodapé. E se o repo ainda
**não publicou** (sem slug no ar), o rename/redirect custa zero → **ofereça
fazer agora** em vez de deferir como legado.
7. **Não** commitar sem o usuário pedir.
## Regras
- **Validador sempre fresco (constituição §6):** nunca reusar um download de
validador de turno anterior nem cache em `/tmp`; baixar na hora e conferir a
versão impressa na 1ª linha == vigente ANTES de confiar no resultado. Um
"0 erros" de validador stale é falso atestado — foi o que deu uma migração por
completa sem estar.
- A REGRA Nº 1 do CLAUDE.md vale aqui: ambiguidade ou mais de um caminho →
apresentar opções com recomendação e confirmar antes.
- Brecha ou ambiguidade na **própria constituição** → não contornar
localmente: gerar o texto do problema e orientar o usuário a colá-lo numa
sessão do Claude Code no repo central `documentacao` (constituição §6).
Skill /xadm-setup (.claude/skills/xadm-setup/SKILL.md)¶
Configura as funcionalidades da Central de Apps (erros,
arquivos, analytics, dashboards): lê o app.json, pergunta o que ativar,
provisiona via broker e grava o client-grade de volta no app.json. Rode
depois de codar e antes da release (constituição
§7).
---
name: xadm-setup
description: Configura as funcionalidades da Central de Apps (erros/GlitchTip, arquivos/Garage, analytics, docs) em build-time — pergunta o que ativar, provisiona via o auth-broker e grava o resultado client-grade no app.json. Rode depois de codar e ANTES da /xadm-release.
---
<!--
Template canônico da skill /xadm-setup — vive no repo central xadm/documentacao
(templates/xadm-setup-skill.md). Copie para .claude/skills/xadm-setup/SKILL.md no
repo do app. Mude AQUI primeiro e re-derive. Contrato: docs.xadm.biz/documentacao/central-de-apps.
-->
# /xadm-setup — Configurar funcionalidades da Central de Apps (build-time)
Liga as funcionalidades de infra da X-Adm a **este** app, **em build-time**: pergunta o que
usar, **provisiona via o `auth`** (que tem os tokens admin dos provedores) e **grava o resultado
client-grade no `docs/app.json`** — que o build embarca. **Nada é buscado no boot.** Contrato:
[Central de Apps](https://docs.xadm.biz/documentacao/central-de-apps/).
> **Autenticação (M2M):** o broker de setup autentica por **token de serviço** (não login humano).
> A skill conhece só o **formato** e **pede o valor** ao usuário — **NUNCA** embuta o token nem sua
> regra de geração (vem dos admins / do `auth` do app, **fora deste template**). **Mesmo com o fonte
> do `auth` à mão** (o filtro que valida o token, com a fórmula à vista): **peça o token ao usuário —
> não o derive da fórmula**. Computar um token de auth a partir do fonte é bypass, não conveniência.
> **NÃO commita** (REGRA Nº 2): grava o `app.json` e entrega a mensagem pronta; quem commita é você.
## Fluxo
### 1. Ler o estado atual
Leia `docs/app.json`: `app_id`, `grupo`, **`cliente_id`** (a CHAVE canônica do tenant) e `cliente`
(rótulo de exibição), e o bloco `features` (o que já está `enabled`). **Não confunda os dois:** o
`cliente_id` (minúsculo, `clientes.cliente_id`) é o que vai ao broker como **identidade de tenant**;
o `cliente` (`"Vantroba"`) é **só display** (label de coleção, path no portal). **Fail-closed:**
grupo=cliente sem `cliente_id` válido (minúsculo kebab) → **pare e peça a chave**; **jamais** derive a
chave do display (foi a raiz de um incidente — decisão 0008). Detecte a **stack** (`stack:`/repo). Sem
`app_id` → peça/gere um (kebab, o mesmo namespace dos grants). Sem rede/sem o **token de serviço de
setup** → reporte e pare.
### 2. Perguntar por funcionalidade (`AskUserQuestion`)
Uma pergunta por item; o que já está `enabled` aparece como "já configurado":
- **GlitchTip (erros)** — **default: sim** (observabilidade é baseline da constituição).
- **Garage (arquivos)** — opt-in.
- **Analytics (Aptabase + Metabase)** — opt-in.
- **Central de Documentos (publicar docs)** — opt-in.
### 3. Provisionar (por "sim" novo) — via o auth-broker
Para cada funcionalidade escolhida, chame o broker do `auth`
(`POST {AUTH}/api/setup/provision`, com o **token de serviço de setup**) com o **bloco de identidade
do `app.json`** — `app_id`, `feature`, `grupo` e, para `grupo=cliente`, **`cliente_id`** (a chave, não
o display; ver abaixo). **Campos obrigatórios são contrato travado nos dois lados:** o broker rejeita
payload incompleto (**400**, não 500) e este guia declara os mesmos campos — não mande menos "porque o
resumo era `(app_id, feature)`" (foi esse drift que deu 500 em produção). O `auth`
provisiona **idempotente** (reusa se já existe) e devolve o **client-grade**. **Identidade de tenant =
`cliente_id`** (a chave, não o display): o broker escopa/deduplica coleção e grants pela **chave**,
que ele **valida contra o catálogo `clientes` do `auth` (fail-closed** — chave desconhecida **não**
provisiona); o `cliente` só rotula. Passos por feature:
- **glitchtip:** confirma/cria a **org** xadm → cria/reusa o **projeto** → grava `dsn` no bloco
`features.glitchtip`. O broker **nomeia o projeto pelo `app_id`** (slug estável; o `dsn` usa o id
numérico do projeto, não o slug). O `SENTRY_AUTH_TOKEN` (upload de sourcemap) é **secret** —
registrado no broker, injetado no deploy (não vai no `app.json`).
**Instrumentar o código (scaffold + guia, com confirmação — NÃO auto-edita o negócio):** (1) instale
o SDK (Flutter → `sentry_flutter`; outra stack → o SDK Sentry-compatível); (2) scaffolde o **init
stack-específico** — Flutter: envolver o `runApp` com `SentryFlutter.init(…, appRunner:)` + handlers
(`FlutterError.onError`, `PlatformDispatcher.onError`) — **editando o `main` só com o OK do dev**;
(3) **guie** os pontos de captura (gaps async, fronteiras de integração/IO), sem espalhar `try/catch`;
(4) **ofereça** scaffoldar a **rota de diagnóstico `/test`** (opt-in, com o OK do dev) — tela/endpoint
**oculto e login-gated** que envia uma exceção de teste ao GlitchTip, pra smoke-test pós-deploy sem
esperar um erro real. Recipe fino por stack (init + `/test`): [engenharia/flutter](https://docs.xadm.biz/engenharia/flutter/)
/ [engenharia/java-micronaut](https://docs.xadm.biz/engenharia/java-micronaut/).
- **garage:** provisiona bucket+key → grava `bucket`/`endpoint` em `features.garage`. A **key de
escrita** é secret (web/server → Coolify; **mobile** não usa — escrita via presign runtime).
- **analytics:** o broker provisiona o **Aptabase** (→ `aptabase_key` **+** `aptabase_host` — a key
`A-SH-` self-hosted é inútil no cliente sem o host; o SDK Flutter exige `InitOptions(host:)`). Como o Aptabase **não tem
API**, o broker **forja o token de sessão** (JWT HS256 assinado com o secret do próprio tool, no
env do `auth`) e cria o app → `A-SH-…` — padrão "provider sem API → forjar o token"; mecânica e
guardas em [central-de-apps](https://docs.xadm.biz/documentacao/central-de-apps/). Depois, **pergunte o
arquétipo do app antes dos eventos** (`AskUserQuestion`) — **produto** (muitos usuários; importa
comportamento/retenção) ou **interno/admin** (poucos usuários; importa **auditar a ação sensível**)
— e **adapte o baseline sugerido**: produto → login/logout/`screen_view`/criar-alterar-excluir;
interno/admin → a **ação sensível** de cada fluxo (aprovar/rejeitar, publicar, resetar — quem fez o
quê; `screen_view`/retenção têm valor baixo). Então **scaffolde o helper só para os eventos escolhidos**
(**não** auto-edite CRUD espalhado — instrumentação custom é sua). **Diga ao dev o caveat:** evento
client-side é **bypassável** → é visibilidade **operacional, NÃO audit trail**; auditoria de segurança
de verdade (a ação sensível como registro imutável) pertence ao **backend**, não ao Aptabase. **Após
os eventos**, o broker
**auto-configura o Metabase** (API real): cria `collection(por cliente_id, rotulada pelo cliente) → dashboard(app) → cards
baseline adaptados aos eventos`; a skill **grava o `features.analytics.metabase_dashboard_id`** que
o broker devolve (pro embed futuro). Estrutura/idempotência/sub-estados em
[central-de-apps](https://docs.xadm.biz/documentacao/central-de-apps/).
- **docs:** confirma `slug` (kebab, imutável) + `grupo`/`cliente` (afeta o path no portal); o
pipeline `docs.yml` já publica.
**Confira o resultado por feature — NÃO engula falha.** A resposta do `/provision` carrega o **estado
real** por provider: provisão (`state`/`last_error`) e injeção do secret (`injected` + motivo). Em
**falha de provisão**, reporte o `last_error` e **não grave** `enabled` sem o ref (nada de `app.json`
meia-boca). Em **`injected:false`**, avise o dev: recurso Coolify ausente → o guard de deploy injeta;
token inválido/erro (ex. 401) → **setar o env à mão**. **Nunca re-chame `provision` "pra confirmar"**
— confie na resposta honesta (re-chamar já gerou projeto GlitchTip duplicado).
### 4. Gravar no `app.json`
Escreva os refs **client-grade** no bloco `features.<nome>` (com `enabled: true`). **Segredo
nunca** vai no `app.json` (é público). Registre no broker os **secrets** que o deploy injeta.
### 5. Entrega da config no build (por plataforma)
A **fonte é o `app.json`**; o build o lê e injeta via **`--dart-define`** (Flutter). **Garanta a
ponte `app.json`→`--dart-define` — ela pode não existir no scaffold.** Não assuma que "já lê o
`app.json`": o Dockerfile/CI scaffoldado costuma **hardcodar ARGs** e nem ter `jq`. A **receita**
(instalar `jq` + `RUN … --dart-define=$(jq -r .features.<x>.<ref> docs/app.json) …`) está em
[engenharia/flutter](https://docs.xadm.biz/engenharia/flutter/) (§Identidade/build, "a ponte
`app.json`→`--dart-define`"):
- **Flutter web / server (Coolify):** a ponte vive no **Dockerfile** (lê o `app.json` committado via
`jq` → `--dart-define`; client-grade **não** precisa de build-arg do Coolify — só segredo precisa).
**Se o `.dockerignore` exclui `docs/`, a ponte tem DUAS metades — o Dockerfile ler `docs/app.json`
E a exceção `!docs/app.json` no `.dockerignore`** (senão o arquivo não entra no contexto e o deploy
quebra); as duas viajam no mesmo PR ([coolify](https://docs.xadm.biz/infraestrutura/coolify/#dockerfile-que-le-docsappjson-case-a-excecao-no-dockerignore)).
- **Flutter mobile (CI, sem Coolify):** a mesma ponte no **`ci.yml`** (`flutter build apk --dart-define=…`).
**Se a ponte não existe, crie-a** (não é "confira e siga") — é o que fecha o gap entre o que este
passo pressupõe e o que o scaffold entrega.
### 6. Fechamento
Reporte o que foi configurado (+ os secrets que o deploy vai injetar) e **entregue a mensagem de
commit** do `app.json` (ghost, sem assinatura de IA). Rode a `/xadm-release` depois — o guard dela
confere que o setup foi feito.
## Regras
- **Idempotente:** re-rodar lê o `app.json` e só provisiona o "sim" novo.
- **Não commita** (REGRA Nº 2); não instrumenta código arbitrário (scaffold + guia).
- Brecha/ambiguidade no **padrão** → gere o texto do problema e leve ao repo central
(constituição §6). O provisionamento real (`/api/setup/provision`, injeção no Coolify) é do
`auth` (Camada 2) — se o broker ainda não existe, registre o que faltou e pare.
Skill /xadm-docx (.claude/skills/xadm-docx/SKILL.md)¶
Exporta um pré-projeto ou o livro do projeto para .docx no padrão visual X-Adm,
baixando o pipeline fresco da toolchain. Use quando a diretoria pedir Word
(constituição §1.3). Receita completa em
Exportar para .docx.
---
name: xadm-docx
description: Exporta um pré-projeto (nível 1) ou o livro do projeto (nível 2) para .docx no padrão visual X-Adm (A4, Verdana, cabeçalho/rodapé/logo, sumário, Mermaid como imagem), baixando o pipeline FRESCO da toolchain. Use quando a diretoria pedir Word (constituição §1.3).
---
<!--
Template canônico da skill /xadm-docx — repo central xadm/documentacao
(templates/xadm-docx-skill.md). Copie para .claude/skills/xadm-docx/SKILL.md no
repo da aplicação. O PIPELINE (scripts + reference + logo) NÃO é copiado pro app:
a skill baixa fresco da toolchain a cada run (fonte única). Mude AQUI primeiro e
re-derive. Ver docs/documentacao/export-docx.md.
-->
# /xadm-docx — Exportar para .docx (padrão X-Adm)
Gera um `.docx` no visual da X-Adm a partir de um Markdown da `docs/` —
**pré-projeto** (`docs/pre-projeto/index.md`) ou o **livro** do projeto
(`docs/projeto/index.md`). O `.docx` é **saída gerada** (constituição §1.3): NÃO
se commita. O pipeline vem **fresco da toolchain** (não há cópia no app que envelheça).
## Pré-requisitos (conferir antes)
- **pandoc** — obrigatório (`pandoc --version`). Ausente → instruir a instalar
(`apt install pandoc` / `brew install pandoc` / Windows: instalador em pandoc.org) e parar.
- **mmdc** (`@mermaid-js/mermaid-cli`) — recomendado, renderiza Mermaid **offline**.
Ausente: o pipeline **falha** em doc com diagrama (não vai à rede por default). Só com
`MERMAID_RENDERER=ink` ele usa o serviço externo mermaid.ink — que **ENVIA o diagrama
à rede**: NÃO usar com pré-projeto/conteúdo confidencial (§1.9, veto ZIM).
- **python3** — só stdlib.
## Fluxo
### 1. Escolher a entrada
Detectar o que existe: `docs/pre-projeto/index.md` e/ou `docs/projeto/index.md`.
Os dois → `AskUserQuestion` (pré-projeto / livro). Nenhum → reportar e parar.
O livro embute `projeto/modelagem.md` via `--8<--` (o pipeline expande sozinho).
### 2. Baixar o pipeline FRESCO da toolchain
Sem rede → reportar e parar. Para um diretório efêmero (NUNCA reusar download de
turno anterior nem cache em `/tmp` persistente):
```bash
DL="$(mktemp -d)/export-docx"; mkdir -p "$DL/assets"
BASE=https://docs.xadm.biz/toolchain/raw/scripts/export-docx
for f in export_docx.py expand_snippets.py admonitions.py render_mermaid.py make_reference.py \
frontmatter.lua xadm-reference.docx; do
curl -fsSL "$BASE/$f" -o "$DL/$f"
done
curl -fsSL "$BASE/assets/logo-xadm.png" -o "$DL/assets/logo-xadm.png"
```
(Confira que a constituição vigente — `curl -fsSL
https://docs.xadm.biz/toolchain/constituicao-versao.txt` — bate com a base do app;
defasagem grande → considerar `/xadm-docs` antes.)
### 3. Gerar o .docx
**Sem 2º argumento**, o nome de saída é **auto-descritivo** —
`<slug>-<nível>[-v<versão>].docx` na raiz do repo (rastreável p/ entrega, sem colisão
entre apps/releases). Ex.:
```bash
python3 "$DL/export_docx.py" docs/pre-projeto/index.md
# ou o livro: python3 "$DL/export_docx.py" docs/projeto/index.md
# -> ex.: thoms-integracao-produto-ecom-projeto-v0.1.0.docx
```
slug = nome do diretório-raiz; nível = pre-projeto|projeto (pelo caminho); versão =
`git describe --tags` → `VERSION` → omitida. Passe um 2º arg só p/ forçar outro nome.
Diagrama confidencial sem mmdc instalado → NÃO force `.ink`; oriente instalar o mmdc.
### 4. Entregar
Reportar o caminho do `.docx` gerado. **NÃO commitar** (REGRA Nº 2) — é saída gerada,
fica fora do Git: o `.gitignore` do app deve ter **`/*.docx`** (root-anchored — ignora a
entrega na raiz, mas NÃO um pré-projeto `.docx` commitado em `docs/pre-projeto/`, que o
§1.3 permite). Se a diretoria quer o arquivo, é entrega manual (e-mail/drive), não versão.
## Regras
- O `.docx` é **gerado**, nunca a fonte — a fonte é o Markdown em `docs/` (§1.3).
- Pipeline sempre **fresco** da toolchain; não copiar os scripts pro app.
- Mermaid **offline por default**; `.ink` só com decisão explícita e conteúdo não confidencial.
- Limitação conhecida (1ª onda): o `.docx` do livro traz prosa autorada + embeds + Mermaid;
conteúdo injetado por hook do MkDocs (changelog/etapas) fica fora. Ver
[export-docx](https://docs.xadm.biz/documentacao/export-docx/).
- REGRA Nº 1: ambiguidade (qual entrada, qual destino) → perguntar, não adivinhar.
Skills de workflow X-Adm (desenhar → definir → refinar → planejar → implementar → documentar)¶
O fluxo de trabalho da casa, modelado no ACT 1.0. Stack-aware: detectam a stack e seguem
a base de conhecimento da Engenharia daquela stack; carregam ainda
references/<stack>.md com os concerns por estágio. x-implementar não commita (REGRA Nº 2);
x-documentar destila para docs/ e refina a base (loop §6). Cada skill é um diretório
templates/<nome>/ (SKILL.md + references/) — copie o diretório inteiro para .claude/skills/<nome>/.
---
name: x-desenhar
description: Conduz a entrevista de uma feature e grava o prompt/design (`.ia/NNN-*-prompt.md`) com o rastro de decisões L# embutido, stack-aware. Use ao iniciar uma feature/correção, antes de definir a spec.
argument-hint: "[pedido, ideia, arquivo-fonte ou vazio]"
tools: [Read, Glob, Grep, Write, Edit, AskUserQuestion, Skill]
---
<!--
Template canônico da skill /x-desenhar — repo central xadm/documentacao
(templates/x-desenhar/SKILL.md + references/). Copie o DIRETÓRIO inteiro para
.claude/skills/x-desenhar/. Mude AQUI primeiro e re-derive. Fluxo:
desenhar → definir → refinar → planejar → implementar → documentar.
Derivado do act-interview (ACT 1.0) + xadm-spec §1–4, reescrito para o padrão
X-Adm — NÃO commita (REGRA Nº 2), stack-aware, lineage .ia/ (§1.9).
-->
# /x-desenhar — Entrevistar a feature e gravar o prompt (X-Adm)
Entrevista o pedido até ter **linguagem compartilhada, intenção clara, dependências de decisão
resolvidas e contrato-de-produto suficiente** para uma spec fiel — e **grava o resultado** em
`.ia/NNN-titulo-prompt.md` (o design + o rastro de decisões **L#** embutido). É o **1º passo** e
a **origem da linhagem** `.ia/` (prompt→spec→plan, §1.9). Próximo: `/x-definir`.
## 0. Stack e base de conhecimento — SEMPRE primeiro
1. **Detecte a stack:** `docs/app.json` `stack:` — ou pelo repo (`pubspec.yaml`→Flutter;
`build.gradle*`+micronaut→Micronaut; `build.gradle.kts` sem framework→Java; `package.json`
→Node; MkDocs→docs).
2. **Leia a base de conhecimento da casa** dessa stack — o **idioma** (libs, logs, padrões) que a
feature deve seguir: página `docs.xadm.biz/engenharia/<stack>/` + `engenharia/stack.md`.
3. **Carregue o reference do estágio** se existir: `references/<stack>.md` desta skill traz os
concerns de **entrevista** específicos da stack (ex. Flutter: navegação, estados, a11y,
segredos, cleanup). Stack sem página/reference → Stack da casa + arquétipo mais próximo; marque
o que faltou (feedback §6).
4. Se o hook de sessão acusou defasagem da constituição, rode `/xadm-docs` antes.
## 1. Postura da entrevista
**Uma pergunta por vez.** Prefixe cada pergunta ao usuário com `Pergunta N:` e incremente `N`.
Espere a resposta antes de seguir. Para cada pergunta, dê **uma recomendação concreta** o
bastante pra virar decisão de spec (labels exatos, transições de estado, formato de saída,
requisito negativo quando evita ambiguidade). Razão curta; trade-off só quando muda a decisão.
Perguntas livres na descoberta; opções estruturadas (`AskUserQuestion`) nos checkpoints.
## 2. Antes de perguntar — explorar
Não pergunte o que o projeto responde. Glob/Grep/Read no que o pedido toca: código/testes/docs
da feature; implementações de referência **no idioma da stack** (§0); pontos onde a suposição do
usuário parece divergir do código. **Sem input / vago:** `AskUserQuestion` — "novo recurso /
corrigir-melhorar / investigação" + peça a descrição. **Arquivo (`.md`/path):** leia e use como
base. Não produza outros arquivos além do prompt (§5). Não implemente.
**Refactor/dedup — caracterização precede refactor:** mesmo **nome** de classe ≠ mesmo **corpo**;
antes de propor "unificar/deduplicar N iguais", **leia os corpos** e confirme que são idênticos —
senão a tarefa nasce errada (caso real: 6 variantes `_Measure` que pareciam 1).
## 3. Prioridade das perguntas
1. **Framing** — resolve o conceito de domínio, a linguagem voltada ao usuário, a intenção.
2. **Crítico** — bloqueia a implementação ou cria risco de segurança/dado.
3. **Importante** — afeta bastante UX/manutenção.
4. **Nice-to-have** — tem default razoável; declare a suposição, só pergunte se sobrar espaço.
Só perguntas que **mudam a implementação**. Não pergunte o já dito/inferível nem o default da
casa (ex. HTTP=OkHttp). Prefira contrato-de-produto (comportamento observável, entrada/saída,
estado, erro, compatibilidade) a preferência de implementação. **Capacidade de lib de terceiro:
confirme na FONTE** (código/changelog da versão em uso, pesquisador de docs da stack) antes de
tratar como resolvida — supor API inexistente é inventar capacidade (constituição §1.2).
## 4. Desafiar a linguagem e testar cenários
Trate o vocabulário como parte do design. Palavras concorrentes pro mesmo conceito → pergunte
qual é canônica. Termo vago/sobrecarregado → proponha um mais afiado antes de resolver o
comportamento. Terminologia durável resolvida → registre **na hora** no `docs/glossario.md` do
app (constituição §5) — **NÃO** crie `GLOSSARY.md` na raiz. Caminhe as decisões por **cenários
reais** que expõem bordas, falhas, sequência e colisões de nome; resolva cadeias de dependência
em ordem (nome antes de storage, storage antes de migração).
## 5. Gravar o prompt (`.ia/NNN-titulo-prompt.md`)
Quando framing, bloqueios e escolhas de direção estiverem resolvidos, **grave o prompt** — o
design da feature + o rastro de decisões. **`NNN` é o próximo id livre da LINHAGEM** (a feature),
não uma sequência global; o slug é a feature. Ex.: `.ia/016-exportar-frota-prompt.md`. A spec e o
plano herdam esse `NNN`+slug (`-prompt`→`-spec`→`-plan`). **Nunca** grave segredo — placeholder
(`<TOKEN>`, `${VAR}`) + ponteiro pro valor real (o `.ia/` é versionado, §1.9). Forma:
```
---
titulo: "..."
tipo: prompt
atualizado: AAAA-MM-DD
---
# <título>
## Contexto / objetivo quem usa, pra quê, por quê; stack + base (§0).
## Design / abordagem o desenho no idioma da stack; o que evitar e por quê.
## Decisões (rastro L#) L1, L2… cada uma: Pergunta · Resposta · Decisão · (Razão/Alternativas).
## Em aberto o que ficou não resolvido (vira Blocking/Open na spec).
```
O bloco **Decisões (L#)** é o **Interview Ledger embutido** (não arquivo separado): cada decisão
materialmente resolvida vira um `L#` estável, que a `/x-definir` referencia na spec. Registre só o
que afeta implementação/escopo/terminologia — não conversa trivial.
## 6. Checkpoint — pronto pra spec
Diga: "Prompt salvo: `.ia/NNN-...-prompt.md`." `AskUserQuestion`: **"Definir a spec (recomendado)"**
→ `/x-definir` (na mesma sessão, preservando o contexto) · **"Continuar a entrevista"** ·
**"Parar"**. Se o usuário pedir a spec com bloqueios ainda abertos, honre — preserve-os no prompt
como "Em aberto" pra spec carregar como Blocking Questions.
> Não commita (REGRA Nº 2). O `.ia/` não é conteúdo publicado. Diante de ambiguidade ou decisão
> com mais de um caminho, PERGUNTE (REGRA Nº 1) — não decida sozinho.
---
name: x-definir
description: Escreve a spec X-Adm (`.ia/NNN-*-spec.md`) a partir do prompt/entrevista, no formato do ACT 1.0, referenciando o rastro L#. Use depois de /x-desenhar (ou direto da conversa).
argument-hint: "[prompt .ia/NNN-*-prompt.md ou vazio]"
tools: [Read, Write, Glob, Grep, Bash, AskUserQuestion, Skill]
---
<!--
Template canônico da skill /x-definir — repo central xadm/documentacao
(templates/x-definir/SKILL.md + references/). Copie o DIRETÓRIO para
.claude/skills/x-definir/. Mude AQUI primeiro e re-derive. Fluxo:
desenhar → definir → refinar → planejar → implementar → documentar.
Derivado do act-create-spec (ACT 1.0) + xadm-spec §5, padrão X-Adm —
storage .ia/ (NÃO .act/), NÃO commita (REGRA Nº 2), stack-aware.
-->
# /x-definir — Escrever a spec (X-Adm)
Escreve uma spec **tão clara que a implementação corre sem adivinhação** a partir do prompt de
`/x-desenhar` (ou direto da conversa). Formato do ACT 1.0, **storage X-Adm** (`.ia/NNN-*-spec.md`,
§1.9). Não reentrevista — mas, diante de **ambiguidade genuína ou decisão com mais de um caminho,
PERGUNTE** (`AskUserQuestion`, REGRA Nº 1): isto **sobrepõe** a postura minimalista do ACT.
## 0. Stack e base de conhecimento — primeiro
Detecte a stack e leia a base da casa (`engenharia/<stack>` + `stack.md`) — §0 da `/x-desenhar`.
Carregue `references/<stack>.md` desta skill (o que capturar por seção da spec, por stack). Hook
de defasagem → `/xadm-docs` antes.
## 1. Origem: prompt OU conversa
- **Com prompt** (`.ia/NNN-*-prompt.md` de `/x-desenhar`): leia-o; a spec **herda o `NNN`+slug**
(só troca `-prompt`→`-spec`) e **referencia os `L#`** do prompt inline.
- **Sem prompt** (spec rápida direto da conversa, como o `act-create-spec`): use o **próximo `NNN`
livre** e um slug próprio; extraia da conversa as decisões materialmente resolvidas e escreva um
**ledger mínimo embutido** (≥1 record `L1` `current`) na própria spec.
Se ainda não explorou o alvo, inspecione rápido os arquivos/docs/testes relevantes; use o
vocabulário e os padrões reais do repo (idioma da stack, §0).
## 2. Escrever a spec (`.ia/NNN-titulo-spec.md`)
**Nunca** carregue segredo do prompt pra spec — placeholder + ponteiro pro valor real (§1.9).
Referencie os `L#` inline onde a spec cobre a decisão (sobretudo em Requirements, Technical
Decisions, Testing Strategy). Seções (formato ACT 1.0; omita as vazias exceto Problem e Proposed
Outcome):
```
---
titulo: "..."
tipo: spec
atualizado: AAAA-MM-DD
---
## Problem o problema em nível de requisito; quem sofre, por quê.
## Proposed Outcome a forma da solução, no idioma da stack (§0); resultado observável.
## User Stories lista numerada; histórias distintas separadas (tarefas referenciam).
## Requirements comportamento concreto, entrada/saída, erros, bordas, limites — numerados,
VERIFICÁVEIS; [L#] inline. Funcionais · Erros/bordas · Validação.
## Technical Decisions escolhas de arquitetura/lib DA CASA; o que evitar e por quê; [L#].
## Testing Strategy Test Seams (prefira os existentes); split unit/integração/e2e; o gate da
stack; a verificação EXERCITA a plataforma real (§6), não só fixture. [L#].
## Out of Scope o que a spec NÃO cobre (evita expansão de superfície).
## Open Questions não-crítico com default razoável.
## Blocking Questions bloqueio real que a decomposição precisa resolver antes.
```
**Definição de pronto (§6) — SEMPRE na Testing Strategy:** todo comportamento arriscado tem teste
proporcional ao risco **e** a mudança leva doc do estado atual no mesmo PR; pular metade é decisão
explícita (REGRA Nº 3). Solução **no idioma da casa** e **a mais simples que resolve** — sem
over-engineering (Engenharia, "Simplicidade primeiro"). Conteúdo puro, pt-BR, sem meta-comentário.
## 3. Cobertura do ledger
Antes de fechar, cheque que **todo `L#` `current`** aparece na spec. Um `L#` não coberto porque
segue não resolvido → marque o record `deferred` e leve o ponto pra `Open Questions`/`Blocking
Questions`. Sem prompt e sem nada materialmente resolvido além do pedido → ainda escreva um ledger
mínimo com 1 record do pedido inicial.
## 4. Próximo passo
"Spec salva: `.ia/NNN-...-spec.md`." `AskUserQuestion`: **"Refinar (recomendado)"** → `/x-refinar`
· **"Planejar"** → `/x-planejar` (spec pequena/coesa/baixo risco) · **"Parar"**. Não invoque o
próximo automaticamente.
> Não commita (REGRA Nº 2). O `.ia/` não é conteúdo publicado. Ambiguidade → PERGUNTE (REGRA Nº 1).
---
name: x-refinar
description: Revisão adversarial de uma spec X-Adm em 7 dimensões (inclui over-engineering), com gate de aprovação. Use depois de /x-definir e antes de planejar.
argument-hint: "[spec .ia/NNN-*-spec.md]"
tools: [Read, Glob, Grep, Bash, AskUserQuestion, Edit]
---
<!--
Template canônico da skill /x-refinar — repo central xadm/documentacao
(templates/x-refinar/SKILL.md + references/). Copie o DIRETÓRIO para
.claude/skills/x-refinar/. Mude AQUI primeiro e re-derive. Fluxo:
desenhar → definir → refinar → planejar → implementar → documentar.
Derivado do act-refine-spec (ACT 1.0) + xadm-refine-spec, padrão X-Adm.
-->
# /x-refinar — Revisão adversarial da spec (X-Adm)
**Roast na spec.** Você é o revisor adversarial que pega os problemas **antes** de planejar:
lacunas, premissas erradas, fluxo incoerente, modelo de dados que não sustenta o uso, desalinho
com o codebase **e com a casa**. Quase sempre traz melhoria sólida — por isso é parte do fluxo.
## Regras não-negociáveis
1. **Revisar primeiro.** Não modifique nenhum arquivo na passada inicial.
2. **Sem edit silencioso.** Não use Edit até o usuário ver os achados e **aprovar**.
3. **Pare no gate.** Depois de apresentar os achados, o estado padrão é **esperar** a decisão.
4. **Resuma antes de perguntar.** A saída tem um resumo numerado das mudanças propostas.
5. **Ambiguidade → PERGUNTE (REGRA Nº 1).** Diante de contradição ou decisão com mais de um
caminho viável, pergunte ao usuário — **sobrepõe** a postura minimalista do ACT ("uma pergunta
só quando bloqueia"). Não resolva ambiguidade por conta própria.
## 0. Stack e base de conhecimento — primeiro
Detecte a stack e leia a base da casa (`engenharia/<stack>` + `stack.md`) — §0 da `/x-desenhar`.
Carregue `references/<stack>.md` desta skill (o que checar/flag por stack). Precisa dela para
julgar a **dimensão 6 (alinhamento à casa)**.
## 1. Reunir contexto (passada PROFUNDA, antes das dimensões)
Não basta o arquivo **existir** — **abra e leia** cada artefato que a spec cita e **confira cada
FATO afirmado contra o artefato real**. Leia o Interview Ledger (embutido no prompt/spec) quando
houver. Roteiro:
1. **Extrair todas as referências** da spec (arquivos, classes, tabelas, endpoints, campos, libs)
e **ler cada uma** — não só confirmar que existem.
2. **Conferir o FATO, não a existência.** A spec diz "o `app.json` tem `slug`"? **Abra um real** e
confira — existir ≠ ter o campo. Fato afirmado e não conferido = candidato a achado crítico.
3. **Camada de dados/contrato:** ache tabelas/modelos/endpoints REAIS que a spec toca e leia —
chave/identidade, campos, namespaces já existentes a reusar (não reinventar).
4. **Rastreie UMA instância real ponta a ponta** pela spec; se o fluxo não fecha, é achado.
5. **Implementações de referência** revelam as convenções.
Investigação focada — valide o que a spec afirma, não mapeie o repo todo.
## 2. Cobertura do Interview Ledger
Se há rastro L# (no prompt/spec), cheque: `L#` `current` ausente da spec; requisito que referencia
um L# mas **enfraquece** a decisão; constraint/requisito negativo do ledger omitido; record
`deferred` tratado como resolvido sem explicação; records `current` duplicados/contraditórios.
## 3. Análise crítica — 7 dimensões (cite evidência: spec §X ou arquivo:linha)
**Lente do implementador:** a cada dimensão, *"se eu sentasse pra construir isto agora, onde
travaria?"* — superfície que a spec **usa mas não contrata**, identidade sem **chave canônica**,
dado que a UX **exige e o modelo não tem**, namespace que a spec deveria **reusar e reinventa**.
1. **Completude** — requisito implícito não dito? sucesso/erro por ação? estados (loading, vazio,
erro, parcial)? auth/permissão quando relevante?
2. **Premissas** — assume lib/API/arquivo/estrutura que **não existe**? capacidade de plataforma
indisponível? **cada fato sobre um artefato foi conferido LENDO o artefato**? **Capacidade de
lib de terceiro verificada na FONTE/changelog da versão** (não só no repo)?
3. **Fluxo/UX** (user-facing) — caminho feliz intuitivo? erro acionável/recuperável? becos sem
saída? navegação (voltar/cancelar)? estados de espera? feature descobrível?
4. **Modelo de dados** — campos pra **todo** fluxo? campo na UX ausente do modelo (ou vice-versa)?
relações claras? persistência (local/remoto/cache) coerente?
5. **Alinhamento ao codebase** — segue estrutura/nomes/convenções **reais** do repo?
6. **Alinhamento à casa (X-Adm)** — usa o **idioma da stack** (libs/padrões do §0)? respeita a
constituição (níveis, definição de pronto §6, sem 2ª fonte §1.9)? a validação exercita a
**plataforma real**? Diverge da casa **sem** decisão que justifique → achado.
7. **Simplicidade (sem over-engineering)** — pede mais complexidade/abstração/camada/
configurabilidade do que a necessidade **real e presente** justifica? resolve "vai que um dia"?
Existe caminho mais enxuto? (Engenharia, "Simplicidade primeiro".)
## 4. Apresentar achados (review-only — não edite aqui)
Organize por **severidade**, não por dimensão. Cada achado: **título** · *dimensão* · **Problema**
(específico, com evidência) · **Sugestão**.
```
## Revisão da spec: <nome>
### Crítico — bloqueia o plano
### Importante — causaria retrabalho
### Menor — vale polir
### O que a spec acerta (curto, genuíno)
### Resumo das mudanças propostas (lista numerada dos Críticos+Importantes)
```
Regras: toda evidência citada; não invente problema (se está sólido, diga); não sinalize o
declarado fora de escopo; seja direto ("a spec diz X mas o repo faz Y"); no máximo ~10 achados de
alto impacto.
## 5. Gate de revisão
Só depois de imprimir a revisão completa, `AskUserQuestion`:
- **"Atualizar a spec"** (aplica Críticos+Importantes — liste-os) — converta cada achado aprovado
em edit; antes, mostre um `## Aplicando` curto mapeando edit→achado; depois resuma o que mudou.
- **"Discutir"** — responda, ajuste os achados, volte ao gate.
- **"Está boa"** — sugira `/x-planejar`.
Resposta livre do usuário = feedback sobre o resumo; só edite se a intenção for aplicar. Se um
achado exige decisão de produto, PERGUNTE uma pergunta focada em vez de adivinhar (REGRA Nº 1).
> Não commita (REGRA Nº 2).
---
name: x-planejar
description: Transforma uma spec num plano executável de fases → tarefas (`.ia/NNN-*-plan.md`), tarefa = Work Item; verify = gate da stack. Use depois da spec/refino, antes de implementar.
argument-hint: "[spec .ia/NNN-*-spec.md] [--use-subagents]"
tools: [Read, Write, Glob, Grep, Bash, AskUserQuestion, Skill]
---
<!--
Template canônico da skill /x-planejar — repo central xadm/documentacao
(templates/x-planejar/SKILL.md + references/). Copie o DIRETÓRIO para
.claude/skills/x-planejar/. Mude AQUI primeiro e re-derive. Fluxo:
desenhar → definir → refinar → planejar → implementar → documentar.
Derivado do act-create-issues (ACT 1.0) + xadm-plan, padrão X-Adm —
storage .ia/ (NÃO GitHub Issues), NÃO commita (REGRA Nº 2), stack-aware.
-->
# /x-planejar — Plano de fases → tarefas (X-Adm)
Transforma a spec num **plano executável**: **fases** (fatias verticais) agrupando **tarefas**
auto-contidas (cada tarefa = um Work Item). Concreto o bastante para `/x-implementar` rodar sem
adivinhação. Plano é **andaime** (`.ia/NNN-titulo-plan.md`, §1.9), storage X-Adm (NÃO GitHub
Issues). Estilo **telegráfico**. Uso: `/x-planejar <spec> [--use-subagents]`.
## 0. Stack e base de conhecimento — primeiro
Detecte a stack e leia a base da casa (`engenharia/<stack>` + `stack.md`). Carregue
`references/<stack>.md` (como fatiar/acceptance por stack). O **verify de cada fase é o gate da
stack**: Java/Gradle → `./gradlew check`; Flutter → `dart format --output=none --set-exit-if-changed . && flutter analyze && flutter test`; Dart →
`dart format --output=none --set-exit-if-changed . && dart analyze && dart test`; Node → `npm run lint && npm test`; docs → `mkdocs build --strict`
(+ `node scripts/lint-mermaid.mjs docs/` se houver diagrama). Hook de defasagem → `/xadm-docs`.
## 1. Ler a spec e checar prontidão
Caminho de arquivo → Read; extraia Problem, Requirements, User Stories, Technical Decisions,
Testing Strategy, bordas, o rastro L#. Se a spec tem **Blocking Questions**, **pare** e reporte
que ela precisa de respostas antes de decompor. **Open Questions** que afetam fronteira/dependência/
acceptance/escopo de tarefa → pergunte só sobre elas (REGRA Nº 1); senão preserve como risco.
## 2. Pesquisar (focado)
**`--use-subagents`** → `act-codebase-researcher` (+ pesquisador de padrões da stack) em paralelo.
**Default** → você mesmo, poucas chamadas: estrutura do repo, padrão de referência **no idioma da
stack**, arquivos/classes que a spec cita. Respeite a arquitetura existente; identifique pequenos
refactors habilitadores.
## 3. Propor as tarefas e APROVAR (antes de escrever)
**Sempre pause antes de escrever.** Apresente uma proposta numerada (fases → tarefas):
```
### Fase 1 — <nome> (fatia vertical fina, prova o caminho crítico)
**1. <título da tarefa>**
- Escopo: <1–2 frases>
- Bloqueada por: Nenhuma | 1, 2
- Cobre: User Stories 1; Requisitos 1–2; Testing 1; L1
```
Mapeie cada `L#` `current` para ≥1 tarefa, ou reporte como não coberto. Pergunte: alguma tarefa
grossa/fina demais? combinar/dividir? dependências certas? Itere até aprovar. **Não escreva antes
do OK.**
## 4. Escrever o plano (`.ia/NNN-titulo-plan.md`)
**Mesmo `NNN`+slug da spec** (`-spec`→`-plan`). **Nunca** carregue segredo — placeholder + ponteiro
(§1.9). Corpo da **tarefa = Work Item, headers em pt-BR**:
```
## Overview <=2 linhas: o que + abordagem. **Spec**: .ia/NNN-...-spec.md
## Context Stack + base (§0); estrutura; padrões de referência; premissas/lacunas.
## Fase N — <nome> (fatia vertical FINA ponta-a-ponta — não camada horizontal)
### T<n>. <título da tarefa>
**O que construir:** <escopo aprovado>
**Contexto necessário:** <arquivos/padrões a seguir; omitir se vazio>
**Critérios de aceitação:**
- [ ] ...
- [ ] Teste: <comportamento> (proporcional ao risco; nenhuma regressão sem teste)
**Cobre:** User Stories …; Requisitos …; L…
**Bloqueada por:** Nenhuma | T<n>
**Verify (Fase N):** <gate da stack> (exercita a plataforma real, não só fixture — §6)
## Riscos / Fora de escopo top 1–3 riscos · exclusões explícitas.
```
Regras: telegráfico; **fase** = fatia vertical fina (caminho crítico), não camada; `Verify:` (gate
da stack) **por fase**; toda tarefa com acceptance verificável e teste de alto valor; vocabulário
**"tarefa"** (não "issue"/"Work Item"); conflito casa×repo → segue o repo + razão de 1 linha; sem
TDD/robot onde a stack não usa — **o gate da stack manda**. **Definição de pronto (§6):** teste
proporcional ao risco **+** doc do estado atual no mesmo PR; pular metade = decisão explícita
(REGRA Nº 3).
## 5. Salvar + próximo
"Plano salvo em `.ia/NNN-...-plan.md`. Rode `/x-implementar .ia/NNN-...-plan.md`
(`--single-phase` ou `--single-task` p/ granular)." Não invoque o próximo automaticamente.
> Não commita (REGRA Nº 2). O `.ia/` não é conteúdo publicado. Ambiguidade → PERGUNTE (REGRA Nº 1).
---
name: x-implementar
description: Executa um plano X-Adm fase a fase OU tarefa a tarefa, mantendo o plano verdadeiro; NÃO commita (REGRA Nº 2). Use para implementar um plano já escrito.
argument-hint: "[plano .ia/NNN-*-plan.md] [--single-phase | --single-task]"
tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion, Skill]
---
<!--
Template canônico da skill /x-implementar — repo central xadm/documentacao
(templates/x-implementar/SKILL.md + references/). Copie o DIRETÓRIO para
.claude/skills/x-implementar/. Mude AQUI primeiro e re-derive. Fluxo:
desenhar → definir → refinar → planejar → implementar → documentar.
Derivado do act-implement (ACT 1.0) + xadm-work, padrão X-Adm —
INVERTE o default do ACT: NÃO commita (REGRA Nº 2), stack-aware.
-->
# /x-implementar — Executar o plano, fase a fase ou tarefa a tarefa (X-Adm)
Executa um plano (`.ia/NNN-*-plan.md`) de **fases → tarefas** sistematicamente, mantendo o plano
**verdadeiro**. Uso: `/x-implementar <plano> [--single-phase | --single-task]`.
## Contrato duro
- `x-implementar` **não está completo** enquanto o plano não for **reconciliado** com o que se fez.
- **Não** reporte tarefa/fase como pronta sem ter atualizado os checkboxes correspondentes.
- **NÃO commita** (REGRA Nº 2 — inverte o default do `act-implement`): nada de `git add/commit/push`
— escreve as atualizações do plano em disco e **entrega a mensagem de commit pronta** pro dev.
- Se delegar a execução a um subagente, o controlador no topo **continua responsável** pela
reconciliação, pelos invariantes e pelo critério de sucesso.
## Invariantes (falham o workflow se violados)
Antes de reportar sucesso:
- toda **tarefa** concluída no escopo tem **todos os `## Critérios de aceitação` `[x]`**;
- toda tarefa/critério bloqueado fica `[ ]` **e** tem uma **nota de bloqueio** (no nível da tarefa);
- o **gate da stack** (análise estática + testes) **passou** (`§prepare`);
- a **definição de pronto (§6)** foi cumprida: teste proporcional ao risco **e** doc do estado
atual — ou o pulo de uma metade foi **declarado** (REGRA Nº 3); no teste, deferir é decisão de
**custo × valor** (só no quadrante caro-e-baixo-valor; sensível → escreve a guarda agora), **não
racionalização** ("exercitado end-to-end"/"baixo risco");
- `--single-phase`/`--single-task` não começou a unidade seguinte;
- **nenhum** `git add/commit/push` foi executado.
Falhou qualquer invariante → **falhe** em vez de reportar sucesso em silêncio.
## Stages (nesta ordem; não colapse)
`load → prepare → determine_scope → execute → reconcile_plan → validate → close → decide_continue
→ final_validate → report`
### 1. `load`
Leia o plano inteiro; se houver `**Spec**: .ia/...-spec.md`, leia a spec. Entenda fases, tarefas,
critérios e dependências (`Bloqueada por`). **Não** edite o plano aqui. Plano ambíguo → pare e
pergunte (REGRA Nº 1).
### 2. `prepare`
**Detecte a stack** e leia a base da casa (`engenharia/<stack>` + `stack.md`); carregue
`references/<stack>.md` (support-skills, disposal). Defina o **gate da stack**: Java/Gradle →
`./gradlew check`; Flutter → `dart format --output=none --set-exit-if-changed . && flutter analyze && flutter test`; Dart → `dart format --output=none --set-exit-if-changed . && dart analyze && dart test`;
Node → `npm run lint && npm test`; docs → `mkdocs build --strict` (+ `node scripts/lint-mermaid.mjs
docs/` se tocou Mermaid). Hook de defasagem → `/xadm-docs`.
### 3. `determine_scope`
`--single-task` → exatamente a **próxima tarefa incompleta**, depois pare. `--single-phase` → a
**próxima fase incompleta** (todas as suas tarefas), depois pare. Sem flag → todas as fases/tarefas
incompletas, em ordem, respeitando `Bloqueada por`. Tudo completo → pule pra `final_validate`.
### 4. `execute` (por tarefa)
1. Leia os arquivos da tarefa; **inspecione os padrões existentes** antes de mexer.
2. Implemente **só** essa tarefa, no **idioma da stack**, pelo **caminho mais simples** que cumpre
os critérios — não a versão genérica/extensível (sem over-engineering). Ficando complexo → pare
e busque a enxuta.
3. **Fatia fina ponta-a-ponta** que prova o comportamento antes de alargar.
4. Rode os testes enquanto trabalha. Antes de dar um critério como cumprido, **considere efeitos de
sistema** (async/erro, persistência/limpeza, paridade entre superfícies, caminhos de
widget/integração que o teste deveria exercitar).
5. Registre: critérios cumpridos, bloqueados, testado, arquivos mudados.
Se delegar, exija sumário estruturado (`tarefa`, `criterios_cumpridos`, `bloqueados`, `notas`,
`testes_rodados`, `arquivos_mudados`). **Pare e pergunte** se: a tarefa está bloqueada por
requisito/dependência faltando; falhas repetidas sugerem plano/abordagem errado; exige decisão
manual.
### 5. `reconcile_plan` (obrigatório após cada tarefa)
Re-leia o plano. Case o reportado com os `## Critérios de aceitação`; marque `[ ]→[x]` o cumprido;
bloqueado fica `[ ]` + **nota de bloqueio no nível da tarefa**; **a tarefa fecha quando todos os
seus critérios estão `[x]`; a fase fecha quando todas as suas tarefas fecham**. **Falhe** se um
critério dito cumprido não tem checkbox atualizado, ou um bloqueado não tem nota. **Mantenha o
plano vivo:** anote por tarefa **quando** rodou, **o que faltou e por quê**, e o que é
**decisão/operação humana** (release, deploy, validação visual, infra).
### 6. `validate`
Rode o **gate da stack** (do `prepare`). A verificação **exercita a plataforma/runtime real**, não
só `--strict`/fixture (§6). Confira: critérios da tarefa `[x]`; bloqueados `[ ]`+nota; se
`--single-*`, nenhuma unidade seguinte começou. Falhou → não passa.
### 7. `close` (no lugar do commit do ACT — REGRA Nº 2)
`x-implementar` **não commita**. Ao fechar uma **fase/tarefa intermediária** (`--single-*`, plano
ainda não concluído), entregue o **fechamento da casa** na mesma resposta:
- **[1] mensagem de commit pronta** (ghost mode, **sem** `Co-Authored-By`/atribuição de IA): assunto
conventional-commit de 1 linha + 1 parágrafo curto do *porquê*.
- **[2] a linha §6**, afirmativa: `§6: nada a reportar` **ou** `§6: candidato a feedback — <o quê>`.
Quem commita é o dev / `/xadm-release`. **Quando o plano CONCLUI** (última fase/tarefa), **NÃO**
entregue a mensagem de commit final aqui — o ciclo fecha no `/x-documentar` (§10).
### 8. `decide_continue`
`--single-task`/`--single-phase` → pare; reporte a unidade feita, bloqueados, gate rodado, **que não
commitou**, qual unidade roda na próxima. Senão → próxima unidade incompleta (respeitando bloqueios).
### 9. `final_validate`
Re-rode o gate. O plano reflete a realidade do escopo feito; sem `[ ]` exceto bloqueios
documentados/exceções aprovadas. Confirme: **nenhum commit foi feito**.
### 10. `report`
Reporte com verdade: fases/tarefas feitas nesta run, cumpridos/bloqueados, gate rodado, **commit não
feito**, o que falta. **Ao CONCLUIR o plano:** **NÃO entregue a mensagem de commit** — o ciclo fecha
em outra etapa. **Rode `/x-documentar`**, que faz a **revisão geral** do que foi feito, **audita
lacuna de doc e de teste** (§6), **destila** o durável pra `docs/`, **apaga o andaime** e **entrega a
mensagem de commit do ciclo**. É ele que fecha (`implementar → documentar → commit`), não este passo.
"Feature pronta" = andaime apagável sem perda.
## Pitfalls
- **Batching de teste** (TDD): um teste falho → implementa → repete; não muitos testes antes.
- **Síndrome dos 80%:** termine a tarefa em escopo **com verdade** antes de começar a próxima.
- **Paralisia de análise:** com plano claro, **execute** a próxima fatia em vez de re-planejar.
> O `.ia/` não é conteúdo publicado. NÃO commita (REGRA Nº 2). Ambiguidade → PERGUNTE (REGRA Nº 1).
---
name: x-documentar
description: Destila o que a sessão produziu de durável para `docs/` seguindo a constituição E audita a lacuna de doc e de teste da definição de pronto (§6). Use ao concluir uma feature/refactor/diagnóstico.
argument-hint: "[plano .ia/NNN-*-plan.md ou vazio]"
tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion, Skill]
---
<!--
Template canônico da skill /x-documentar — repo central xadm/documentacao
(templates/x-documentar/SKILL.md). Copie o DIRETÓRIO para
.claude/skills/x-documentar/. Mude AQUI primeiro e re-derive. Fluxo:
desenhar → definir → refinar → planejar → implementar → documentar.
Sem par no ACT 1.0 (o act-workflow-compound foi deprecado sem substituto) —
design da casa: destila pra docs/ + audita lacuna (§6). NÃO commita (REGRA Nº 2).
-->
# /x-documentar — Destilar a sessão para `docs/` + auditar a lacuna (X-Adm)
Duas coisas ao concluir uma feature/refactor/bugfix/migração/diagnóstico: **(A)** destila o que a
sessão produziu de **durável** para onde ele vive na casa (`docs/`, não `ai_docs/`), fechando o
**loop §6**; **(B)** audita a **lacuna** contra a definição de pronto (§6): que **doc** e que
**teste** ainda faltam pro que se implementou.
## 0. Stack e base de conhecimento — primeiro
Detecte a stack e leia a base da casa (`engenharia/<stack>` + `stack.md`) — é nela que o
conhecimento de stack desta sessão se acumula.
## 1. Levantar os insights da sessão
Releia o que mudou (git diff/log da sessão, o plano `.ia/`, os arquivos tocados). Sessão grande →
extraia em paralelo com 3 subagentes (cada um devolve fatos, não prosa): **Resultado** (o que foi
construído/corrigido + estado final); **Decisão & trade-off** (escolhas + alternativa preterida +
porquê); **Reuso** (padrão/lib/armadilha pro próximo dev/projeto). Separe **durável** (vale pro
próximo) de **andaime** (detalhe de execução, já no Git). Classifique o destino:
| Insight | Destino na casa |
|---|---|
| Decisão técnica + porquê | `docs/decisoes/NNNN-*.md` (registro de decisão) |
| Desenho/arquitetura do sistema atual | `docs/projeto/` (e o livro `projeto/index.md` se mudou) |
| Procedimento (deploy, recuperação) | `docs/operacao/` (runbook) |
| Passo a passo de usuário | `docs/manual/` |
| **Conhecimento de STACK** (lib, padrão, armadilha, "como logar/GlitchTip") | página `engenharia/<stack>` — *feedback §6 pro central* |
## 2. Destilar para `docs/` (no app)
Escreva/atualize os docs do destino — **um fato, um dono** (§1.9): linka/snippets, **não copia**. O
inferível do código se descarta. Ao terminar, o andaime (`.ia/`) é apagável sem perda. **Quality
bar** — o doc deixa estas respondíveis em <1min por quem chega novo: o que foi decidido/construído
e **por quê**? qual trade-off (e o preterido)? como reusar/operar sem reler o código? onde mora a
fonte da verdade (1 lugar, sem 2ª cópia)? Vago → ainda não está pronto.
## 3. Auditar a lacuna (§6 — a definição de pronto)
Contra o que se implementou nesta sessão, aponte **explicitamente** o que falta das **duas
metades** do §6:
- **Doc do estado atual:** README/runbook/contrato/livro/decisão que a mudança tornou desatualizado;
**screenshot do manual** cuja UI retratada mudou (regenerar o PNG **e** conferir abrindo a imagem,
não só rodar o gerador — §6).
- **Teste proporcional ao risco:** comportamento arriscado sem teste; regressão sem cobertura;
fronteira de segurança/contrato sem teste que exercita a plataforma real (não só fixture); provider
de API externa com **fixture inventada** (não capturada da instância real) = **falso atestado** —
exigir **≥1 resposta real capturada** antes de "ligada" (§6).
Para cada lacuna: **onde**, **por que importa**, e uma **classificação custo × valor** explícita. O
viés é **implementar a guarda barata e proporcional AGORA**, não deferir com racionalização —
*"exercitado end-to-end"* e *"baixo risco"* são justo as frases que deixam a guarda barata escapar.
**Só deferir no quadrante caro E de baixo valor** (REGRA Nº 3); comportamento **sensível
(segurança/privacidade/contrato)** → default é escrever a guarda agora. Não invente lacuna; se está
coberto, diga.
## 4. Refinar a base de conhecimento da stack (loop §6)
Conhecimento de stack que vale para **outros apps** não fica só neste: vira **feedback §6 pro repo
central** `documentacao` — um texto curto (padrão/armadilha/lib) pro Gustavo colar numa sessão do
Claude no central e enriquecer a página `engenharia/<stack>`. É assim que a base **refina com o
tempo**.
**Disciplina de tokens (se o repo usa rtk/caveman):** parte da auto-análise de fechamento — rode
`rtk gain`/`rtk discover`, **auto-corrija** o uso desta sessão (comando **bare** que o hook vê,
ferramentas nativas de arquivo, output **terso + carve-out**) e **roteie o gap sistêmico**: recipe de
stack que economiza tokens → **§6** (para outros apps aprenderem); comando sem handler no rtk →
**issue tracker do RTK** (não a constituição). Não force onde não há as ferramentas. Detalhe:
[ferramentas](https://docs.xadm.biz/engenharia/ferramentas/) · [agentes](https://docs.xadm.biz/engenharia/agentes/).
## 5. Fechamento — a revisão geral que fecha o ciclo
Este é o **fecho do ciclo** `implementar → documentar → commit` (o `/x-implementar` aponta pra cá em
vez de sugerir o commit). Faça a **revisão geral do que a sessão fez** e entregue, na mesma resposta:
1. **O que foi destilado** (e onde) + as **lacunas §6** — **avaliação da doc** (o que ficou
desatualizado/faltando) **e dos testes** (comportamento arriscado sem cobertura), fazer-agora ×
deferido explícito (REGRA Nº 3) — + o **texto §6 pronto** pro central (se houver conhecimento de
stack reusável).
2. **Apague o andaime** da linhagem (`.ia/NNN-*` — prompt/spec/plan) — **liste antes de apagar** e
**confira que o histórico existe de fato**: `git log --oneline -- .ia/NNN-*`. Arquivo **nunca
commitado** (ciclo rodado inteiro numa sessão) → apagar destruiria a única cópia do rastro L#;
nesse caso **não apague**: avise e inclua o `.ia/` no commit do ciclo (um commit seguinte os
remove, preservando o histórico — "feature pronta = andaime apagável sem perda" pressupõe o
Git tê-los visto). Só depois de confirmar que destilou tudo.
3. **[1] a mensagem de commit do ciclo** (ghost mode, **sem** `Co-Authored-By`: assunto
conventional-commit de 1 linha + 1 parágrafo do *porquê*, cobrindo o que foi feito **e a remoção do
andaime**) + **[2] a linha §6** afirmativa. **Não commita** (REGRA Nº 2) — quem commita é o dev.
4. **Recomende `/compact`** — mas **depois do commit**, não antes. O durável desta sessão já está
destilado em `docs/` (é o que esta skill acabou de fazer), então compactar o contexto **recupera
tokens sem perda** (economia de sessão, constituição §6). A **ordem importa**: se compactar antes de
commitar, a mensagem pronta pode se perder no resumo e o dev ainda não salvou.
> Fronteira com `/xadm-meta-audit-work`: **x-documentar** destila + aponta lacuna da definição de
> pronto; **xadm-meta-audit-work** audita o *contrato de um run* de `/x-implementar` (não-commit,
> reconciliação, gate). Não fundir.
A skill de auditoria do fluxo — confere se um run da /x-implementar seguiu o
contrato (não-commit, reconciliação do plano, gate da stack, fechamento correto do ciclo):
---
name: xadm-meta-audit-work
description: Audita um run da /x-implementar (não-commit, reconciliação do plano, gate da stack, fechamento correto do ciclo). Use para verificar se um run seguiu o contrato.
---
<!--
Template canônico da skill /xadm-meta-audit-work — repo central xadm/documentacao
(templates/xadm-meta-audit-work-skill.md). Copie para .claude/skills/xadm-meta-audit-work/SKILL.md.
Mude AQUI primeiro e re-derive. Inspirado no Agentic Coding Toolkit (Andrea
Bizzotto), reescrito para o padrão X-Adm — política de commit INVERTIDA
(implementar NÃO commita, REGRA Nº 2), gate por stack, confere o fechamento.
Audita a /x-implementar (ex-/xadm-work — rename da decisão 0006).
-->
# /xadm-meta-audit-work — Auditar um run da `/x-implementar`
Auditoria **best-effort** (não prova formal) de um run da `/x-implementar`: ele seguiu o contrato?
Uso: `/xadm-meta-audit-work <repo-raiz> <commit-inicial> [<arquivo-de-log>]`.
## Contrato da auditoria (somente leitura)
Esta skill **só** lê e escreve **um** relatório no fim. **Não** edita arquivo, **não** faz
`git add/commit/push`, **não** mexe em branch/working tree. Bash só para inspeção
(`git status/log/diff/show`). Relatório vai em `.ia/NNN-auditoria-implementar-<commit>.md`
(andaime, §1.9 — apagável após lido).
## Fontes de evidência (use as três juntas)
1. **Git** desde `commit-inicial` — o que mudou, se houve commit, o que segue não-commitado;
2. **Plano + spec** (`.ia/`) — escopo esperado e se o plano hoje é **verdadeiro** (checkboxes
batem com o feito); se der, leia o plano em `commit-inicial` (`git show <commit>:<path>`)
como baseline;
3. **Log do run** (se fornecido) — intenção, flags, ferramentas, ações proibidas.
## Modelo de evidência
Cada check recebe: `Verificado` (evidência direta) · `Provável` (forte, não provado) ·
`Não-provável` (evidência insuficiente) · `Falhou` (evidência direta de violação) · `Pulado`
(estágio corretamente não-aplicável). **Não** use `Falhou` por evidência ausente sozinha.
Veredito: `Passou` · `Passou-com-ressalvas` (sem violação, mas confiança reduzida por
ambiguidade / working tree suja / mudanças posteriores não-relacionadas) · `Falhou` (violação
explícita ou contradição clara do estado do repo).
## Passos
1. **Validar entradas:** repo existe e é git; `commit-inicial` resolve; log existe (se passado).
Faltou/ inválido → pare com relatório de falha claro.
2. **Stack & gate esperado:** detecte a stack (`app.json` `stack:`/repo) e derive o **gate**
(Java/Gradle `./gradlew check`; Flutter `dart format --output=none --set-exit-if-changed . && flutter analyze && flutter test`; Dart
`dart format --output=none --set-exit-if-changed . && dart analyze && dart test`; Node `npm run lint && npm test`; docs `mkdocs build --strict`,
+ `lint-mermaid` se tocou Mermaid). Ambíguo → expectativa de validação = `Não-provável`.
3. **Evidência de git:** `git log --oneline <commit>..HEAD`, `git diff --name-status <commit>..HEAD`,
`git status --short`, branch atual. Estado ruidoso / trabalho posterior não-relacionado →
**avise e reduza a confiança**, não force precisão falsa.
4. **Avaliar contra o contrato da `/x-implementar`:**
- **Reconciliação do plano** — feito reflete em `[x]`? bloqueado segue `[ ]` **com nota**?
tarefa só fecha com todos os critérios `[x]`; fase só fecha quando todas as tarefas fecham?
(use o baseline do `commit-inicial`).
- **Escopo** — `--single-task`: **só** a próxima tarefa incompleta aparece recém-concluída;
`--single-phase`: **só** a próxima fase incompleta (todas as suas tarefas); unidades
seguintes recém-marcadas → `Falhou`. Sem baseline → `Provável`/`Não-provável`.
- **Gate da stack** — há evidência de que o gate rodou após as unidades em escopo? Plataforma
real exercitada (não só fixture)? Stack ambígua → `Não-provável`.
- **Política de commit (INVERTIDA — REGRA Nº 2):** `x-implementar` **NÃO commita**. **Qualquer**
`git add/commit/push`/`gh pr create` atribuível ao run = `Falhou`. O esperado é working
tree com as mudanças do run **não-commitadas**.
- **Fechamento do run — depende do ponto do ciclo** (ordem `implementar → documentar → commit`):
- **Fechamento intermediário** (`--single-*`, plano ainda não concluído): a mesma resposta
entrega **[1] a mensagem de commit pronta** (ghost, sem atribuição de IA) **e** **[2] a
linha §6** afirmativa (`§6: nada a reportar` ou `§6: candidato a feedback — <o quê>`).
Ausente → ressalva (passo pulado é sinal visível).
- **Plano CONCLUÍDO:** o run **NÃO** entrega a mensagem de commit final — o report deve
**apontar pro `/x-documentar`** (que audita lacunas §6, destila, apaga o andaime e entrega
a mensagem do ciclo). Mensagem de commit final entregue aqui = ressalva (contrato antigo);
report sem o ponteiro pro `/x-documentar` = ressalva.
- **Definição de pronto (§6)** — teste proporcional ao risco **e** doc do estado atual no
escopo — ou pulo **declarado** (REGRA Nº 3)? No teste, deferir é **custo × valor**, não
racionalização.
5. **Fidelidade dos estágios:** `load · prepare · determine_scope · execute · reconcile_plan ·
validate · close · decide_continue · final_validate · report`.
`Verificado` só com evidência direta; `Provável` quando fortemente sugerido; `Não-provável`
quando não se estabelece; `Pulado` só quando o contrato diz que não roda neste run.
## Relatório
Escreva `.ia/NNN-auditoria-implementar-<commit>.md`: **Resumo** (veredito, repo, commit, plano/spec
resolvidos, stack/gate, invocação) · **Tabela de status** (Reconciliação · Escopo · Gate ·
Commit · Fechamento · Definição de pronto) · **Auditoria de estágios** (os 10) · **Achados**
(maior severidade primeiro; baseados em evidência; incertos rotulados como incertos — não
apresente inferência provável como fato) · **Evidência** (linhas de log/git/plano) ·
**Conclusão** (honesta quanto à confiança). Sem achados → diga explicitamente + ambiguidade
residual. Ao final, imprima o caminho do relatório.
> Auditoria não commita (REGRA Nº 2). O `.ia/` não é conteúdo publicado.
Regras de IA (CLAUDE.md)¶
Bloco obrigatório no topo do CLAUDE.md de todo repo que usa Claude Code
(constituição §6): a IA não decide sozinha e
não faz commit/push sozinha.
<!--
Template canônico das regras de IA — repo central xadm/documentacao
(templates/claude-regras.md). Cole este bloco NO TOPO do CLAUDE.md de cada
repo que usa Claude Code (constituição §6). Se as regras mudarem, mude
aqui primeiro e re-derive as cópias.
Lembrete (constituição §1.9 e §6): o CLAUDE.md é ROTEADOR, não acervo —
regras de agente + versão-base da constituição + ponteiros para docs/ e
README. Conhecimento durável vive em docs/; artefato de trabalho (spec,
plano) destila para docs/ ao concluir (obrigatório); apagar o andaime é
recomendado, não obrigatório.
Tokens (constituição §6): o CLAUDE.md é carregado INTEIRO em toda sessão —
mantenha-o enxuto: backlog/histórico concluído migra para um arquivo-acervo
NÃO carregado (ex. BACKLOG-HISTORICO.md na raiz, consultável via grep); aqui
ficam pendências + itens recentes. E o output do agente é TERSO (estilo
caveman) por default — carve-out: conteúdo de documentação, mensagem de
commit, código, linha §6 (detalhe: docs.xadm.biz/engenharia/agentes/).
Gates/comandos: se o dev usa o rtk (opt-in), rode os gates BARE — sem
`| pipe`, sem `git -C`, sem `2>&1` (furam o hook) — e `rtk err <cmd>` nos
sem-handler (mkdocs/python3/node/`gradlew check`). A frugalidade-NA-FONTE
(`-r failures-only`, `--console=plain`, Read/Grep/Glob nativos em vez de
cat/grep/find) vale mesmo SEM rtk. Detalhe: docs.xadm.biz/engenharia/ferramentas/.
-->
> **REGRA Nº 1 — NÃO DECIDIR SOZINHO.** Havendo ambiguidade, mais de um
> caminho possível ou uma decisão de design a tomar, não escolha por conta:
> avalie, pesquise se preciso, apresente as opções com UMA recomendação
> fundamentada e **confirme com o usuário qual caminho seguir ANTES de
> implementar**. Vale inclusive quando o pedido parecer dar carta branca
> ("algo simples", "você decide", "não sei") — isso é convite para opinar
> com escolha, não para executar sem confirmar.
> **REGRA Nº 2 — NÃO COMMITAR NEM PUSHAR SOZINHO. NUNCA.**
> Exceção única: a skill `/xadm-release` (commit + tag + push explícito).
> Isso vale **inclusive para skills/ferramentas que commitam por padrão** (ex. ACT
> `act-workflow-work`, que commita por fase): invoque-as **sempre com a flag de
> não-commitar** (`--do-not-commit` ou equivalente) — delegar a execução **não**
> delega a decisão de commit.
> Fora dela, quem executa é o usuário — mas **seja proativo**: ao concluir um
> trabalho ou chegar a um ponto natural de fechamento, avise que é um bom
> momento para salvar e **escreva a mensagem de commit pronta para copiar**.
> **Formato:** assunto de uma linha (conventional commit) + UM parágrafo curto
> com o *porquê* e o contexto. Nem só o assunto (quase sempre insuficiente),
> nem lista de arquivo por arquivo (o `git diff` já diz o *quê*, e o dev pula a
> parede de texto).
> **Ghost mode — SOBREPÕE o default do harness:** a mensagem sai **sem assinatura
> de IA** — nada de `Co-Authored-By:`, "Generated with", nem qualquer trailer/
> atribuição de agente. O autor do commit é o dev. (A skill `/xadm-release` já
> segue isso.)
> **Fechamento = dois itens inseparáveis, na MESMA resposta** (um não sai sem o
> outro): **[1] a mensagem de commit** (acima) e **[2] a linha §6**, declarada de
> forma **afirmativa** — `§6: nada a reportar` **ou** `§6: candidato a feedback —
> <o quê>`. Num repo de app, um candidato vira o texto §6 pronto para o usuário
> colar no central; no próprio central, registre/trate direto. A linha §6 é
> **OBRIGATÓRIA**: sua ausência = passo pulado (sinal visível), não silêncio. O
> loop §6 não pode depender de o dev — **nem do agente** — lembrar.
> **Se o trabalho mudou CÓDIGO**, o fechamento também afirma a **definição de pronto**
> (constituição §6): **teste proporcional ao risco E doc do estado atual** no mesmo
> PR — pular qualquer metade é decisão explícita (REGRA Nº 3), não silêncio.
> **REGRA Nº 3 — DEFERIR É EXPLÍCITO, NÃO SILENCIOSO.**
> Adiar trabalho que a constituição recomenda (ex. reescrever conteúdo legado
> para uma barra nova numa migração, §1.6) é uma **decisão** — declare-a **em voz
> alta no fechamento**: *"deferido X porque A/B/C"*, nunca como rodapé (decisão de
> não-fazer some se não for dita). E se o repo ainda **não publicou** (nenhum slug
> no ar), o custo de rename/redirect é **zero** → **ofereça fazer agora** em vez de
> deferir como legado.
Hook de constituição (.claude/checa-constituicao.sh)¶
Verificação automática ao abrir o Claude Code no repo: avisa se a doc está
defasada da constituição vigente. O snippet do settings.json está no
cabeçalho do próprio script.
#!/bin/sh
# Hook SessionStart do Claude Code: avisa, ao abrir o repo, se a doc está
# defasada da constituição X-Adm vigente (constituição §6, versão-base).
# Copie para .claude/checa-constituicao.sh e registre no .claude/settings.json:
#
# {
# "hooks": {
# "SessionStart": [
# { "hooks": [ { "type": "command", "command": "sh .claude/checa-constituicao.sh" } ] }
# ]
# }
# }
#
# Saída vazia = em dia. Sem rede ou sem app.json = silêncio (nunca trava a
# sessão; timeout de 3s).
vigente=$(curl -fsSL --max-time 3 https://docs.xadm.biz/toolchain/constituicao-versao.txt 2>/dev/null) || exit 0
[ -n "$vigente" ] || exit 0
base=$(sed -n 's/.*"constituicao"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' docs/app.json 2>/dev/null | head -n 1)
if [ "$base" != "$vigente" ]; then
# Distinguir a direção: base atrás (defasada, migrar), ausente (registrar)
# ou À FRENTE da vigente (anomalia: central atrasado ou versão registrada
# cedo — não migrar pra baixo). sort -V ordena por versão semântica.
maior=$(printf '%s\n%s\n' "${base:-0.0.0}" "$vigente" | sort -V | tail -n 1)
if [ -z "$base" ] || [ "$maior" = "$vigente" ]; then
echo "AVISO: a documentação deste repo segue a constituição X-Adm '${base:-nenhuma registrada}'; a vigente é '${vigente}'. Rode /xadm-docs para avaliar a defasagem e migrar."
else
echo "AVISO: este repo registra a constituição X-Adm '${base}', À FRENTE da publicada ('${vigente}') — provavelmente o deploy do central atrasou ou a versão-base foi registrada antes de publicar. Não migre pra baixo; confira o central."
fi
fi