Imported from caarlosandree/footfirma (
AGENTS.md). Install upstream withnpx skills add caarlosandree/footfirma. Copyright stays with the author.
FootFirma — Índice do sistema
Este diretório é a raiz de um monorepo git único que abriga os dois lados do sistema, cada um com seu próprio conjunto de regras. Um commit pode tocar os dois, e o histórico é compartilhado — mas as regras não: cada lado tem as suas.
| Lado | Caminho | Stack | Regras |
|---|---|---|---|
| Frontend | frontend/ |
Turborepo + pnpm · Next.js 16 (App Router) · React 19 · Tailwind v4 · shadcn/ui sobre Base UI · Zod 4 | frontend/AGENTS.md → frontend/.rules/ |
| Backend | backend/footfirma/ |
Spring Boot 4 · Java 25 · Spring Modulith · PostgreSQL · Redis · Flyway · Gradle | backend/footfirma/AGENTS.md → backend/footfirma/.rules/ |
As regras citam apenas a major de cada dependência. As versões exatas vivem nos
manifestos (package.json / pnpm-lock.yaml no frontend, build.gradle no backend)
— consulte-os quando a versão precisa importar, em vez de confiar no que está escrito
nas regras.
Antes de qualquer coisa
Abra o AGENTS.md do lado em que você vai trabalhar e siga o arquivo de .rules/
correspondente à tarefa. Este índice existe só para orientar; ele não substitui as
regras específicas.
Se a tarefa atravessa os dois lados (mudança de contrato de API, por exemplo), leia
os dois AGENTS.md antes de começar.
Contrato entre os dois
- O backend expõe a API em
/api/v1/. Swagger emhttp://localhost:8080/swagger-ui.html, OpenAPI em/v3/api-docs. - O frontend consome essa API e roda em
http://localhost:3000. - Mudança incompatível de contrato vira
/api/v2— não se altera a v1 em uso. - Alterou o contrato no backend? Diga explicitamente o que o frontend precisa ajustar. Estando os dois no mesmo repositório, nada obriga o ajuste a vir no mesmo commit — e um contrato alterado de um lado só quebra silenciosamente o outro.
Comandos por lado
# frontend/
pnpm lint && pnpm typecheck && pnpm build
# backend/footfirma/
./gradlew compileJava # rápido
./gradlew build # compila + testa (exige Docker)
Documentação vs. regras
Cada repositório separa as duas coisas, e a distinção importa:
| Pasta | Responde |
|---|---|
.rules/ |
como trabalhar aqui — arquitetura, padrões, checklists |
docs/ |
o que o sistema é — runbooks e notas de domínio |
Toda nota em docs/ segue o padrão obrigatório do docs/README.md do repositório:
Status: verificado em AAAA-MM-DD, ## Fontes com caminhos reais e um comando de
validação. Documentação sem lastro no código não entra — o código sempre vence.
Guardrails automáticos
Os três diretórios (raiz, frontend/, backend/footfirma/) têm
.claude/settings.json com um hook PreToolUse apontando para
.claude/hooks/guard.mjs. O guard bloqueia, não avisa:
- subir serviços (
pnpm dev,./gradlew bootRun,docker compose up, …) git pushe force-push — só o que publica no remote- editar ou commitar migration Flyway já versionada
- commitar segredo (PAT, chave de API, private key, URL de banco com senha, JWT)
Os três guard.mjs são cópias byte-idênticas. Ao alterar um, copie por cima nos
outros dois: o settings.json tem escopo de diretório, então cada um precisa apontar
para um guard que exista ao seu lado. Verifique com
md5 -q .claude/hooks/guard.mjs frontend/.claude/hooks/guard.mjs backend/footfirma/.claude/hooks/guard.mjs | sort -u | wc -l
— o resultado precisa ser 1.
Bloqueio não é convite a contornar por outro caminho. Se o usuário realmente quer o
comando, ele executa na própria sessão com ! <comando>.
Vale para os dois lados
- Responda e escreva commits em português brasileiro.
- Não suba serviços sem pedido explícito (o guard bloqueia).
- Não faça
git pushnem force-push sem pedido explícito. O remote éorigin→git@github.com:caarlosandree/footfirma.git. - Operação local é livre —
merge,rebase,commit --amend,reset,clean,branch -D. Nada disso sai da máquina, e desfazer é problema local. A fronteira que o guard defende é o remote, não o histórico local. - Fluxo de branches:
stagingé a branch padrão do repositório;mainé produção. Nada vai paramainsem passar porstagingantes — branch de feature → PR parastaging→ depois de validado, PR destagingparamain. As duas branches são protegidas no GitHub (PR obrigatório, 1 aprovação, sem force-push nem delete); só o dono do repositório aprova. A proteção vale no remote e não vale para administradores (enforce_admins: false): o dono consegue push direto. Ela não alcança o repositório local — merge, rebase e reset aqui não passam por ela. - Nunca adicione
Co-authored-by:em mensagem de commit. - Nenhum segredo em código, log ou commit.
- Antes de encerrar uma tarefa, percorra o checklist do repositório:
frontend/.rules/nextjs-checklist.mdoubackend/footfirma/.rules/java-checklist.md. - Feche toda tarefa com o registro: arquivos alterados, validações executadas, validações não executadas e por quê, e alterações preexistentes no worktree. Nunca diga "pronto" sem ter rodado o comando que sustenta a frase.
Releases (release-please)
Os dois lados são versionados de forma independente, a partir dos commits convencionais. Nada é publicado em registry: o release-please só gera versão, CHANGELOG, tag e GitHub Release.
| Branch | Workflow | Produz |
|---|---|---|
staging |
.github/workflows/release-please-staging.yml |
pré-releases frontend-v0.1.0-rc.1, backend-v0.1.0-rc.1 |
main |
.github/workflows/release-please.yml |
releases estáveis frontend-v0.1.0, backend-v0.1.0 |
O que gera release
Só feat, fix, perf, revert e breaking change. docs, refactor, chore,
test, build, ci e style entram no histórico mas não versionam nada —
seguem para o próximo release junto de um commit que conte.
No release-please as duas coisas são a mesma decisão: ele pula o release quando as release notes saem vazias, então um tipo oculto no changelog é também um tipo que não dispara release. Não existe "aparece no CHANGELOG mas não versiona".
Por isso as changelog-sections são idênticas nos quatro configs. Se
divergirem, staging e main passam a discordar sobre o que merece release — e
sai versão estável em produção sem o release candidate correspondente. Ao mexer
nelas, mexa nas quatro.
Nada é compartilhado entre componentes
Cada combinação branch × componente tem config, manifesto e par de labels
próprios — quatro conjuntos independentes em .github/release-please/:
config.<componente>.<branch>.json
manifest.<componente>.<branch>.json
Isso não é organização, é correção. Com um manifesto único para os dois componentes, os dois PRs de release editam o mesmo arquivo: ao mergear um, o outro fica desatualizado e, se for mergeado antes do workflow recriá-lo, sobrescreve a versão que o primeiro acabou de gravar. As labels também precisam ser distintas, porque o release-please varre todo PR mergeado que tenha a label de release e o cruza contra os pacotes do seu config.
Consequências ao mexer nisso:
include-component-in-tag: trueé obrigatório em cada config. Como cada um declara um pacote só, sem essa opção as tags sairiam comov0.1.0, sem o prefixo do componente — e os dois componentes colidiriam na mesma tag.- Ao adicionar um terceiro componente, crie o par de arquivos e as labels dele e
acrescente o nome à
matrix.componentdos dois workflows.
Por que o PR staging → main não conflita
Nenhum arquivo é escrito pelos dois fluxos:
stagingroda comskip-changeloge sem arquivo de versão, então seu PR de release altera só o própriomanifest.<componente>.staging.json.mainescrevefrontend/package.json,frontend/CHANGELOG.md,backend/footfirma/CHANGELOG.mde a versão dobuild.gradle.
Consequência prática: não crie frontend/version.txt nem
backend/footfirma/version.txt. O release type simple só atualiza esse arquivo
se ele já existir; criá-lo faria as duas branches escreverem no mesmo lugar e traria
o conflito de volta.
A versão do backend vive no build.gradle, na linha marcada com
// x-release-please-version — é ela que o release-please reescreve. Não remova a
marcação nem coloque outro número semver na mesma linha.
Duas condições para o fluxo funcionar:
- O PR
staging → mainprecisa ser mergeado com merge commit, não squash. O release-please lê os commits convencionais individuais para calcular o bump e montar o CHANGELOG; um squash colapsa tudo em um commit só. - Em Settings → Actions → General, habilite "Allow GitHub Actions to create and
approve pull requests", senão o workflow não consegue abrir o PR de release.
Opcionalmente, defina o secret
RELEASE_PLEASE_TOKEN(PAT comcontentsepull_requests) para que o PR de release dispare os demais workflows — oGITHUB_TOKENpadrão não dispara.
Estado atual
Status: verificado em 2026-08-05.
Os dois lados estão em estágios muito diferentes.
O backend saiu do scaffold. Tem dez módulos de domínio (temporada, geografia,
clube, jogador, competicao, avaliacao, mundo, treinador, tatica,
calendario), mais config e shared como módulos abertos, vinte e duas migrations
Flyway e uma suíte de 442 testes em 69 classes. A API é read-only em /api/v1/clubes,
/jogadores, /competicoes, /jogadores/{slug}/overall, /rankings,
/competicoes/{slug}/edicoes/{temporada}/rodadas e /clubes/{slug}/jogos. Aqui já
existem exemplos prontos: ao criar um módulo novo, copie o formato de um existente em vez
de partir do zero.
O banco tem o schema completo e os seeds de país, estado, posição, característica e
perfis de peso. Em base zerada continua vazio de clubes e jogadores — os endpoints
respondem lista vazia e 404, e isso é esperado. Rodar o gerador sob o profile mundo
popula duas ligas nacionais fictícias com 40 clubes, 1.520 jogadores, 13.680 linhas
de overall, um plano tático vigente por clube e 760 jogos datados em 76 rodadas. Nada aqui é real além da geografia: clubes, estádios e jogadores são
inventados; cidade e UF saem do seed. O mundo é balanceado por seis arquétipos de
clube, de modo que time pobre tenha base forte e gigante endividado decaia. Ver
backend/footfirma/docs/runbooks/mundo.md.
O frontend continua em scaffold: layout raiz, página inicial, theme-provider e o
Button do design system. Ali as regras ainda guiam a construção em vez de descrever
o que existe.
Roadmap do backend, para situar uma tarefa nova: o subsistema de catálogo foi fatiado
em três planos. Plano 1 (schema e API read-only) e Plano 2 (avaliação e overall) estão
executados. O Plano 3 — dataset, importador de CSV e pipeline Python de dados reais —
foi abandonado e removido: o mundo passou a ser gerado proceduralmente, e o
importador existia para desconfiar de dado externo que não existe mais. Ver
docs/superpowers/specs/2026-08-03-mundo-ficticio-design.md.
O caminho até a partida foi fatiado em três camadas, e duas estão feitas: treinador
(quem decide) e tatica (o que ele decide — plano versionado, cinco instruções, onze,
banco, capitão e escalador automático determinístico). Falta a camada 3, partida.
O que faltava antes dela — calendário e rodada — está entregue em calendario, um módulo
próprio: rodada, confronto e jogo, com data e mando atribuídos por gerador determinístico
que respeita descanso mínimo por clube. Só pontos corridos está implementado; o schema
de grupos e eliminatória existe desde a V22 e não tem gerador ainda (gerarTemporada
falha alto nesses tipos). Registro de resultado e resolução de confronto existem; a
propagação do chaveamento é o que falta. Ver
docs/superpowers/specs/2026-08-05-calendario-rodada-design.md e o plano irmão.
Duas dívidas conhecidas, ambas sem spec escrito. A progressão entre temporadas:
hoje o mundo tem uma temporada só (2026), o versionamento por temporada que o schema
suporta não é exercitado, e clube.reputacao é estática — o que já morde em dois
lugares, na moral do treinador e na mentalidade da IA. E a autenticação, que é
quem liga pessoa a treinador; sem ela, treinador e tatica não têm controller, de
propósito. Competição continental e copa também ficaram fora. Ver
docs/superpowers/specs/ e docs/superpowers/plans/.