Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions .github/workflows/autofix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# autofix — o bot conserta o MECÂNICO e deixa o PR pronto. Ele NÃO mergeia.
#
# POR QUE EXISTE (medido em 21/08/2026, nos últimos 29 PRs): nenhum bot deste
# repositório jamais commitou um conserto. O #405 ficou vermelho por
# `DOCS1: documentação DESATUALIZADA`, cujo conserto é `npm run docs` mais um
# commit — trabalho sem julgamento nenhum, que hoje ou espera um mantenedor ou
# devolve o PR para o autor.
#
# A TRAVA: `scripts/ci/autofix_allowlist.py`. O bot só commita se TUDO que ele
# mexeu for arquivo gerado. Encostou em qualquer outro caminho, aborta e comenta.
# Sem essa lista, o primeiro conserto errado reescreve o mapa de um colaborador e
# ninguém repara — e a confiança de todo mundo de fora vai junto.
#
# `pull_request_target` porque é o único gatilho que enxerga secret em PR de fork
# (ver a régua WSEC1). O checkout é do MERGE do PR e roda apenas ferramentas do
# repositório base sobre ele — nunca `npm run` de script vindo do fork.
name: autofix

on:
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]
workflow_dispatch:
inputs:
pr_number:
description: PR a consertar
required: true
type: number

permissions:
contents: read
pull-requests: write

concurrency:
group: autofix-${{ github.event.pull_request.number || inputs.pr_number }}
cancel-in-progress: true

jobs:
autofix:
if: github.event.pull_request.draft != true
runs-on: ubuntu-latest
timeout-minutes: 15
env:
GH_TOKEN: ${{ secrets.CSBRASIL_BOT_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
REPO: ${{ github.repository }}
steps:
- name: Guard de identidade
run: |
if [ -z "$GH_TOKEN" ]; then
echo "::warning::CSBRASIL_BOT_TOKEN ausente — autofix não roda, e não reprova ninguém por isso."
echo "pular=1" >> "$GITHUB_ENV"
fi

- name: Dados do PR
if: env.pular != '1'
run: |
gh pr view "$PR_NUMBER" --repo "$REPO" \
--json headRefName,headRepository,headRepositoryOwner,maintainerCanModify > /tmp/pr.json
{
echo "HEAD_REF=$(jq -r .headRefName /tmp/pr.json)"
echo "HEAD_REPO=$(jq -r '.headRepositoryOwner.login + "/" + .headRepository.name' /tmp/pr.json)"
echo "PODE_EDITAR=$(jq -r .maintainerCanModify /tmp/pr.json)"
} >> "$GITHUB_ENV"

# Checa o código do PR para poder REGERAR a doc a partir dele. Nada do fork é
# executado: os geradores abaixo são os do repositório base, restaurados logo
# depois do checkout.
- name: Checkout do PR
if: env.pular != '1'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
repository: ${{ env.HEAD_REPO }}
ref: ${{ env.HEAD_REF }}
token: ${{ secrets.CSBRASIL_BOT_TOKEN }}
fetch-depth: 0

# As ferramentas que vão rodar têm de ser as da BASE, nunca as do fork — senão
# um PR malicioso reescreve o gerador e o bot executa o que ele mandar.
- name: Restaura as ferramentas da base
if: env.pular != '1'
run: |
git remote add base "https://github.com/$REPO.git" 2>/dev/null || true
git fetch --depth=1 base "${{ github.event.pull_request.base.ref || 'main' }}"
git checkout FETCH_HEAD -- tools/ scripts/ package.json

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
if: env.pular != '1'
with: { node-version: 22 }
- name: Instala dependências
if: env.pular != '1'
run: npm ci --ignore-scripts

- name: Conserta o mecânico
if: env.pular != '1'
run: |
node tools/gen-docs.mjs || echo "::warning::gen-docs falhou"
node tools/gen-arch.mjs || echo "::warning::gen-arch falhou"

# As ferramentas foram restauradas da base só para rodar; o que volta ao PR é
# apenas o resultado delas.
- name: Devolve as ferramentas ao estado do PR
if: env.pular != '1'
run: git checkout HEAD -- tools/ scripts/ package.json

- name: A trava — só arquivo gerado pode virar commit
if: env.pular != '1'
id: trava
run: |
git status --porcelain > /tmp/mexidos.txt
if [ ! -s /tmp/mexidos.txt ]; then
echo "nada=1" >> "$GITHUB_OUTPUT"
echo "PR já está em dia — nada a consertar."
exit 0
fi
if ! python3 scripts/ci/autofix_allowlist.py < /tmp/mexidos.txt > /tmp/permitidos.txt; then
cat /tmp/permitidos.txt
echo "bloqueado=1" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "commitar=1" >> "$GITHUB_OUTPUT"

- name: Comenta quando a trava bloqueia
if: env.pular != '1' && steps.trava.outputs.bloqueado == '1'
run: |
gh pr comment "$PR_NUMBER" --repo "$REPO" --body \
"🤖 **autofix**: o conserto encostou em arquivo fora da lista de permissão, então **não commitei nada**. Rode \`npm run docs && node tools/gen-arch.mjs\` e confira o que mudou."

- name: Empurra o conserto
if: env.pular != '1' && steps.trava.outputs.commitar == '1' && env.PODE_EDITAR == 'true'
run: |
git config user.name "csbrasil-bot"
git config user.email "csbrasil-bot@users.noreply.github.com"
xargs -a /tmp/permitidos.txt -r git add --
git commit -m "chore(docs): regenera bloco derivado (autofix)" \
-m "Rodado pelo autofix: só arquivo gerado, conferido pela lista de permissão." \
-m "Signed-off-by: csbrasil-bot <csbrasil-bot@users.noreply.github.com>"
git push origin "HEAD:$HEAD_REF"
gh pr comment "$PR_NUMBER" --repo "$REPO" --body \
"🤖 **autofix**: regenerei os blocos derivados e empurrei o commit. O PR não precisa de mais nada disso."

# Fork com "allow edits by maintainers" desligado: o bot não tem como empurrar.
# Comentar o comando é melhor que um vermelho que o autor não entende.
- name: Sem permissão de push — comenta o comando
if: env.pular != '1' && steps.trava.outputs.commitar == '1' && env.PODE_EDITAR != 'true'
run: |
gh pr comment "$PR_NUMBER" --repo "$REPO" --body \
"🤖 **autofix**: dá para consertar sozinho, mas este PR está com *allow edits by maintainers* desligado. Rode \`npm run docs && node tools/gen-arch.mjs\` e commite — ou ligue a opção e eu faço."
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ jobs:
# Régua se prova antes de medir o PR: portão quebrado passa calado.
- name: Autoteste das réguas de CI
run: |
python3 scripts/ci/ensure_labels.py --selftest
python3 scripts/ci/autofix_allowlist.py --selftest
python3 scripts/ci/dco_check.py --selftest
python3 scripts/ci/agente_check.py --selftest
- name: Check Signed-off-by
Expand Down
19 changes: 16 additions & 3 deletions .github/workflows/csbrasil-bot-automerge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ jobs:
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: '3.13'
- name: Garante a etiqueta de pronto
run: python3 scripts/ci/ensure_labels.py pronto-pra-merge
- name: Fetch PR status
run: gh pr view "$PR_NUMBER" --json isDraft,labels,reviewDecision,mergeStateStatus,statusCheckRollup,files > /tmp/automerge.json
- name: Check eligibility
Expand All @@ -53,13 +55,24 @@ jobs:
run: |
python3 scripts/ci/check_automerge.py --selftest
python3 scripts/ci/check_automerge.py < /tmp/automerge.json > /tmp/automerge-result.json
- name: Merge when eligible
# DEIXAR PRONTO NÃO É MERGEAR (decisão do dono, 21/08). Este passo chamava
# `gh pr merge --squash --auto` e fechava o PR sozinho. A intenção declarada dos
# bots é outra: autocorreção e autorrevisão para deixar o PR PRONTO, com o botão
# de merge continuando humano. O que era merge vira etiqueta e um comentário que
# diz por que o PR está pronto.
- name: Marca como pronto quando elegível
run: |
python3 - <<'PY'
import json, os, subprocess, sys
data = json.load(open('/tmp/automerge-result.json'))
pr = os.environ['PR_NUMBER']
if not data.get('eligible'):
print('automerge: not eligible')
print('pronto-pra-merge: not eligible')
subprocess.run(['gh', 'pr', 'edit', pr, '--remove-label', 'pronto-pra-merge'], check=False)
sys.exit(0)
subprocess.run(['gh', 'pr', 'merge', os.environ['PR_NUMBER'], '--squash', '--delete-branch', '--auto'], check=True)
subprocess.run(['gh', 'pr', 'edit', pr, '--add-label', 'pronto-pra-merge'], check=True)
subprocess.run(['gh', 'pr', 'comment', pr, '--body',
'🤖 **csbrasil-bot**: portões verdes e diff dentro do que o '
'`check_automerge` aceita — marquei como `pronto-pra-merge`. '
'O merge continua sendo seu.'], check=True)
PY
8 changes: 8 additions & 0 deletions .github/workflows/preview-bot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ jobs:
REPO: ${{ github.repository }}
run: gh pr edit "$PR" --repo "$REPO" --remove-label "preview-autorizado" 2>/dev/null || true

# O job `preview` abaixo só dispara em `labeled` com esta etiqueta. Ela nunca foi
# criada no repositório, então o caminho inteiro era código morto: o bot pedia um
# rótulo que ninguém tinha como aplicar. Criar aqui é o que liga o mecanismo.
- name: garante a etiqueta que autoriza o preview
env:
GH_TOKEN: ${{ github.token }}
run: python3 scripts/ci/ensure_labels.py preview-autorizado

- name: classifica o diff sem executar código do fork
env:
GH_TOKEN: ${{ github.token }}
Expand Down
6 changes: 3 additions & 3 deletions ARCH.generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Números atuais das zonas, do quality gate e do `package.json`:
|---|---|---|---|
| `public/` | o **jogo** | 44 arquivos `.js`, 31.956 linhas · Three.js `r160` vendorizado | ES modules servidos crus, **zero build**, sem dependência de runtime |
| `src/` | o **site** | 18 páginas `.astro`, 21 rotas `/api` · Astro `^7.1.1` | framework é bem-vindo; `service_role` só no servidor |
| `tools/` | o **arnês** | 195 scripts em `tools/eval/`, 54 em `tools/` | node puro: sobe o jogo real sem browser |
| `tools/` | o **arnês** | 197 scripts em `tools/eval/`, 54 em `tools/` | node puro: sobe o jogo real sem browser |

**Não existe `public/index.html`.** O HTML do jogo é `src/pages/index.astro`, servido na rota `/`. Servir `public/` estaticamente entrega os arnêses visuais, **não o jogo** — é a pegadinha que custa a primeira hora de todo mundo.

Expand All @@ -25,10 +25,10 @@ Comandos e scripts atuais do quality gate:
<!-- BEGIN:GERADO:scripts — não edite à mão, rode `npm run docs` -->

```bash
npm run check:fast # node tools/eval/runner.mjs syntax eval:release eval:telemetry eval:identity eval:error-console eval:error-origin eval:webgl eval:webglguard eval:maprotate eval:shaderlog eval:shaderbudget eval:botbrain eval:prune eval:vminspect eval:faccao eval:mapid eval:mapjson eval:mapcontrato eval:pickuparma eval:parquewheel eval:redesign eval:matchoptions eval:charvoice eval:screenquery docs:check arch:check audio:check feet:check eval:vmlabhud eval:ctfhud eval:pause eval:ctfround eval:ctfwin eval:spawn eval:regen eval:pegada eval:dmgdir eval:ctflabels anims:check anims:merge:check walls:check media:check menuwalls:check travessao:check eval:medianet eval:posters eval:grafitelayout eval:simclock eval:backendhints changelog:check eval:velhooeste eval:penitenciaria eval:comentario eval:fixture eval:wfsecret eval:preload eval:docsautoria eval:replaycam
npm run check:fast # node tools/eval/runner.mjs syntax eval:release eval:telemetry eval:identity eval:error-console eval:error-origin eval:webgl eval:webglguard eval:maprotate eval:shaderlog eval:shaderbudget eval:botbrain eval:prune eval:vminspect eval:faccao eval:mapid eval:mapjson eval:mapcontrato eval:pickuparma eval:parquewheel eval:redesign eval:matchoptions eval:charvoice eval:screenquery docs:check arch:check audio:check feet:check eval:vmlabhud eval:ctfhud eval:pause eval:ctfround eval:ctfwin eval:spawn eval:regen eval:pegada eval:dmgdir eval:ctflabels anims:check anims:merge:check walls:check media:check menuwalls:check travessao:check eval:medianet eval:posters eval:grafitelayout eval:simclock eval:backendhints changelog:check eval:velhooeste eval:penitenciaria eval:comentario eval:fixture eval:wfsecret eval:deploygate eval:autofix eval:preload eval:docsautoria eval:replaycam
```

`package.json` tem **119 scripts**; o motivo de cada um mora em `SCRIPTS.md` (migrado das chaves `//nome` em 18/08/2026) — é onde está o porquê.
`package.json` tem **121 scripts**; o motivo de cada um mora em `SCRIPTS.md` (migrado das chaves `//nome` em 18/08/2026) — é onde está o porquê.

> Bloco gerado por `node tools/gen-docs.mjs`. Fonte: `node -p "Object.keys(require('./package.json').scripts)"`

Expand Down
24 changes: 24 additions & 0 deletions PROD-READINESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@
- Headers de cache: `/vendor/*` curto, `/models/*` e `/audio/a/*` longo/immutable, `og-image.png` 7 dias.
- CSP está declarado em `vercel.json`; import map e scripts são `self` + `unsafe-inline` + Cloudflare beacon.

## Preview de PR e a Vercel (21/08/2026)

Medido nos últimos 29 PRs: a Vercel reprovou **13 dos 14 PRs vindos de fork**, sempre com
`Authorization required to deploy.` - é a proteção de fork da própria Vercel, e nenhum
commit do colaborador a resolve.

**Quem bloqueia o PR é o `ci.yml`**, que já roda `npm run build` em `pull_request`. A
Vercel é conveniência, não portão - a régua `eval:deploygate` (DG1) guarda essa condição.

Dois ajustes ficam no **painel da Vercel**, fora do alcance do repositório, e precisam da
conta dona do projeto:

1. **Project → Git → Deploy Hooks / Fork Protection**: desligar o deploy automático de PR
vindo de fork. Enquanto estiver ligado, todo PR externo nasce com um vermelho que o
autor não tem como consertar.
2. **Settings → Git → Ignored Build Step**: opcional, para parar de gastar build em branch
de PR interna. A produção continua publicando pela `main`.

O preview de fork continua disponível **sob demanda**: um mantenedor aplica a etiqueta
`preview-autorizado` e o job `preview` do `preview-bot.yml` publica pela CLI da Vercel,
depois de conferir permissão do ator e SHA aprovado. A etiqueta passou a ser criada pelo
próprio workflow (DG3) - antes ela não existia no repositório e o caminho inteiro era
código morto.

## Blockers conhecidos para prod

1. **`npm run check` (full) não foi executado nesta sessão.** `check:fast` passou; `check` ainda inclui `eval:vm`, `invariants`, `kick`, `bots` e deve ser verde antes de publicar.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ contra bots, direto na aba. Sem download, sem instalação, sem cadastro.
| Personagens jogáveis | 44, em 5 facções | array `CHARACTERS` de `characters.js` |
| Mapas no registro | 12 | objeto `MAPS` de `maps.js` |
| Arnêses visuais em HTML | 15 | `git ls-files 'public/*.html' \| wc -l` |
| Scripts do arnês | 195 | `git ls-files 'tools/eval/*.mjs' 'tools/eval/*.py' \| wc -l` |
| Scripts do arnês | 197 | `git ls-files 'tools/eval/*.mjs' 'tools/eval/*.py' \| wc -l` |
| Scripts de pipeline | 54 | `git ls-files 'tools/*.mjs' \| wc -l` |
| Tarefas de entrada escritas | 26 | `git ls-files 'docs/issues/[0-9]*.md' \| wc -l` |
| Versão | `2.0.0-alpha.172` | `public/js/version.js` e `package.json` (batem) |
Expand Down
2 changes: 1 addition & 1 deletion STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
- **Versão:** `2.0.0-alpha.172`
- **Conteúdo jogável:** 5 facções, 44 personagens, 12 mapas e 26 armas com GLB
- **Código do jogo:** 31.956 linhas em 44 módulos JavaScript
- **Automação:** 119 comandos npm, 195 scripts de avaliação e 54 scripts de pipeline
- **Automação:** 121 comandos npm, 197 scripts de avaliação e 54 scripts de pipeline

> Bloco gerado por `node tools/gen-docs.mjs`. Fonte: `package.json · CHARACTERS · MAPS · public/models/weapons · public/js · tools/`

Expand Down
6 changes: 3 additions & 3 deletions docs/docs/comecando.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ esta página envelhecia no primeiro commit — ver
| Personagens jogáveis | 44, em 5 facções | array `CHARACTERS` de `characters.js` |
| Mapas no registro | 12 | objeto `MAPS` de `maps.js` |
| Arnêses visuais em HTML | 15 | `git ls-files 'public/*.html' \| wc -l` |
| Scripts do arnês | 195 | `git ls-files 'tools/eval/*.mjs' 'tools/eval/*.py' \| wc -l` |
| Scripts do arnês | 197 | `git ls-files 'tools/eval/*.mjs' 'tools/eval/*.py' \| wc -l` |
| Scripts de pipeline | 54 | `git ls-files 'tools/*.mjs' \| wc -l` |
| Tarefas de entrada escritas | 26 | `git ls-files 'docs/issues/[0-9]*.md' \| wc -l` |
| Versão | `2.0.0-alpha.172` | `public/js/version.js` e `package.json` (batem) |
Expand Down Expand Up @@ -275,10 +275,10 @@ E os dois quality gates, com a lista exata do que cada um roda — direto do `pa
{/* BEGIN:GERADO:scripts — não edite à mão, rode `npm run docs` */}

```bash
npm run check:fast # node tools/eval/runner.mjs syntax eval:release eval:telemetry eval:identity eval:error-console eval:error-origin eval:webgl eval:webglguard eval:maprotate eval:shaderlog eval:shaderbudget eval:botbrain eval:prune eval:vminspect eval:faccao eval:mapid eval:mapjson eval:mapcontrato eval:pickuparma eval:parquewheel eval:redesign eval:matchoptions eval:charvoice eval:screenquery docs:check arch:check audio:check feet:check eval:vmlabhud eval:ctfhud eval:pause eval:ctfround eval:ctfwin eval:spawn eval:regen eval:pegada eval:dmgdir eval:ctflabels anims:check anims:merge:check walls:check media:check menuwalls:check travessao:check eval:medianet eval:posters eval:grafitelayout eval:simclock eval:backendhints changelog:check eval:velhooeste eval:penitenciaria eval:comentario eval:fixture eval:wfsecret eval:preload eval:docsautoria eval:replaycam
npm run check:fast # node tools/eval/runner.mjs syntax eval:release eval:telemetry eval:identity eval:error-console eval:error-origin eval:webgl eval:webglguard eval:maprotate eval:shaderlog eval:shaderbudget eval:botbrain eval:prune eval:vminspect eval:faccao eval:mapid eval:mapjson eval:mapcontrato eval:pickuparma eval:parquewheel eval:redesign eval:matchoptions eval:charvoice eval:screenquery docs:check arch:check audio:check feet:check eval:vmlabhud eval:ctfhud eval:pause eval:ctfround eval:ctfwin eval:spawn eval:regen eval:pegada eval:dmgdir eval:ctflabels anims:check anims:merge:check walls:check media:check menuwalls:check travessao:check eval:medianet eval:posters eval:grafitelayout eval:simclock eval:backendhints changelog:check eval:velhooeste eval:penitenciaria eval:comentario eval:fixture eval:wfsecret eval:deploygate eval:autofix eval:preload eval:docsautoria eval:replaycam
```

`package.json` tem **119 scripts**; o motivo de cada um mora em `SCRIPTS.md` (migrado das chaves `//nome` em 18/08/2026) — é onde está o porquê.
`package.json` tem **121 scripts**; o motivo de cada um mora em `SCRIPTS.md` (migrado das chaves `//nome` em 18/08/2026) — é onde está o porquê.

> Bloco gerado por `node tools/gen-docs.mjs`. Fonte: `node -p "Object.keys(require('./package.json').scripts)"`

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/quality-gates.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ PR (`.github/workflows/ci.yml`).
{/* BEGIN:GERADO:invariantes — não edite à mão, rode `npm run docs` */}

- `tools/eval/invariants.mjs`: **2.275 linhas**, **65 identificadores de invariante declarados** (`put()`), dos quais **28** têm caminho de `skip()` declarado.
- O arnês inteiro são **195 scripts** em `tools/eval/` (`.mjs` + `.py`), mais **54 scripts** de pipeline em `tools/`.
- O arnês inteiro são **197 scripts** em `tools/eval/` (`.mjs` + `.py`), mais **54 scripts** de pipeline em `tools/`.
- Quantas invariantes rodam como **críticas** numa execução **não é derivável do fonte**: depende de qual insumo existe na máquina (o JSON do auditor de viewmodel, um GLB, uma pasta de anims). Esse número só sai rodando o quality gate — e o lugar dele é o cabeçalho do `KNOWN-BUGS.md`, atualizado com saída real.

Reproduza:
Expand Down
Loading
Loading