Compare commits

...

3 commits

Author SHA1 Message Date
Francisco Solla
bc1a109239 docs(OPS68): agregado do changelog com a entrada OPS68 (ficou fora do rebase)
All checks were successful
CI (PR) / checks (pull_request) Successful in 1m15s
PR Ready + auto-merge / ready-automerge (pull_request) Successful in 1m21s
Promote plan-issue Related on main merge / plan-issue-ready (pull_request) Has been skipped
Main merge flips claimed issues to done + in-prod / issue-done (pull_request) Successful in 12s
O --ours no conflito do agregado (OPS66) deixou a entrada OPS68 fora do
commit; changelog:build regenera e o docs-guard do CI exige o agregado
commitado.
2026-08-19 10:31:16 -03:00
Francisco Solla
235cf36dc1 docs(OPS68): reaplica fixes do AGENTS.md perdidos na resolução do rebase (OPS66)
Some checks failed
CI (PR) / checks (pull_request) Failing after 37s
PR Ready + auto-merge / ready-automerge (pull_request) Failing after 2m3s
O checkout --ours no conflito da L15 (gate + frase build-after-migrate do
OPS66) descartou os hunks limpos do rebase: Neon-refs da seção de DB
(header/at Neon/no Neon risk/use Neon), rationale WebAuthn host-agnóstico.
2026-08-19 10:26:14 -03:00
Francisco Solla
0043608302 docs(OPS68): reescreve docs canônicos da era GitHub/Vercel para Forgejo/homeserver
Parte 2 do OPS50 (fechamento): AGENT-OPS, README, roadmap (ponteiro),
AGENTS.md (refs Neon/job gate), rule agent-pr-workflow e 8 skills de
operação sem comandos gh (API/MCP + safety net) nem secrets Vercel —
fonte canônica única: git.solla.dev/fsolla/teqo.
2026-08-19 10:21:29 -03:00
16 changed files with 173 additions and 89 deletions

View file

@ -11,13 +11,15 @@ alwaysApply: true
```bash
pnpm push -u origin HEAD
gh pr create --base main --title "<id> — <title>" --body "$(cat <<'EOF'
```
Depois, abra a PR via API/MCP do opencode (humano) ou `ManagePullRequest` (Cursor Cloud) — **Ready**, base **`main`**, title `<id> — <title>`, body:
```
## O que é
<2–4 linhas>
Closes #<N>
EOF
)"
```
- **Never** `--draft`. PRs must be **Ready for review** on creation.
@ -26,11 +28,9 @@ EOF
### Cursor Cloud / `ManagePullRequest`
When the environment says to use `ManagePullRequest` instead of `gh pr create`:
- **`draft: false`** (or omit only if the tool’s default is Ready — today Cloud defaults to draft, so **always pass `draft: false`**).
- Right after create (or on `update_pr` if you opened draft by mistake): mark Ready if still draft, then enable auto-merge with `gh pr merge --auto --rebase <url|number>` (write allowed for this step — same as `agent-pr-workflow`; do not leave the PR waiting on a human).
- Never end a turn with an open agent PR left as **Draft** or without **auto-merge** armed — inclui PRs do `engineering-audit` (artefatos e remediações).
- Não há auto-merge a armar por PR: o safety net (abaixo) espera o rollup `checks` e mergea sozinho.
- Never end a turn with an open agent PR left as **Draft** — inclui PRs do `engineering-audit` (artefatos e remediações).
## Push (determinístico)
@ -48,13 +48,7 @@ git push --no-verify # WIP / cutover escape — CI repete tudo; ver AGENT-OPS
## Auto-merge immediately
Right after the PR exists and is **Ready**:
```bash
gh pr merge --auto --rebase
```
Do not wait for human approval on normal deliveries. Auto-merge fires when **required** checks pass.
The safety net (`agent-pr-ready-automerge.yml`, abaixo) merges the PR when the required checks pass — there is nothing to arm per PR. Do not wait for human approval on normal deliveries.
## Required checks
@ -66,26 +60,22 @@ Branch protection on `main` (applied 2026-08-18, OPS61) requires only:
(`migration-lock` removed 2026-08-12 — the CI validates migration chains via migrate + int on every PR/main.)
**There is no deploy from CI.** The `ci.yml` verifier on `main` runs the full suite and stops (production is self-hosted on the homeserver — `jorgesolla1313.com.br` — deployed via an ops runbook outside this repo). Do not add a Vercel/Git-integration deploy gate.
**There is no deploy from PR CI.** The `ci.yml` verifier on `main` runs the full suite in its fixed 30-minute window and, on a production change, the `deploy` job publishes to the homeserver (`jorgesolla1313.com.br`, self-hosted) — migrations applied before the rollout. Do not add a Vercel/Git-integration deploy gate.
When watching CI:
When watching CI, use the PR page (or the API) — the safety net is what waits on checks; `gh` does not exist in this repo (OPS50).
```bash
gh pr checks <PR> --watch --required
```
Use `--required` — without it, `--watch` also waits for Vercel.
Use the rollup context `CI (PR) / checks` — without it, you are not covering the cascade.
## Promote
There is no stage→main promote. Merging to `main` is the delivery; `ci.yml` verifies the full suite and production deploys from the homeserver runbook.
There is no stage→main promote. Merging to `main` is the delivery; `ci.yml` verifies the full suite and, on a production change, deploys to the homeserver (OPS53/OPS65; runbook: `docs/ops/teqo-1313-deploy.md`).
## Safety net (workflow)
`agent-pr-ready-automerge.yml` — PRs same-repo → `main` (todas as branches).
O workflow roda `scripts/forgejo-pr-automerge.mjs` (plain Node, zero-dep): se
draft `cursor/*` → marca Ready; espera os checks e mergea por rebase (o
equivalente Forgejo do `gh pr merge --auto --rebase`). O wait exige o rollup
equivalente Forgejo do auto-merge — sem `gh`). O wait exige o rollup
`CI (PR) / checks` POSTADO e verde (OPS61) — o único contexto que só existe
quando a cascata inteira do ci-pr assentou (jobs ainda não agendados não
postam status; o próprio status do job do safety net é `pending` durante o run

View file

@ -2,7 +2,7 @@
name: agent-pool
description: >-
Opera e monitora o pool de agentes Cursor Cloud do Teqo (supervisor remoto
determinístico em GitHub Actions). Ao invocar: SEMPRE status + fila elegível
determinístico em Forgejo Actions). Ao invocar: SEMPRE status + fila elegível
primeiro; NUNCA liga o pool sem confirmação explícita do usuário. Usar quando
o usuário pedir /agent-pool, ligar/desligar/pausar o pool, ver status dos
workers, "pool de agentes", orquestrador, triage de Issue que o pool mandou
@ -11,7 +11,11 @@ description: >-
# Agent pool — operação do supervisor remoto
O pool mantém até **5 Cursor Cloud Agents** (configurável, máx 12) rodando `agent-work-issue` sobre Issues `ready` elegíveis para autonomia. O supervisor é **determinístico** (não é um agente): workflow **`.github/workflows/agent-pool.yml`** (tick stateless a cada 10 min + a cada merge em `main` + sob dispatch) rodando `scripts/agent-pool.mjs`. Arquitetura e elegibilidade: `docs/plans/agent-pool-orchestrator.md`.
**DORMENTE (OPS65):** o workflow `agent-pool.yml` foi removido (o tick `*/10`
custava 144 `pnpm install`/dia na workstation) e o dispatch via CLI falha 404 —
scripts/skill ficam para histórico e leitura. O que foi:
O pool mantém até **5 Cursor Cloud Agents** (configurável, máx 12) rodando `agent-work-issue` sobre Issues `ready` elegíveis para autonomia. O supervisor é **determinístico** (não é um agente): workflow `.forgejo/workflows/agent-pool.yml` (removido) com tick stateless a cada 10 min + a cada merge em `main` + sob dispatch, rodando `scripts/agent-pool.mjs`. Arquitetura e elegibilidade: `docs/plans/agent-pool-orchestrator.md`.
**O pool NUNCA deploya** — deploy gated fica em `ci.yml` após merge em `main`.
@ -19,7 +23,7 @@ O pool mantém até **5 Cursor Cloud Agents** (configurável, máx 12) rodando `
## Fluxo obrigatório ao invocar (não pule)
**Nunca** rode `pnpm agent:pool -- start` (nem `gh workflow run … action=start`) no primeiro passo. O default é só leitura.
**Nunca** rode `pnpm agent:pool -- start` no primeiro passo — e o dispatch do workflow não existe mais (OPS65: `start`/`stop` via CLI falham 404). O default é só leitura.
```
- [ ] 1. Pré-check leve: `pnpm agent:pool -- doctor` se for a 1ª vez na sessão / suspeita de secret; senão pode ir direto ao status
@ -87,7 +91,7 @@ pnpm agent:pool -- resume
pnpm agent:pool -- status # leitura local (config, slots, fila, exclusões)
```
Os wrappers disparam `gh workflow run agent-pool.yml -f action=…`; acompanhe com `gh run list --workflow agent-pool.yml --limit 3` (o job summary de cada run é a superfície remota de status: slots, spawns, falhas, decisão do tick). Estado escalar vive em repo variables `POOL_*` (Settings → Variables); workers/Issues em voo são derivados — Issues `in-progress` com marcador `pool-worker` nos comentários.
Os wrappers disparam `workflow_dispatch` em `agent-pool.yml` via API do Forgejo (workflow removido no OPS65 — dispatch falha 404); acompanhe com `pnpm agent:pool -- status` (o job summary de cada run era a superfície remota de status: slots, spawns, falhas, decisão do tick). Estado escalar vive em `pool-state.json` na branch `pool-state`; workers/Issues em voo são derivados — Issues `in-progress` com marcador `pool-worker` nos comentários.
## Ciclo de vida de uma Issue no pool

View file

@ -1,6 +1,6 @@
---
name: capture-review-debts
description: Use when the user asks to register leftover /simplify or /impeccable findings as trackable GitHub Issues, harvest session review debts, triage post-simplify or post-critique follow-ups, or says "registra os débitos", "o que ficou do simplify", "coloca como issue o que o critique apontou", "harvest debts".
description: Use when the user asks to register leftover /simplify or /impeccable findings as trackable Forgejo Issues, harvest session review debts, triage post-simplify or post-critique follow-ups, or says "registra os débitos", "o que ficou do simplify", "coloca como issue o que o critique apontou", "harvest debts".
---
# Capturar débitos de /simplify e /impeccable em Issues
@ -15,7 +15,7 @@ Após um ciclo de entrega, `/simplify` e `/impeccable` deixam achados **maiores
```
- [ ] 1. Colher candidatos da sessão (simplify + impeccable)
- [ ] 2. Deduplicar contra código, Issues (gh issue list) e planos
- [ ] 2. Deduplicar contra código, Issues (`pnpm issue all`) e planos
- [ ] 3. Pontuar importância e classificar destino
- [ ] 4. Mesclar relacionados (mesmo lote / mesma superfície)
- [ ] 5. Apresentar tabela de triage e obter confirmação
@ -50,7 +50,7 @@ Contra o repositório **antes** de pontuar:
| Check | Ação se verdadeiro |
| ------------------------------------------------------ | --------------------------------------------------------------- |
| Já no diff / código da sessão | → balde **já_resolvido** (não reabrir) |
| Já em `docs/plans/*.md` ou numa Issue (grep + `gh issue list`) | → **absorver** no plano existente ou **descartar** como coberto |
| Já em `docs/plans/*.md` ou numa Issue (grep + `pnpm issue all`) | → **absorver** no plano existente ou **descartar** como coberto |
| Pré-existente em `main`, fora do escopo da entrega | → **descartar** deste lote (bug separado só se o usuário pedir) |
| Intentional em `DESIGN.md` / decisão travada | → **descartar** (ex.: field-mode mobile documentado) |

View file

@ -11,9 +11,9 @@ description: Runs a Pass-style engineering audit of the Teqo codebase — code s
A skill roda sem supervisão humana no paradigma de agentes paralelos. Quatro diferenças em relação à execução interativa (desktop):
1. **Precheck solitário, fail-closed.** Antes de qualquer varredura: `pnpm agent:pool -- status` (read-only). Pool `desligado` ou `pausado` → segue. Pool **ligado** → o agente Cloud não consegue pausá-lo (`gh workflow run` é escrita): **pare** com o remédio nomeado ("pause o pool — `pnpm agent:pool -- pause` — e re-dispare o audit"). Nunca rodar a varredura com o pool spawnando.
1. **Precheck solitário, fail-closed.** Antes de qualquer varredura: `pnpm agent:pool -- status` (read-only). Pool `desligado` ou `pausado` → segue. Pool **ligado** → o agente Cloud não consegue pausá-lo (dispatch de workflow é escrita): **pare** com o remédio nomeado ("pause o pool — `pnpm agent:pool -- pause` — e re-dispare o audit"). Nunca rodar a varredura com o pool spawnando.
2. **Entrega = PR Ready + auto-merge.** O gate "apresentar os três artefatos antes de escrever" (passo 5) não se aplica em modo autônomo — não há humano interativo na sessão. Os artefatos são escritos na branch e o **PR Ready → `main` com auto-merge** (mesmo contrato `agent-pr-workflow`) é a entrega do plano. Fechar o PR antes do merge = rejeição (o ledger sobrevive no histórico da branch). As remediações **P0/P1 e os guardrails das misses colhidas** seguem na mesma sessão, cada um em PR próprio com gate completo + auto-merge. P2/P3 continuam só ledger/plano.
3. **Canais de PR:** fechar miss colhida = `Closes #N` no body do PR do guardrail (o flip `done`/`in-prod` no merge em `main` — keyword adjacente a cada número, `Closes #52, closes #73`, nunca `Closes #52, #73`). Em Cloud, preferir `ManagePullRequest` com **`draft: false`**, depois `gh pr merge --auto --rebase`. **Sem exceção:** o PR dos *artefatos* do audit também é Ready + auto-merge (igual remediações P0/P1 e guardrails). Push: `pnpm push -u origin HEAD`.
3. **Canais de PR:** fechar miss colhida = `Closes #N` no body do PR do guardrail (o flip `done`/`in-prod` no merge em `main` — keyword adjacente a cada número, `Closes #52, closes #73`, nunca `Closes #52, #73`). Em Cloud, usar `ManagePullRequest` com **`draft: false`** (o merge fica com o safety net `agent-pr-ready-automerge`). **Sem exceção:** o PR dos *artefatos* do audit também é Ready + auto-merge (igual remediações P0/P1 e guardrails). Push: `pnpm push -u origin HEAD`.
4. **Delta desde o último audit é o foco declarado** (passo 1): a varredura cobre o repo inteiro, mas o esforço concentra-se no churn desde a data do último Pass — é o trabalho paralelo dos agentes desde o último audit que precisa ser consolidado.
Read-only audit that sweeps the Teqo codebase for code smells, drift from documented patterns, and consolidation opportunities, then produces a findings ledger and a remediation plan. **No fixes in the audit run** — deliverables are updated docs, presented for sign-off before writing — exceto as remediações P0/P1 declaradas acima. Sign-off: interativo = apresentação prévia dos artefatos; autônomo (Cursor Cloud) = PR Ready + auto-merge dos artefatos (ver "Modo autônomo (Cursor Cloud)").
@ -39,7 +39,7 @@ Method: the engineering skills this repo follows (improve-code-quality, clean-co
1. Read-only on `src/`, `tests/`, `scripts/` durante a varredura. Os únicos writes da sessão são: os artefatos do passo 5 (após sign-off interativo; em modo autônomo, direto na branch do PR) e as remediações P0/P1 + guardrails de misses colhidas declarados no cabeçalho.
2. Gate commands bare, never piped (`pnpm test | tail` swallows the exit code).
3. Never `knip --fix` blind — verify with `git grep -w <symbol>` (knip cannot load `payload.config.ts`; ledgered P3).
4. Production is live Neon with real PII. Local DB only, `teqo_test` for tests. The audit needs no DB writes.
4. Production is live Postgres on the homeserver (`teqo_1313`) with real PII. Local DB only, `teqo_test` for tests. The audit needs no DB writes.
5. Every claim gets a number (lines, exports, call sites, ms, kB). "Rejected by measurement" is an acceptable outcome for any hypothesis, including the ones below.
## Step 0a — Load the canon
@ -63,12 +63,12 @@ Read, in this order:
Antes da varredura, colha os défices comportamentais registrados pelo fluxo:
```bash
gh issue list --label kind:agent-miss --state open
pnpm issue all # kind `agent-miss` nos contadores; ou api.listIssues({ state: 'open', labels: 'kind:agent-miss' })
```
**Investigue cada miss colhida** (`gh issue view <N>` — leitura, disponível no Cloud): o corpo registra causa raiz, fix aplicado e o guardrail proposto por `agent:file-miss`. Verifique no código se o fix segurou e se a classe do defeito ainda é reproduzível. Miss cuja classe já tem guarda viva não gera guardrail novo — gera item de hardening da guarda existente (o passo 2 já trata guarda dodgeable como finding).
**Investigue cada miss colhida** (leitura da Issue via API do Forgejo/MCP — disponível no Cloud): o corpo registra causa raiz, fix aplicado e o guardrail proposto por `agent:file-miss`. Verifique no código se o fix segurou e se a classe do defeito ainda é reproduzível. Miss cuja classe já tem guarda viva não gera guardrail novo — gera item de hardening da guarda existente (o passo 2 já trata guarda dodgeable como finding).
Cada Issue colhida alimenta o **Passo 4b (recurrence prevention)**: toda miss vira candidata a guardrail determinístico (tipo / ESLint / convention spec / CI / pin comportamental). O ledger desses guardrails vive em **`docs/GUARDRAILS.md`** (criar o arquivo na primeira execução — é o escopo da Issue OPS2): uma linha por guardrail — miss de origem (link da Issue), classe 1–6, mecanismo, status. Quando o guardrail mergeia, **feche as Issues colhidas** (`done`) com comentário apontando o guardrail. Em Cursor Cloud o fechamento é determinístico e não exige escrita `gh`: o PR do guardrail carrega `Closes #N` (keyword repetida por número — o regex do `issue-done-on-stage-merge.yml` não resolve `Closes #A, #B`) e o merge em `stage` flippa a miss para `done`; guardrails de várias misses podem ir no mesmo PR desde que cada uma tenha seu `closes`. Miss que não admite guardrail determinístico vira convenção explícita marcada "judgment-only" — não fingir que doc é guarda.
Cada Issue colhida alimenta o **Passo 4b (recurrence prevention)**: toda miss vira candidata a guardrail determinístico (tipo / ESLint / convention spec / CI / pin comportamental). O ledger desses guardrails vive em **`docs/GUARDRAILS.md`** (criar o arquivo na primeira execução — é o escopo da Issue OPS2): uma linha por guardrail — miss de origem (link da Issue), classe 1–6, mecanismo, status. Quando o guardrail mergeia, **feche as Issues colhidas** (`done`) com comentário apontando o guardrail. Em Cursor Cloud o fechamento é determinístico e não exige escrita manual: o PR do guardrail carrega `Closes #N` (keyword repetida por número — o regex do `issue-done-on-main-merge.yml` não resolve `Closes #A, #B`) e o merge em `main` flippa a miss para `done`; guardrails de várias misses podem ir no mesmo PR desde que cada uma tenha seu `closes`. Miss que não admite guardrail determinístico vira convenção explícita marcada "judgment-only" — não fingir que doc é guarda.
## Step 1 — Hotspot map
@ -166,7 +166,7 @@ Rules:
1. `docs/IMPROVE-CODE-QUALITY-PLAN.md` — new Pass section (context, audit headlines with numbers, workstreams, decisions), matching earlier-pass format.
2. `docs/TECH-DEBT.md` — new rows in the same tables/columns, marked `open — Pass N`; stale rows closed with evidence.
3. `docs/plans/entrega-engenharia-pN.md` (pt-BR, like the other plans) — workstreams ordered P0→P1→P2; each item: goal, evidence, target shape, migration path (Branch by Abstraction / Parallel Change for wide merges), pins to write first, full gate, rollback. Items too big for one delivery → Issue rastreável via `plan-issue`. Every workstream item declares its **Guarda determinística** (class 1–6, step 4b), and the plan carries a **guard map** section: new guards shipped / existing guards hardened / judgment-only residue with the convention that stands in.
4. **Interativo (desktop):** present ALL THREE for sign-off before writing. Then write and stop. **Autônomo (Cursor Cloud):** não há sign-off interativo — escreva os três na branch e abra o PR (**Ready**, base `main`, **`gh pr merge --auto --rebase`** — mesmo fluxo `agent-pr-workflow`; "Modo autônomo", item 2). As remediações P0/P1 e os guardrails das misses colhidas seguem na mesma sessão, em PRs próprios com gate completo + auto-merge, sem esperar o merge dos artefatos.
4. **Interativo (desktop):** present ALL THREE for sign-off before writing. Then write and stop. **Autônomo (Cursor Cloud):** não há sign-off interativo — escreva os três na branch e abra o PR (**Ready**, base `main` — mesmo fluxo `agent-pr-workflow`; "Modo autônomo", item 2; o merge fica com o safety net). As remediações P0/P1 e os guardrails das misses colhidas seguem na mesma sessão, em PRs próprios com gate completo + auto-merge, sem esperar o merge dos artefatos.
## Execution rules for the eventual implementation (record in the plan)

View file

@ -1,7 +1,7 @@
---
name: plan-issue
description: >-
Transforma ideias humanas em Issues GitHub + planos de intenção em
Transforma ideias humanas em Issues do Forgejo + planos de intenção em
docs/plans/ (persona, fluxo, objetivo, direção suave no código — sem
decisões duras de engenharia). Se o item muda UI, apresenta no gate um
rascunho visual HTML+Tailwind (PNG embutido no plano de intenção). Divide
@ -13,7 +13,7 @@ disable-model-invocation: true
# Planejar Issues (intenção, não engenharia)
Esta skill transforma ideias soltas em: (1) um **plano de intenção** em `docs/plans/<slug>.md` por item, e (2) uma **GitHub Issue rastreável** (`pnpm agent:register`, frontmatter `id/depends/serializes/priority/model`). GitHub Issues são a fonte canônica de spec/status/deps/prio/modelo — `docs/roadmap.md` é legado congelado e **nunca** é editado aqui.
Esta skill transforma ideias soltas em: (1) um **plano de intenção** em `docs/plans/<slug>.md` por item, e (2) uma **Issue rastreável no Forgejo** (`pnpm agent:register`, frontmatter `id/depends/serializes/priority/model`). As Issues do Forgejo (`git.solla.dev/fsolla/teqo`) são a fonte canônica de spec/status/deps/prio/modelo — `docs/roadmap.md` é legado congelado e **nunca** é editado aqui.
## Ciclo de vida (obrigatório)
@ -29,7 +29,7 @@ rascunho local (Issue: —)
**Regras duras:**
1. **Nada no GitHub antes do gate.** Antes da confirmação explícita do Passo 5: proibido `pnpm agent:register`, `gh issue create`, `gh pr create` / push de PR de planos. Planos locais (`Issue: —`) ok.
1. **Nada no Forgejo antes do gate.** Antes da confirmação explícita do Passo 5: proibido `pnpm agent:register`, criar Issue/PR / push de PR de planos. Planos locais (`Issue: —`) ok.
2. **Confirmação = OK ao overview do lote** (ex. “confirma”, “pode registrar”). “Ok” ambíguo no meio da edição **não** dispara o Passo 6.
3. **Register com `--plan` não nasce `ready`.** O script aplica `blocked` automaticamente quando `--plan` está presente (`--blocked` explícito ainda vale para chores sem plano). Sem `--plan` (chore body-only / `file-miss`), pode nascer `ready`.
4. **Promote só depois do plano em `main`.** Nunca flipar para `ready` com o PR ainda aberto — isso recria a race de claim. Caminhos: (A) `pnpm agent:ready -- --issue N` no fim do Passo 6 após merge; (B) Action determinística no merge que lê `Related #N` (OPS18). Ambos idempotentes.
@ -67,7 +67,7 @@ Aqui **não** se implementa código de produto, **não** se escreve plano de imp
1. **Separe os itens.** Entrada pode ser 1 ideia ou N. Se ambíguo, assuma a leitura mais provável e liste — a confirmação vai no gate.
2. **Fatia mínima útil.** Prefira várias Issues pequenas a um epic. Cada item deve caber num appetite curto e entregar um outcome verificável sozinho. Mesclar só quando separar criaria trabalho inútil (mesmo fluxo, mesma persona, mesma superfície sem valor incremental).
3. **Dedup intra-lote:** mesclar | absorver (fase de plano existente, sem ID novo) | manter separados com `depends`.
4. **Dedup contra o existente:** `gh issue list --state all` + `issuesById()` + grep em `docs/plans/*.md` e `docs/roadmap.md` (legado).
4. **Dedup contra o existente:** `pnpm issue all` + `issuesById()` + grep em `docs/plans/*.md` e `docs/roadmap.md` (legado).
- Já coberto / entregue → apontar e não criar.
- Issue **`in-progress` / `done` / `in-prod`:** **não** editar o plano dela — se a intenção mudou, item **sucessor** (plano + Issue novos).
- Issue só `blocked` / `ready` (ainda não claimada): pode editar o plano existente (fase de plano) sem ID novo.
@ -150,7 +150,7 @@ Sem `--plan`: nasce `ready` (use `--blocked` só se quiser não-claimável sem p
2. Atualize `Issue: #N` (e status) no plano local.
3. Commit + **`pnpm push`** + PR **Ready** `--base main` com **`Related #N`** (nunca `Closes #N` em PR só de `docs/plans/` — `plans-only-closes`).
4. Auto-merge (`gh pr merge --auto --rebase`); espere o merge em `main`.
4. Auto-merge via safety net (`agent-pr-ready-automerge.yml` — o rollup `CI (PR) / checks` verde mergea por rebase); espere o merge em `main`.
5. **Promote** com o script (idempotente se já `ready`; só Issues `blocked` + link `docs/plans/`, sem gates humanos `needs:*` nem `in-progress`/`done`/`in-prod`):
```bash

View file

@ -1,11 +1,11 @@
---
name: project-status
description: Apresenta o estado do projeto Teqo a partir das GitHub Issues rastreáveis — overview (estado/prio/kind), fila atual na ordem do agent:claim com o modelo de cada item, grafo mermaid de dependências, bloqueios com motivo e sugestões de consolidação (merge/break-down). Read-only; NUNCA modifica Issues, em particular nunca toca Issues in-progress. Usar quando o usuário pedir status/visão geral do projeto, "como está o projeto", "qual a fila", "o que está bloqueado", "mostra o grafo", ou perguntar o que dá para consolidar.
description: Apresenta o estado do projeto Teqo a partir das Issues do Forgejo rastreáveis — overview (estado/prio/kind), fila atual na ordem do agent:claim com o modelo de cada item, grafo mermaid de dependências, bloqueios com motivo e sugestões de consolidação (merge/break-down). Read-only; NUNCA modifica Issues, em particular nunca toca Issues in-progress. Usar quando o usuário pedir status/visão geral do projeto, "como está o projeto", "qual a fila", "o que está bloqueado", "mostra o grafo", ou perguntar o que dá para consolidar.
---
# Status do projeto (read-only)
Esta skill apresenta o estado do projeto a partir das GitHub Issues rastreáveis — a fonte canônica de spec/status/deps/prio/modelo. **Read-only por contrato:** nenhuma Issue é criada, editada ou relabelada aqui, e Issues `in-progress` são intocáveis (risco de conflito com o agente que está executando).
Esta skill apresenta o estado do projeto a partir das Issues do Forgejo rastreáveis — a fonte canônica de spec/status/deps/prio/modelo. **Read-only por contrato:** nenhuma Issue é criada, editada ou relabelada aqui, e Issues `in-progress` são intocáveis (risco de conflito com o agente que está executando).
## Fluxo
@ -22,6 +22,6 @@ pnpm agent:status
- **Bloqueios** — cada `blocked` com o motivo (dep aberta, jurídico, `requirements-changed`).
- **Sugestões de consolidação** — as heurísticas do script (merge por domínio+prio; break-down por gargalo de depends) como **texto para o usuário decidir**. Nunca aplique consolidação aqui: aplicação é manual ou via `plan-issue`/`pnpm agent:register`. Nunca proponha nada sobre Issues `in-progress`.
3. Se o usuário pedir interpretação além da saída (ex.: "o que destrava mais coisa?"), responda a partir do grafo — sem inventar dados que o script não imprimiu; se faltar dado, rode `gh issue view <N>` somente-leitura.
3. Se o usuário pedir interpretação além da saída (ex.: "o que destrava mais coisa?"), responda a partir do grafo — sem inventar dados que o script não imprimiu; se faltar dado, leia a Issue via API do Forgejo (MCP/`pnpm issue`) somente-leitura.
**NÃO faz:** editar `docs/roadmap.md` (legado congelado), claim, registro, qualquer escrita no GitHub.
**NÃO faz:** editar `docs/roadmap.md` (legado congelado), claim, registro, qualquer escrita de Issue.

View file

@ -42,11 +42,12 @@ pular a pausa do impl plan; Draft / sem auto-merge.
## Passo 1 — Contexto da sessão
A Issue chega no prompt/`$ARGUMENTS` do `/work-issue` (`--issue <N>` ou o número —
contrato OPS33; chamada manual: passar o número). Sempre consulte o GitHub para o
contrato OPS33; chamada manual: passar o número). Sempre consulte o Forgejo para o
resto (fonte única é a Issue, nunca um brief duplicado).
- **Presente** → valide o básico (`gh issue view <N>`: existe, `OPEN`, label
`in-progress` — confirma que o claim foi feito fora da skill) e use.
- **Presente** → valide o básico (issue do Forgejo `git.solla.dev/fsolla/teqo`:
existe, `OPEN`, label `in-progress` — confirma que o claim foi feito fora da
skill) e use.
- **Ausente** → **uma** pergunta ao humano ("Qual Issue?"), valide o número
informado com a mesma checagem e siga.
- **Checagem falhou** (não existe / não `OPEN` / sem `in-progress`) → a Issue

View file

@ -35,10 +35,9 @@ entrega.
(insert-only: entradas históricas nunca mudam; o diff será só a entrada
nova + o agregado). Rode `pnpm changelog:check` para confirmar.
3. **`pnpm push -u origin HEAD`**
4. PR **Ready** (nunca draft): `gh pr create --base main` com `Closes #<N>`
5. `gh pr merge --auto --rebase` imediatamente
6. `gh pr checks <PR> --watch --required` (`checks`; ignore Vercel Git)
7. CI flipa `done`/`in-prod` no merge. Comente na Issue o desfecho em uma linha.
4. PR via API/MCP (ou `ManagePullRequest` no Cloud) — **Ready** (nunca draft), base `main`, com `Closes #<N>`
5. O safety net `agent-pr-ready-automerge.yml` espera o rollup `CI (PR) / checks` e mergea por rebase — nada a armar
6. CI flipa `done`/`in-prod` no merge. Comente na Issue o desfecho em uma linha.
## Deltas por ator

View file

@ -31,7 +31,7 @@ No opencode, isso é o comando **`/worktree next`** / **`/worktree kill`** (`.op
**Default-go:** `next`, `plan` e `new` imprimem `cd <dir>` na última linha **por padrão**; `--stay` suprime; `--go` explícito continua aceito como no-op. No terminal interativo, use a função `worktree()` de **`.agents/shell/worktree.sh`** (uma linha de `source` no profile) para o `cd` ser aplicado de verdade no shell que te chamou — node não consegue mudar o cwd do shell pai. `kill` também imprime `cd <main>` no fim (a sessão nunca fica num diretório destruído).
**Launch do opencode (OPS26 + OPS33 + OPS31):** só a função shell marca `TEQO_WORKTREE_TERMINAL=1`; com o marcador, `next`/`plan`/`new` imprimem também a diretiva `launch opencode <dir> --model deepseek/deepseek-v4-flash --auto [--prompt "/work-issue --issue <N>"]` **antes** do `cd`, e a função a executa depois do cd — `next` abre o TUI com `/work-issue --issue <N>` já enviado (a Issue claimada vai no prompt; a skill lê o resto do GitHub); `plan` abre com `/plan-issue` já enviado (OPS31: o TUI já cai no fluxo de planejamento); `new` abre sem `--prompt` ("apenas conversar"). Sem `exec`: sair do opencode volta ao shell no worktree. Presets são constantes em `scripts/lib/worktree.mjs`. **O comando `/worktree` do opencode (e qualquer automação sem o marcador) nunca lança o TUI.**
**Launch do opencode (OPS26 + OPS33 + OPS31):** só a função shell marca `TEQO_WORKTREE_TERMINAL=1`; com o marcador, `next`/`plan`/`new` imprimem também a diretiva `launch opencode <dir> --model deepseek/deepseek-v4-flash --auto [--prompt "/work-issue --issue <N>"]` **antes** do `cd`, e a função a executa depois do cd — `next` abre o TUI com `/work-issue --issue <N>` já enviado (a Issue claimada vai no prompt; a skill lê o resto do Forgejo); `plan` abre com `/plan-issue` já enviado (OPS31: o TUI já cai no fluxo de planejamento); `new` abre sem `--prompt` ("apenas conversar"). Sem `exec`: sair do opencode volta ao shell no worktree. Presets são constantes em `scripts/lib/worktree.mjs`. **O comando `/worktree` do opencode (e qualquer automação sem o marcador) nunca lança o TUI.**
## Fluxo quando invocado como skill (agentes que não têm opencode)

View file

@ -12,7 +12,7 @@ These are product/architecture constraints — not a ban on editing existing cod
- Single Next.js app, three route groups: `(frontend)` public site, `(payload)` admin, `(campaign)` internal campaign tool. No separate Rust service or frontend app for `/campanha`; it is modeled with Payload collections. The campaign roles are `coordinator` (label "Coordenador Geral"), `advisor` ("Assessor"), `leader` ("Liderança"), and `candidate` ("Candidato", unrestricted visibility), all on the separate auth collection `campaignUser` (see "Campaign auth" below). An advisor sees and manages ONLY the municipalities they administer; a leader is locked down to the supporter contact tool.
- **2026-07-23 remodel (DEPLOYED to production 2026-07-23): "Município" is the operational unit** (it replaced "Praça", which replaced "Núcleo Eleitoral"). Municípios are PREDEFINED (seeded, geography read-only): 435 rows — one per Bahia municipality (416), except Salvador (19 zone Municípios, ZE 1–19); Camaçari is a single whole municipality (ZE 170/171 aggregated); shared/leaky TSE zones are cut at the municipal boundary. Code identifier: `municipality`. Leaderships are one record per person (`contact` UNIQUE) linked to N municipalities and N organizations; vote pledges are per leadership×municipality with `declaredVotes` (staff-entered, leader-visible) vs `estimatedVotes` (three scenarios, staff-only, hidden from the leader). Campaign production data was reset by the remodel migrations (public-site data untouched). Master plan: `docs/plans/remodelagem-municipios.md` (supersedes `remodelagem-pracas.md`). Details: "Campaign Municípios model" below.
- Production is self-hosted on the homeserver (`jorgesolla1313.com.br`, behind the Cloudflare tunnel; container/database `teqo-1313`/`teqo_1313`). **There is no Vercel deploy anymore** — the Vercel deploy job, `vercel-promote.yml`, the Vercel scripts/`vercel.json` and the `VERCEL_*` repo secrets were removed in OPS50 (2026-08-17). The CI (`ci.yml` on `main`) is the verifier — full suite green — **plus the deploy step (OPS53, 2026-08-17; windowed since OPS65, 2026-08-18)**: `ci.yml` no longer fires on every push — it runs in a **fixed 30-minute window** (`schedule */30` + `workflow_dispatch`), where a cheap gate job (`check`, host runner, no pnpm) reads the SHA deployed on the homeserver from `~/stack/docker-compose.yml` over SSH, diffs it against `main` HEAD and classifies "production change" using `.dockerignore` as the source of truth (docs/`.agents`/`.env*` changes skip; `scripts/`, `tests/`, `.forgejo/`, `AGENTS.md` count as production — conservative). No production change → suite + deploy skipped, run stays green; production change → full suite + deploy in the same window; dispatch always runs the full pipeline. The `deploy` job (runs-on `host` on the workstation runner) streams `scripts/deploy-homeserver.sh` to the homeserver over SSH; the remote script is idempotent ("already deployed" → skip), builds the migrator image first, applies pending migrations through the compose maintenance service `teqo-1313-migrate` **before the runner build** (OPS66: the static build reads the new schema — build-after-migrate, never the reverse), builds the standalone image at the pushed SHA (BuildKit secrets from `~/stack/teqo-1313.env`), pushes it to the local registry (`localhost:5000`), and recreates the container. Merge em `main` com mudança de produção verde == site atualizado em até ~1 h (janela de 30 min + suite + deploy). Runbook (rollback, falhas conhecidas): `docs/ops/teqo-1313-deploy.md`. **The agent-pool supervisor workflow was killed in OPS65** (the `*/10` tick cost 144 full `pnpm install`/day on the workstation; `agent-pool.yml` is gone, pool scripts/skills stay dormant). **The production database lives on the homeserver Postgres (`teqo_1313`, migrated from Neon in OPS51, 2026-08-17)** — the full production content (contacts, signatures, subscriptions, campaign) was restored there via `pg_dump`/`pg_restore` (dump in `/srv/hdd/backups/teqo-neon-pre-migracao/`); Neon remains alive and untouched as the documented rollback (re-point `DATABASE_URL` in `~/stack/teqo-1313.env`). Media moved to the homeserver Garage S3 in OPS52 (2026-08-18): the `media` collection uploads via `@payloadcms/storage-s3` when the `S3_*` envs are set, and Payload proxies the files through `/api/media/file/...` (the bucket stays private). Production env lives in `~/stack/teqo-1313.env` (bucket `teqo-media`, endpoint `http://host.docker.internal:3900` — o container alcança o Garage no host via `extra_hosts: host-gateway` + regras ufw `allow from 10.0.11.0/24 to any port 3900` (INPUT e route/FWD); a workstation/tailnet usa `http://100.119.220.31:3900`, key `teqo-key`); a partial `S3_*` config aborts the boot, and no `S3_*` means local disk (dev/test). The 40 pre-existing media rows are RECOVERED (OPS52-media, 2026-08-18): 38/40 objects restaurados no bucket a partir do WordPress (38 capas servindo 200 em `/api/media/file/...`; exceções sem fonte: `fim-escala-6x1.jpg` e `jorgesolla.jpg`) — re-executar via `pnpm media:recover` (restaura objetos sem tocar no DB; runbook em `docs/plans/ops52-media-recuperar-arquivos-impl.md`; desde OPS52-media-guard o reconcile exige `MEDIA_RECOVER_CONFIRM=1` — guard de intenção explícita para escrita no bucket, `--dry-run`/`--verify` seguem sem flag); the URLs themselves are the same contract the proxy now serves. Never point dev/test databases at the homeserver prod DB either — the local guards (`guard-dev-db`, `assertTestDatabase`) still enforce `localhost`/`_test`-suffixed targets.
- Production is self-hosted on the homeserver (`jorgesolla1313.com.br`, behind the Cloudflare tunnel; container/database `teqo-1313`/`teqo_1313`). **There is no Vercel deploy anymore** — the Vercel deploy job, `vercel-promote.yml`, the Vercel scripts/`vercel.json` and the `VERCEL_*` repo secrets were removed in OPS50 (2026-08-17). The CI (`ci.yml` on `main`) is the verifier — full suite green — **plus the deploy step (OPS53, 2026-08-17; windowed since OPS65, 2026-08-18)**: `ci.yml` no longer fires on every push — it runs in a **fixed 30-minute window** (`schedule */30` + `workflow_dispatch`), where a cheap gate job (`gate`, host runner, no pnpm) reads the SHA deployed on the homeserver from `~/stack/docker-compose.yml` over SSH, diffs it against `main` HEAD and classifies "production change" using `.dockerignore` as the source of truth (docs/`.agents`/`.env*` changes skip; `scripts/`, `tests/`, `.forgejo/`, `AGENTS.md` count as production — conservative). No production change → suite + deploy skipped, run stays green; production change → full suite + deploy in the same window; dispatch always runs the full pipeline. The `deploy` job (runs-on `host` on the workstation runner) streams `scripts/deploy-homeserver.sh` to the homeserver over SSH; the remote script is idempotent ("already deployed" → skip), builds the migrator image first, applies pending migrations through the compose maintenance service `teqo-1313-migrate` **before the runner build** (OPS66: the static build reads the new schema — build-after-migrate, never the reverse), builds the standalone image at the pushed SHA (BuildKit secrets from `~/stack/teqo-1313.env`), pushes it to the local registry (`localhost:5000`), and recreates the container. Merge em `main` com mudança de produção verde == site atualizado em até ~1 h (janela de 30 min + suite + deploy). Runbook (rollback, falhas conhecidas): `docs/ops/teqo-1313-deploy.md`. **The agent-pool supervisor workflow was killed in OPS65** (the `*/10` tick cost 144 full `pnpm install`/day on the workstation; `agent-pool.yml` is gone, pool scripts/skills stay dormant). **The production database lives on the homeserver Postgres (`teqo_1313`, migrated from Neon in OPS51, 2026-08-17)** — the full production content (contacts, signatures, subscriptions, campaign) was restored there via `pg_dump`/`pg_restore` (dump in `/srv/hdd/backups/teqo-neon-pre-migracao/`); Neon remains alive and untouched as the documented rollback (re-point `DATABASE_URL` in `~/stack/teqo-1313.env`). Media moved to the homeserver Garage S3 in OPS52 (2026-08-18): the `media` collection uploads via `@payloadcms/storage-s3` when the `S3_*` envs are set, and Payload proxies the files through `/api/media/file/...` (the bucket stays private). Production env lives in `~/stack/teqo-1313.env` (bucket `teqo-media`, endpoint `http://host.docker.internal:3900` — o container alcança o Garage no host via `extra_hosts: host-gateway` + regras ufw `allow from 10.0.11.0/24 to any port 3900` (INPUT e route/FWD); a workstation/tailnet usa `http://100.119.220.31:3900`, key `teqo-key`); a partial `S3_*` config aborts the boot, and no `S3_*` means local disk (dev/test). The 40 pre-existing media rows are RECOVERED (OPS52-media, 2026-08-18): 38/40 objects restaurados no bucket a partir do WordPress (38 capas servindo 200 em `/api/media/file/...`; exceções sem fonte: `fim-escala-6x1.jpg` e `jorgesolla.jpg`) — re-executar via `pnpm media:recover` (restaura objetos sem tocar no DB; runbook em `docs/plans/ops52-media-recuperar-arquivos-impl.md`; desde OPS52-media-guard o reconcile exige `MEDIA_RECOVER_CONFIRM=1` — guard de intenção explícita para escrita no bucket, `--dry-run`/`--verify` seguem sem flag); the URLs themselves are the same contract the proxy now serves. Never point dev/test databases at the homeserver prod DB either — the local guards (`guard-dev-db`, `assertTestDatabase`) still enforce `localhost`/`_test`-suffixed targets.
- Donations are NOT handled in this app — they run through QueroApoiar (`apoiar.me/jorgesolla`, TSE-homologated). The site only needs a "Doar" link/CTA out to that URL.
- **Embasamento de produto/domínio:** `docs/research/` guarda o compêndio de literatura (campanha de DF na Bahia, geografia do voto, cartografia eleitoral) e o relatório de discovery aprovado (2026-07-21) com o playbook dado→decisão. Para métricas, análises territoriais, mapa e priorização em `/campanha`, consulte-o antes de inventar — o kernel: a disputa de DF é conta de quociente fragmentada; leitura útil é relativa e local (captura, LQ, quantis, cobertura de meta por pledges), nunca % estadual absoluto.
@ -27,9 +27,9 @@ These are product/architecture constraints — not a ban on editing existing cod
- i18n: Payload admin defaults to `pt` (not `pt-BR` — see `payload.config.ts`, `payload/i18n/pt`).
- Language & naming conventions: ALL code identifiers are in English — variable, function, parameter, type/interface names, local constants, and Next.js dynamic route segment folder names used as param keys (e.g. `[type]/[category]/[slug]`, not `[tipo]/[categoria]`). Portuguese is allowed ONLY in: user-visible string literals (JSX text, button/labels), image `alt` text, SEO/metadata values (title/description/keywords), Payload admin config text (`labels`/`singular`/`plural`/`admin.description`/field `label`), and intentional URL slug/enum VALUES kept in Portuguese for SEO (e.g. the `post.type` enum values `noticia|campanha|artigo|evento`, tag/category slugs like `saude`, `eleitoral`). Never translate those data/URL values or admin labels. Renaming a route segment folder changes only the param KEY, never the public URL (segment values come from the data).
**Database & local development (safety-critical — production is a live Neon Postgres with real citizens' PII):**
**Database & local development (safety-critical — production is the homeserver Postgres `teqo_1313` with real citizens' PII):**
- Local dev and tests must NEVER touch production. Local dev runs against a local Docker Postgres (`pnpm db:start`); `pnpm dev` refuses a non-local `DATABASE_URL` (guard: `scripts/guard-dev-db.mjs`) unless `ALLOW_REMOTE_DB=true`. Tests load `.env.test` (`teqo_test`) and refuse any database whose name doesn't end in `_test` (`tests/helpers/assertTestDatabase.ts`). Never repoint `.env`/`.env.local`/`.env.test` `DATABASE_URL` at Neon. Full workflow lives in the `local-database` skill (`.agents/skills/local-database`).
- Local dev and tests must NEVER touch production. Local dev runs against a local Docker Postgres (`pnpm db:start`); `pnpm dev` refuses a non-local `DATABASE_URL` (guard: `scripts/guard-dev-db.mjs`) unless `ALLOW_REMOTE_DB=true`. Tests load `.env.test` (`teqo_test`) and refuse any database whose name doesn't end in `_test` (`tests/helpers/assertTestDatabase.ts`). Never repoint `.env`/`.env.local`/`.env.test` `DATABASE_URL` at the homeserver prod DB (`teqo_1313`). Full workflow lives in the `local-database` skill (`.agents/skills/local-database`).
- **`pnpm dev` honra o `PORT` do `.env.local` (OPS40):** `next dev` resolve a porta ANTES de `@next/env` carregar os env-files (commander `.env('PORT')` no `bin/next`), então o `PORT=3100+slot` do provisionador de worktree era ignorado e todo dev server subia em 3000. `pnpm dev` hoje passa por `scripts/dev.mjs`: roda o mesmo preflight do `guard-dev-db`, carrega `.env.local` → `.env` (`override:false` — env real do processo vence, como o `PORT` que o webServer do Playwright injeta) e entrega a porta ao `next dev` como **flag CLI `-p`** (`resolveDevPort`/`nextDevArgs` em `scripts/lib/cli.mjs`, unit-testados); sem `PORT` em lugar nenhum, nenhum `-p` é passado e o default 3000 (com `allowRetry`) do Next segue intacto no main repo.
- Schema changes go through committed Payload migrations — `push: false` everywhere, never flip it on against a remote DB. Edit configs → `pnpm migrate:create <name>` → commit `src/migrations/*` (both `.ts` and `.json`, plus `index.ts`) → `pnpm migrate` locally. `pnpm build` runs `payload migrate`, so **every production deploy applies pending migrations to prod**. Prod is baselined at migration `20260715_163458_initial` — never regenerate or replace the initial migration. Full workflow (incl. hand-written data/reconciliation migrations) lives in the `payload-migrations` skill (`.agents/skills/payload-migrations`).
- `Consent.text` was reconciled from `varchar` to `jsonb` via `20260715_163500_consent_text_to_jsonb` (matching the `richText` field). Any future field-type change is a migration, not a manual DB edit.
@ -95,7 +95,7 @@ The internal `/campanha` area is gated by its own authentication, deliberately k
- **Route layout.** The route group's root layout `src/app/(campaign)/layout.tsx` renders the `<html>`/`<body>` with `data-theme="campaign"`. The public login lives at `src/app/(campaign)/campanha/login/` (`page.tsx` + client `LoginForm.tsx`). Everything gated sits in the `(app)` group, whose layout (`src/app/(campaign)/campanha/(app)/layout.tsx`) calls `getCampaignUser()` and `redirect('/campanha/login')` when there is no session — that layout is the barrier.
- **Biometric login (WebAuthn, B40 — 2026-07-28).** A second way into the same session, never a second session: the biometric path ends at the same `setCampaignAuthCookie` with the 14-day TTL (enrolling the device _is_ the trust opt-in), and the password path is untouched. Credentials live in `campaignWebAuthnCredential` (owner-only access in `src/utilities/access/webauthnCredentials.ts`; `update` is denied to users because the signature counter and `lastUsedAt` are written with `overrideAccess: true` inside the transaction). Four anonymous-or-authenticated JSON routes sit in `src/app/(campaign)/campanha/webauthn/` — deliberately **outside `(app)`**, since two of them run before there is a session and the cookie's `/campanha` path already covers them. **No `Consent` key, and this is not an art. 11 question:** the OS never releases the fingerprint, so what we store is a public key.
- **There is no password-less login in Payload 3.82.0.** `loginOperation` requires `password`. `issueCampaignWebAuthnSession` (`src/utilities/campaignWebAuthnSession.ts`) mints the session from public exports only: `payload.db.findOne` → `checkLoginPermission` → `addSessionToUser` (`payload/shared`) → `getFieldsToSign` + `jwtSign`. **Never load the user with `payload.findByID` here.** `addSessionToUser` read-modify-writes the _whole_ user document, so a doc that passed through `CampaignUser`'s `afterRead` `removePrivateAuthFields` would be written back without `sessions` or `email` — logging the user out of every other device — and with `avatar` expanded into an object. `user.updatedAt = null` is set for the same reason Payload's own login does it. The mint runs inside `withPayloadTransaction` under `campaign-webauthn-session:{userId}`, because rewriting the whole `sessions` array makes two concurrent logins lose one.
- **RP ID must match the host that served the page.** `getCampaignWebAuthnRelyingParty` derives `rpID`/`origin` from `getCampaignInviteBaseURL`, _and_ compares the request's host against it — returning `null` when they differ, which hides the button. Without that comparison a Vercel preview resolves the canonical origin successfully and the browser throws `SecurityError` mid-ceremony.
- **RP ID must match the host that served the page.** `getCampaignWebAuthnRelyingParty` derives `rpID`/`origin` from `getCampaignInviteBaseURL`, _and_ compares the request's host against it — returning `null` when they differ, which hides the button. Without that comparison a preview served from a different host resolves the canonical origin successfully and the browser throws `SecurityError` mid-ceremony.
- **The challenge cookie must carry the library's own challenge.** It is an HMAC-signed httpOnly cookie (5 min, single-use, path `/campanha`, precedent `supporterImportToken.ts`) — but store the exact base64url string `generateRegistrationOptions`/`generateAuthenticationOptions` returned. Generating our own alongside theirs makes every `register` answer 400.
- **Deleting a `campaignUser` needs the cascade in a hook.** Payload emits `ON DELETE SET NULL` for the required `user` relationship, which cannot apply to a `NOT NULL` column; `CampaignUser` has a `beforeDelete` that removes the user's credentials in the same transaction. E2E uses a CDP virtual authenticator (`WebAuthn.addVirtualAuthenticator`, `transport: 'internal'`), which also makes `isUserVerifyingPlatformAuthenticatorAvailable()` answer `true`.
- **Migrations.** `20260716_010420_add_campaign_user` created the auth collection; `20260728_041958_add_campaign_webauthn_credentials` added the WebAuthn credential table; the unpublished 2026-07-17 campaign chain was consolidated into `20260718_010733_consolidate_campaign_schema`, which adds role/username auth, contact phone, and the remaining final campaign schema.
@ -144,7 +144,7 @@ Static Bahia map foundation for future Leaflet surfaces (roadmap B2; UI is B3).
- **Runtime helpers:** `src/lib/bahiaGeometries.ts` — typed topologies, feature arrays and `getMunicipalityFeature` (the territory and zone accessors were deleted by B8+: only the int specs used them, and a `Map` built in one line is what a spec wants). TopoJSON→GeoJSON decode is LAZY (dynamic import via `loadMunicipalityGeometryModule` / `loadMunicipalityZoneGeometryModule`; B5 F1 done) — the default `/campanha` server graph never pays it. Shared feature types live in `bahiaGeometriesTypes.ts`: `BahiaFeature<P>` is the one spelling of `Feature<Polygon | MultiPolygon, P>`, `BahiaMeshFeature` is what the map layer receives from any of the three meshes, and geometry math (point-in-polygon, centroid, area, bounds — all built on the exported `polygonRingsOf`) takes **`PolygonalFeature`**, which declares the actual requirement instead of dragging `GeoJsonProperties` (i.e. `any`) through the signature.
- **Territory polygons:** dissolved from IBGE municipality meshes via `bahiaIdentityTerritoryRecords` (`topojson` `merge`), not the IDE Bahia shapefile (IDE remains provenance/validation reference only).
- **Rebuild (Salvador zones):** `pnpm build:municipality-zone-geometries` (`scripts/build-municipality-zone-geometries.mjs`; `--report` reconciles without writing) downloads IBGE `BA_bairros_CD2022.zip` from geoftp, parses the shapefile with **`shpjs`** (devDependency — that cut is SHP-only, SIRGAS 2000), keeps `CD_MUN === '2927408'` (170 polygons) and dissolves neighbourhood→zone against **TRE-BA RA 02/2017** (`municipalityZoneNeighborhoods.ts`). Reconciliation is **curated data in the script header**, not cleverer matching: an alias table, a table for TRE neighbourhoods with no polygon of their own (the build refuses a merge that would cross zones), and a table for the one neighbourhood the resolution splits mid-street (it goes whole to one ZE — the neighbourhood is the atom). The 5 polygons the resolution never names are assigned by **longest shared boundary**, a rule validated against the 4 it names under another label. The build fails on an orphan polygon or a TRE neighbourhood with no polygon, so 170/170 → 19/19 is an invariant, not a hope. Same cache dir and the same "**does not touch the database**" guarantee as the sibling below.
- **Rebuild:** `pnpm build:geometries` (`scripts/build-bahia-geometries.mjs`) downloads IBGE Malhas (qualidade intermediaria) + Localidades, reconciles names with `canonicalizeMunicipalityName`, simplifies/quantizes, emits the artifacts above. Cache under `data/geometries/` (gitignored; override with `GEOMETRIES_CACHE_DIR`). **Does not touch the database** — no `assertLocalDatabase` / no Neon risk. Not part of `pnpm build` or `pnpm dev`. Details: `docs/plans/mapa-bahia-geometrias.md`. Scale/DRY follow-ups from `/simplify`: `docs/plans/escala-dry-pos-b2.md` (B5).
- **Rebuild:** `pnpm build:geometries` (`scripts/build-bahia-geometries.mjs`) downloads IBGE Malhas (qualidade intermediaria) + Localidades, reconciles names with `canonicalizeMunicipalityName`, simplifies/quantizes, emits the artifacts above. Cache under `data/geometries/` (gitignored; override with `GEOMETRIES_CACHE_DIR`). **Does not touch the database** — no `assertLocalDatabase` / no prod-DB risk. Not part of `pnpm build` or `pnpm dev`. Details: `docs/plans/mapa-bahia-geometrias.md`. Scale/DRY follow-ups from `/simplify`: `docs/plans/escala-dry-pos-b2.md` (B5).
## Election baseline data (TSE 2022)
@ -156,7 +156,7 @@ The artifact is at **v3**: v2 added E8's `campoFederalVotesByYear` / `federalTal
### Local verification and deploy checklist
1. Confirm `DATABASE_URL` is `postgresql://teqo:teqo@localhost:5432/teqo` and `.env.test` points to `teqo_test`; never use Neon for development, tests, E2E, or local builds. In a provisioned worktree (`pnpm worktree next`) the values come from its `.env.local` / `.env.test.local` (`teqo_wt<slot>` / `teqo_wt<slot>_test`, dev port `3100+slot`) — read them there instead of assuming the main-repo names.
1. Confirm `DATABASE_URL` is `postgresql://teqo:teqo@localhost:5432/teqo` and `.env.test` points to `teqo_test`; never use the homeserver prod DB for development, tests, E2E, or local builds. In a provisioned worktree (`pnpm worktree next`) the values come from its `.env.local` / `.env.test.local` (`teqo_wt<slot>` / `teqo_wt<slot>_test`, dev port `3100+slot`) — read them there instead of assuming the main-repo names.
2. Run `pnpm db:start`, `pnpm migrate:status`, and `DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test pnpm migrate:status`; every migration must show `Ran: Yes`.
3. Run `pnpm generate:types`, `pnpm generate:importmap` when components changed, `pnpm exec tsc --noEmit`, `pnpm lint` (zero warnings — the script enforces `--max-warnings=0`), `pnpm format:check` (Prettier is the formatting authority; `pnpm format` fixes), `pnpm exec knip` (dead files/dependencies are CI-blocking; delete what your change orphaned), `pnpm check:cycles` (madge — zero circular imports), `pnpm test` (unit + int), and `pnpm build` against the local database. Payload CLI scripts (`migrate`, `generate:*`, `payload`) and the build migrate step use `--conditions=react-server` so `server-only` modules load correctly outside Next.js. **O e2e não roda mais no gate local (OPS59):** ele roda **uma vez, no CI** (PR e main — gateia merge e deploy); local opcional para feedback antecipado: `pnpm test:e2e:affected` (gera `--no-deps` para runs filtrados — S6-FOLLOWUP: no dev mode a cadeia de dependencies dos projects arrasta os arquivos das dependências via closure; filtros manuais locais: `pnpm test:e2e --no-deps -- <path>` ou `--no-deps --project=<família>`).
- Never run `knip --fix` blind: when knip's module graph is incomplete (it still cannot load `payload.config.ts` — ledgered as P3), `--fix` deletes exports that migrations/tests still reference. Verify each removal (e.g. `git grep -w <symbol>`) before deleting.

View file

@ -6,16 +6,16 @@
> **Cheatsheet de operação agentic (humanos)** — o fluxo em 5 linhas:
>
> 1. O agente roda `pnpm agent:claim` e pega a próxima Issue `ready` por prioridade (`prio:P0..P3`).
> 2. Ele implementa, roda o fast gate (`lint + typecheck + unit`) e abre **PR para `main`** — sozinho.
> 3. CI verde → o PR mergeia em `main` automaticamente; o verificador `ci.yml` roda a suíte completa.
> 4. Suíte verde → Action dispara `vercel deploy --prod` (Vercel Git builds estão desligados).
> 5. Secrets humanos (uma vez): `FORGEJO_API_TOKEN`, `CURSOR_API_KEY`, `POOL_GITHUB_TOKEN`; `pnpm configure:branch-protection` (main: required `CI (PR) / checks`).
> 1. O agente roda `pnpm agent:claim` (ou `pnpm worktree next --issue N`) e pega a próxima Issue `ready` por prioridade (`prio:P0..P3`).
> 2. Ele implementa, roda o fast gate (`lint + typecheck + unit`) e `pnpm push -u origin HEAD` — abre a PR para `main` (Ready, `Closes #N`).
> 3. CI verde → o safety net `agent-pr-ready-automerge.yml` mergea em `main` por rebase; o verificador `ci.yml` do main roda a suíte completa na janela fixa de 30 min.
> 4. Com mudança de produção → o job `deploy` publica no homeserver (`jorgesolla1313.com.br`, self-hosted) — migrações aplicadas antes do rollout.
> 5. Secrets humanos (uma vez): `FORGEJO_API_TOKEN` no Forgejo; envs de prod em `~/stack/teqo-1313.env` no homeserver. Branch protection de `main` já aplicada (reaplicar: `pnpm configure:branch-protection`).
>
> Comandos: `pnpm agent:claim | agent:register | agent:prioritize | agent:file-miss | agent:pool` e `pnpm db:seed:minimal`.
> Comandos: `pnpm agent:claim | agent:register | agent:prioritize | agent:file-miss | worktree next` e `pnpm db:seed:minimal`.
> Labels: estado `ready|in-progress|blocked|done|in-prod`, `prio:P0..P3`, `kind:*`, `needs:migration|consent`, `requirements-changed`.
> **Agente faz sozinho:** claim → implementar → PR → main. **Só humano:** secrets Vercel/pool, branch protection, envs Neon.
> Tudo em detalhe: [`docs/AGENT-OPS.md`](docs/AGENT-OPS.md) · CI: `.github/workflows/ci-pr.yml` + `ci.yml`.
> **Agente faz sozinho:** claim → implementar → PR → main. **Só humano:** envs do homeserver, branch protection, runbook de rollback.
> Tudo em detalhe: [`docs/AGENT-OPS.md`](docs/AGENT-OPS.md) · CI: `.forgejo/workflows/ci-pr.yml` + `ci.yml`.
Teqo starts as the official digital platform for **deputado Jorge Solla** and evolves into a **white-label civic engagement platform** for politicians in Brazil.
@ -35,7 +35,7 @@ Initial delivery focuses on Jorge Solla's public website, editorial CMS, and the
- Public-facing content and updates (news/`post` + `tag` live)
- Institutional pages and biography (still pending a `Pages` collection)
- Internal campaign operations: electoral nuclei, leaderships, estimates, updates, WhatsApp invites
- Internal campaign operations: municípios (435, pré-definidos), leaderships, estimates, updates, WhatsApp invites
- Editorial operations through Payload CMS
### Phase 2: White-Label Platform
@ -49,7 +49,7 @@ Teqo then becomes a configurable base product for other politicians in Brazil:
## Local Development
> **Production safety:** the production database is a live Neon Postgres holding real citizens' data. Local development and tests run against a **local** Postgres and must never point at production. `pnpm dev` refuses to start against a non-local database, and the test suite refuses any database whose name doesn't end in `_test`.
> **Production safety:** the production database is a live Postgres on the homeserver (`teqo_1313`) holding real citizens' data. Local development and tests run against a **local** Postgres and must never point at production. `pnpm dev` refuses to start against a non-local database, and the test suite refuses any database whose name doesn't end in `_test`.
### First-time setup
@ -62,8 +62,8 @@ Teqo then becomes a configurable base product for other politicians in Brazil:
DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo
```
5. Load a schema + content into the local database — pick one:
- **Mirror production content (recommended):** `PROD_DATABASE_URL="<unpooled Neon URL>" pnpm db:pull`
Copies production's schema and content into local. It only reads production and **excludes supporter PII** (`contact`/`signature`/`subscription` data).
- **Mirror production content (recommended):** `PROD_DATABASE_URL="<prod connection string>" pnpm db:pull`
Copies production's schema and content into local. It only reads production and **excludes supporter PII** (`contact`/`signature`/`subscription` data). The prod string is the `DATABASE_URL` of `~/stack/teqo-1313.env` on the homeserver (Postgres `teqo_1313`).
- **Empty schema from migrations:** `pnpm migrate`
6. Start the dev server: `pnpm dev`
7. Open: `http://localhost:3000`
@ -84,7 +84,7 @@ curl -X POST "https://<prod-domain>/api/revalidate" -H "x-revalidate-secret: $RE
curl -X POST "https://<prod-domain>/api/revalidate?tag=global_privacy-policy" -H "x-revalidate-secret: $REVALIDATE_SECRET"
```
`REVALIDATE_SECRET` must be set in the Vercel production env (see `.env.example`). See the "Posts & Tags" section of `AGENTS.md` for the full model.
`REVALIDATE_SECRET` must be set in the production env (`~/stack/teqo-1313.env` on the homeserver — see `.env.example`). See the "Posts & Tags" section of `AGENTS.md` for the full model.
### Database migrations
@ -97,7 +97,7 @@ To change the schema:
3. Review and commit the generated files in `src/migrations/` (both `.ts` and `.json`, plus `index.ts`).
4. Apply it locally: `pnpm migrate` (check status anytime with `pnpm migrate:status`).
**Deploying to production:** `pnpm build` runs `payload migrate` before building, so every Vercel deploy automatically applies pending migrations to the production database. Do not run migrations against production by hand.
**Deploying to production:** merges to `main` with production changes are deployed by the `deploy` job of `ci.yml` (windowed — see `docs/AGENT-OPS.md`); the remote script (`scripts/deploy-homeserver.sh`) builds on the homeserver and applies pending migrations through the compose maintenance service `teqo-1313-migrate` **before** the rollout. Do not run migrations against production by hand. Runbook (rollback, known failures): `docs/ops/teqo-1313-deploy.md`.
### Running tests
@ -113,7 +113,7 @@ Then run:
- `pnpm test:e2e` — Playwright (requires the test DB schema and a free port)
- `pnpm test:all` — unit + integration + E2E
Quality gates (also enforced by CI on the GitHub mirror): `pnpm lint` (zero warnings — `--max-warnings=0`), `pnpm typecheck`, and `pnpm exec knip` (dead files/dependencies fail; delete what your change orphaned). Standards: `.agents/rules/engineering-standards.mdc`.
Quality gates (also enforced by CI on the Forgejo Actions): `pnpm lint` (zero warnings — `--max-warnings=0`), `pnpm typecheck`, and `pnpm exec knip` (dead files/dependencies fail; delete what your change orphaned). Standards: `.agents/rules/engineering-standards.mdc`.
## Tech Stack
@ -124,10 +124,10 @@ Quality gates (also enforced by CI on the GitHub mirror): `pnpm lint` (zero warn
## Campaign (`/campanha`)
Internal campaign tool for electoral nuclei, local leaderships, vote estimates, field updates, and WhatsApp invites. Authenticated separately from `/admin` via the `campaignUser` collection (`geral` / `coordenador` / `lideranca`). Operational status and decisions: [`.agents/rules/projects/nucleos-eleitorais.mdc`](.agents/rules/projects/nucleos-eleitorais.mdc). Conventions and deploy checklist: `AGENTS.md` (“Campaign auth” / “Campaign nuclei MVP”).
Internal campaign tool for municípios (435 pré-definidos), local leaderships, vote estimates, field updates, and WhatsApp invites. Authenticated separately from `/admin` via the `campaignUser` collection (`coordinator` / `advisor` / `leader` / `candidate`). Operational status and decisions: [`.agents/rules/projects/nucleos-eleitorais.mdc`](.agents/rules/projects/nucleos-eleitorais.mdc). Conventions and deploy checklist: `AGENTS.md` (“Campaign auth” / “Campaign Municípios model”).
**Production blocker:** do not load real leadership data or enable invites until counsel-approved `Consent.key = 'lideranca-autopreenchimento'` exists. Absolute invite URLs require `NEXT_PUBLIC_SITE_URL` as an exact HTTPS DNS origin in production.
## Roadmap
Backlog and future plans live as tracked [GitHub Issues](https://github.com/fsolla/teqo/issues) (spec + deps + prio + model per issue; flow skills: `plan-issue` → `work-issue` → `project-status`; `docs/roadmap.md` is a frozen legacy stub).
Backlog and future plans live as tracked [Forgejo Issues](https://git.solla.dev/fsolla/teqo/issues) (spec + deps + prio + model per issue; flow skills: `plan-issue` → `work-issue` → `project-status`; `docs/roadmap.md` is a frozen legacy stub).

View file

@ -1,6 +1,6 @@
# AGENT-OPS — operação do paradigma de agentes paralelos
Uma página. Norte: simplicidade (só GitHub + Cursor), agente entrega em `main`, deploy de produção só após o verificador full-suite verde.
Uma página. Norte: simplicidade (só Forgejo + Cursor), agente entrega em `main`, deploy de produção só após o verificador full-suite verde.
## Ambientes de dados (test ladder — nunca misturar)
@ -9,7 +9,7 @@ Uma página. Norte: simplicidade (só GitHub + Cursor), agente entrega em `main`
| **Mínimo** (`teqo_test` local / service CI) | schema migrado + `pnpm db:seed:minimal` (sintético, sem PII) | agentes, Cursor Cloud, CI |
| **Prod** (`teqo_1313`, homeserver) | real | job `deploy` do `ci.yml` (OPS53) |
Agentes **nunca** recebem `DATABASE_URL` de prod e nunca setam `ALLOW_REMOTE_DB`. Não há mais smoke Neon `stage` / `ALLOW_STAGE_TEST_DB` / `ci-stage.yml`.
Agentes **nunca** recebem `DATABASE_URL` de prod e nunca setam `ALLOW_REMOTE_DB`. Não há mais smoke `stage` / `ALLOW_STAGE_TEST_DB` / `ci-stage.yml`.
## Fluxo
@ -18,10 +18,10 @@ claim → feature branch → PR --base main → CI PR green (cascade + skips)
main → ci.yml (janela de 30 min, OPS65) full suite verde → job `deploy` (homeserver, OPS53 — merge com mudança de produção == site atualizado em ~30 min + suite/deploy)
```
**Skills:** `plan-issue` (intenção + Issues) → `work-issue` (humano: Issue já claimada → impl plan → confirmação → execução) ou `agent-work-issue` (pool: já claimada → impl plan → execução sem pausa) → `/simplify` → `capture-review-debts` → PR `--base main` → `project-status`. `docs/roadmap.md` = legado congelado; fonte canônica = GitHub Issues.
**Skills:** `plan-issue` (intenção + Issues) → `work-issue` (humano: Issue já claimada → impl plan → confirmação → execução) ou `agent-work-issue` (pool: já claimada → impl plan → execução sem pausa) → `/simplify` → `capture-review-debts` → PR `--base main` → `project-status`. `docs/roadmap.md` = legado congelado; fonte canônica = Issues do Forgejo (`git.solla.dev/fsolla/teqo`).
- **Agente faz sozinho:** claim (pool-supervisor no pool; humano via `pnpm agent:claim` fora da sessão — OPS33: `worktree next` claima) → implementa → **`pnpm push`** → PR **Ready** (nunca draft) com `Closes #N` → `gh pr merge --auto --rebase` → `gh pr checks --watch --required`. Regra always-on: `.agents/rules/agent-pr-workflow.mdc`. Em Cursor Cloud: `ManagePullRequest` com `draft: false`, depois armar auto-merge via `gh pr merge --auto --rebase` (o default draft da tool **não** vale neste repo).
- **Só humano:** secrets Vercel (`VERCEL_TOKEN`, `VERCEL_ORG_ID`, `VERCEL_PROJECT_ID`), `POOL_GITHUB_TOKEN`, `pnpm configure:branch-protection`, editar envs Neon/Vercel.
- **Agente faz sozinho:** claim (pool-supervisor no pool; humano via `pnpm agent:claim` fora da sessão — OPS33: `worktree next` claima) → implementa → **`pnpm push`** → PR via API/MCP **Ready** (nunca draft) com `Closes #N` → o safety net `agent-pr-ready-automerge.yml` espera o rollup `CI (PR) / checks` e mergea por rebase. Regra always-on: `.agents/rules/agent-pr-workflow.mdc`. Em Cursor Cloud: `ManagePullRequest` com `draft: false` (o default draft da tool **não** vale neste repo) — o merge continua pelo safety net.
- **Só humano:** envs de produção no homeserver (`~/stack/teqo-1313.env`, fora do repo — o deploy as lê via BuildKit secrets), `pnpm configure:branch-protection` (idempotente; reaplicar se o drift voltar), runbook manual/rollback (`docs/ops/teqo-1313-deploy.md`).
### Dono do PR, dono do CI
@ -56,7 +56,7 @@ Labels: `ready|in-progress|blocked|done|in-prod`, `prio:*`, `kind:*`, `needs:*`.
1. Schema → atualizar `db:seed:minimal` no mesmo PR (`needs:migration`).
2. Migrations NÃO são serializadas entre PRs: o CI (migrate + int, incl. `campaignMigrationReconciliation`) valida a cadeia em todo PR e na main; rebase antes de `migrate:create` continua obrigatório.
3. Ready + auto-merge: `gh pr create --base main` → `gh pr merge --auto --rebase`; `gh pr checks --watch --required` (Vercel Git não é gate). **Merge é por rebase** (nunca merge commit): preserva a linha histórica limpa do `main` e é o modo que o CI guarda contra conflitos latentes.
3. Ready + auto-merge: `pnpm push -u origin HEAD` → PR via API/MCP (Ready, base `main`, `Closes #N`) → o safety net `agent-pr-ready-automerge.yml` (`scripts/forgejo-pr-automerge.mjs`) espera o rollup `CI (PR) / checks` e mergea por rebase — não há `gh` em nenhuma máquina (OPS50), o safety net é quem espera os checks. **Merge é por rebase** (nunca merge commit): preserva a linha histórica limpa do `main` e é o modo que o CI guarda contra conflitos latentes.
4. **Changelog da entrega (OPS44):** toda entrega escreve `docs/changelog/<data>-<id>.md` (ex. `2026-08-13-ops44.md`) e roda `pnpm changelog:build` antes do push — o agregado `docs/CHANGELOG-AGENTS.md` é **insert-only** (nunca remove/reescreve entradas; entradas históricas não são migradas). Guard de CI `docs-guards`: (a) agregado é append-only (multiset sobre blobs — perda de linha falha); (b) `docs/changelog/` é additions-only (M/D falham); (c) agregado está up to date com `docs/changelog/` (`changelog:check`); (d) nenhum marcador de conflito em diffs de markdown (docs/, AGENTS.md, .agents/ — cobre PRs docs-only, onde a suíte unit não roda). **Desde OPS63 os mesmos três checks rodam também no pre-push local** (`gate:push` → `gate:ci`, fase 1 — mesmos scripts, default `origin/main`). Restauração legítima (header muda, restauração estilo D8) escapa escrevendo **`changelog-rewrite: <motivo>` como linha própria** no body do PR — definição canônica aqui; o texto do checkbox do PR template nunca ativa (linha-ancorado). **O escape é CI-only (body do PR):** no pre-push local a restauração legítima falha com a mesma mensagem do CI — bypass documentado é `git push --no-verify` direto (o CI, com o escape no body, continua sendo a via da restauração). A própria troca `--merge` → `--rebase` deste doc usou o escape — header do agregado mudou.
## Registries compartilhados (serialização por arquivo, não por PR)
@ -76,6 +76,7 @@ Arquivos de "lista" que N agentes escrevem em paralelo (changelog, plans, migra
| `issue-done-on-main-merge.yml` | PR merged → `main` (same-repo) | — | `Closes`/`Fixes` → `done` + `in-prod` (PAT `FORGEJO_API_TOKEN`; `GITHUB_TOKEN` 403 em Issues) |
| `plan-issue-ready-on-main-merge.yml` | PR merged → `main` (same-repo) | — | `Related #N` aguardando plano → `ready` (OPS18; soft-skip; mesmo PAT) |
| `agent-pr-ready-automerge.yml` | PR same-repo → `main` (open/sync/…) | — | Safety net via `forgejo-pr-automerge.mjs` (plain Node): espera o rollup `checks` e mergea por rebase com `FORGEJO_API_TOKEN` (o `GITHUB_TOKEN` nativo não tem write no repo — o push do merge é rejeitado com 409); draft `cursor/*` → Ready; draft não-`cursor/*` = veto, skip |
| `archive-cursor-agent.yml` | dispatch manual (ops) | — | Helper: cancela run + arquiva um agente Cursor Cloud (`CURSOR_API_KEY`) — **dormente (OPS65)** como o pool |
`agent-pool.yml` foi **removido no OPS65** (pool dormente; o tick `*/10` custava 144 `pnpm install`/dia na workstation).
@ -85,11 +86,12 @@ Fast gate: `pnpm gate:fast`. Push: `pnpm push`.
### Secrets (humano, uma vez)
| Secret | Uso |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FORGEJO_API_TOKEN` | Supervisor do pool e scripts Forgejo (`forgejo-api.mjs`); também nos workflows pós-merge (`issue-done`, `plan-issue-ready`) — o `GITHUB_TOKEN` nativo 403 em Issues (OPS61) |
| `CURSOR_API_KEY` | Supervisor do pool (Cursor Cloud) |
| `POOL_GITHUB_TOKEN` | PAT `actions:write` + `issues:write` (variables do pool; `GITHUB_TOKEN` costuma 403) |
| Secret | Uso |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FORGEJO_API_TOKEN` | Supervisor do pool e scripts Forgejo (`forgejo-api.mjs`); também nos workflows pós-merge (`issue-done`, `plan-issue-ready`) e no safety net — o `GITHUB_TOKEN` nativo 403 em Issues e não tem write no repo (OPS61) |
| `CURSOR_API_KEY` | Supervisor do pool (Cursor Cloud) — **dormente (OPS65)**; só o helper `archive-cursor-agent.yml` a usa hoje |
`POOL_GITHUB_TOKEN` é **legado** (era da transição GitHub): o `scripts/agent-pool.mjs` o ignora quando presente; não criar no Forgejo. Envs de produção (DB, S3, VAPID, `REVALIDATE_SECRET`, `NEXT_PUBLIC_SITE_URL`) vivem em `~/stack/teqo-1313.env` no homeserver — nunca como secrets do Forgejo.
Branch protection de `main` (aplicada em 2026-08-18, OPS61): required status check `CI (PR) / checks` (o rollup do `ci-pr.yml` — único contexto que cobre a cascata inteira), `strict=false`, 0 reviews. O `waitForChecks` do safety net espera esse mesmo rollup antes de mergear (o próprio status do job é `pending` durante o run e nunca entra no veredito); a regra no servidor é o gate real — o Forgejo bloqueia o merge POST com 405 se o contexto exigido não estiver verde, inclusive merges manuais via API (a PR #52 entrou em main com CI vermelho por merge manual). Reaplicar/consertar drift: `pnpm configure:branch-protection` (idempotente).

View file

@ -26,6 +26,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
**Recently resolved (2026-08-19):** **OPS66** — deadlock de migrations em caminho estático: o deploy buildava o runner **antes** do migrate e, se uma migration criasse tabela lida em geração estática (caso S2, 2026-08-18: `social_feed_settings` na home), o `next build` falhava para sempre (`relation ... does not exist`) e o migrate nunca rodava — 4 deploys seguidos vermelhos até o migrator manual. Fix em `scripts/deploy-homeserver.sh` (reordenação de fases, sem custo novo — migrate e build do migrator já rodavam em todo deploy): **build do migrator** (o estágio não roda `next build` — builda contra o schema antigo) → push/tag do migrator → swap do compose (backup + trap na mesma posição) → **migrate** via `docker compose run teqo-1313-migrate` (caminho canônico do serviço de maintenance, agora com a imagem do SHA novo) → **build do runner** contra o banco JÁ migrado → push/tag → rollout → healthcheck → smoke. Alternativas rejeitadas: `payload migrate` dentro do build do runner (efeito colateral de escrita em prod a partir de um processo de build; contraria o desenho deliberado do Dockerfile) e `docker run` direto do migrator com env-file (duplicaria a definição do serviço de maintenance — caminho paralelo ao canônico). Caveat do rollback ajustado no runbook: migrate antes do build do runner deixa o banco à frente do código se o build falhar depois (mesma classe do caveat atual — migrations append-only e revisadas). Testes: `tests/unit/deployScript.unit.spec.ts` com pin de ordem completo (migrator build < swap < migrate < runner build < rollout) + invariante novo do Dockerfile (estágio migrator nunca roda `next build`). Runbook `docs/ops/teqo-1313-deploy.md` atualizado (fluxo, caveat, falha conhecida nova). Dockerfile/AGENTS.md com a precisão build-after-migrate.
**Recently resolved (2026-08-19):** **OPS68** — fechamento da parte 2 do OPS50: reescrita dos docs canônicos operacionais que ainda descreviam a era GitHub/Vercel. **12 arquivos, 78+/82−, edições linha-a-linha:** `docs/AGENT-OPS.md` (norte "só Forgejo + Cursor"; fonte canônica = Issues do Forgejo; fluxo do agente e Contrato de PR sem `gh` — criação via API/MCP + merge pelo safety net `agent-pr-ready-automerge.yml`, o único que espera o rollup `CI (PR) / checks`; "Só humano" = envs de `~/stack/teqo-1313.env` + branch protection + runbook; tabela de secrets sem `POOL_GITHUB_TOKEN` (legado — o script o ignora) e com nota de pool dormente; linha `archive-cursor-agent.yml` na tabela CI; "smoke Neon stage" → sem smoke stage), `README.md` (cheatsheet de 5 linhas reescrito: claim → push → PR via API/MCP → safety net mergea → ci.yml em janela de 30 min + deploy homeserver; Neon → homeserver Postgres; `PROD_DATABASE_URL` = `DATABASE_URL` de `~/stack/teqo-1313.env`; migrações no deploy via `teqo-1313-migrate`; "GitHub mirror" → Forgejo Actions; papéis `coordinator/advisor/leader/candidate`; "electoral nuclei" → municípios; Issues → link Forgejo), `docs/roadmap.md` (só o ponteiro da fonte canônica — o stub continua congelado), `AGENTS.md` (refs Neon → homeserver `teqo_1313`; job do gate `check`→`gate`, key real do YAML; "Vercel preview" → host genérico na rationale do WebAuthn), `.agents/rules/agent-pr-workflow.mdc` (comandos `gh pr create/merge/checks` morrem — `gh` não existe em nenhuma máquina (OPS50); regra descreve API/MCP + safety net; "There is no deploy from CI" corrigido — OPS53 colocou o deploy no `ci.yml`), e as skills `work-issue` (SKILL + `execution-pipeline`), `plan-issue` (frontmatter, gate "nada no Forgejo", dedup via `pnpm issue all`), `project-status`, `capture-review-debts`, `engineering-audit` (`gh issue list` → `pnpm issue all`/API; `issue-done-on-stage-merge.yml`→`on-main`; merge `stage`→`main`), `worktree-next-issue` ("resto do Forgejo") e `agent-pool` (descrição Forgejo Actions; workflow marcado removido OPS65; estado em `pool-state.json`, não mais variables). **Mantido como registro histórico** (exclusões explícitas do plano): menções de Vercel/Neon/POOL_GITHUB que documentam remoções (AGENTS.md L15/L105/L167, AGENT-OPS L94/L98, "Vercel/Git-integration deploy gate" como guardrail no rule). Fonte canônica única: `git.solla.dev/fsolla/teqo`. Sem migration/access/Consent/UI. Gate: grep de saída zero nas classes `gh pr`/`gh issue`/`github.com/fsolla`/`GitHub Issues`/`GitHub mirror`/`POOL_GITHUB`/Neon-em-uso; docs-only → CI roda os docs-guards.
**Recently resolved (2026-08-18):** **OPS52** — a media saiu do Vercel Blob para o Garage S3 do homeserver (bucket `teqo-media` + key `teqo-key`, já existentes no Garage v2.1.0). O plugin `@payloadcms/storage-vercel-blob` (e o `@vercel/blob` do seed) foi trocado por `@payloadcms/storage-s3` na config, no padrão R2 da doc do Payload (`endpoint` + `forcePathStyle: true`, region `garage`). **Decisão de arquitetura (revisão A→B durante a execução, gate com o humano):** o Garage v2.1.0 **não suporta acesso anônimo na API S3** (sem `--public-read`; source: "when we support anonymous access in the future"; testado: `anonymous`/`anon` → `NoSuchAccessKey`), então o bucket fica **privado** e a app **serve a media por proxy** — sem `disablePayloadAccessControl`, o `staticHandler` do plugin streama o objeto do Garage em `/api/media/file/<filename>` (range + etag/304 nativos) — o **mesmo contrato de URL relativa que as 40 rows pré-existentes já têm** (a recuperação dos arquivos é a Issue #10/OPS52-media). **Fail-closed:** `resolveS3StorageEnv` (`src/utilities/mediaStorage.ts`, unit-testado) — envs `S3_*` parciais → throw no boot com a lista exata de faltantes; nenhuma → storage local (dev/test). Envs novas: `S3_BUCKET`, `S3_ENDPOINT`, `S3_REGION` (default `garage`), `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` (`.env.example`, `.env.test`, worktree.mjs com cópia all-or-nothing). Seed de posts: pre-delete do Blob removido (o S3 sobrescreve o key determinístico nativamente — idempotência intacta). Prova de contrato real: PUT/GET/DELETE via SDK contra o Garage (tailnet `http://100.119.220.31:3900`), depois media criada pelo Payload local com S3 ativo → `media.url` relativa + `GET /api/media/file/...` → 200 image/png; bucket devolvido a 0 objects e `.env.local` do worktree limpo (sem credenciais de prod). Sem migration (config-only; importMap regenerado sem `VercelBlobClientUploadHandler`). Runbook de cutover registrado no impl plan: setar as 5 `S3_*` no `~/stack/teqo-1313.env` (endpoint tailnet) antes do próximo deploy (OPS53). Repo: `docs/plans/ops52-blob-vercel-para-garage-impl.md`; infra-solla `STATE.md` atualizado à parte.
**Recently resolved (2026-08-18):** **OPS52-media** — os objetos de media de produção (as ~40 capas dos posts, que existiam como rows com URL relativa `/api/media/file/<filename>` mas sem arquivo em lugar nenhum) são recuperáveis do WordPress de origem com `pnpm media:recover` (`scripts/recover-media.mjs`, novo): lê as rows de `media` + `post.coverImage` (mapeamento exato row→slug; fallback pelo filename determinístico `<slug>.<ext>`), resolve a capa com a MESMA lógica do seed (featured media do REST `_embed`, senão 1ª imagem inline do corpo — só 7/43 posts têm featured), baixa e faz `PutObject` no bucket das envs `S3_*` com key = `filename` da row, **sem nunca escrever no DB** (rows e URLs intocadas; o staticHandler do Payload passa a servir). Extraído para `scripts/lib/wpArticles.mjs` o fetch ao vivo do WordPress (seed-posts agora importa de lá — sem twin; unit-testado). Fail-closed: exige as 4 `S3_*` completas (`resolveS3StorageEnv` reusado; recuperação nunca vai pro storage local), `ALLOW_REMOTE_DB=true` explícito para DB remoto + echo do alvo (DB/bucket/endpoint), e timeouts curtos no SDK (`@smithy/node-http-handler`, 5s/15s) — endpoint inalcançável falha em segundos em vez de pendurar (a rota pública de media de prod pendurava >25s; evidência de possível falta de rota container→Garage, diagnosticada no runbook do impl plan). Modos: reconcile (padrão, upload idempotente por overwrite), `--dry-run` (plano + `headObject`), `--verify` (HEAD da URL pública por filename — o aceite da Issue, sem credencial). Runbook de execução no homeserver: `docs/plans/ops52-media-recuperar-arquivos-impl.md`.

View file

@ -0,0 +1 @@
**Recently resolved (2026-08-19):** **OPS68** — fechamento da parte 2 do OPS50: reescrita dos docs canônicos operacionais que ainda descreviam a era GitHub/Vercel. **12 arquivos, 78+/82−, edições linha-a-linha:** `docs/AGENT-OPS.md` (norte "só Forgejo + Cursor"; fonte canônica = Issues do Forgejo; fluxo do agente e Contrato de PR sem `gh` — criação via API/MCP + merge pelo safety net `agent-pr-ready-automerge.yml`, o único que espera o rollup `CI (PR) / checks`; "Só humano" = envs de `~/stack/teqo-1313.env` + branch protection + runbook; tabela de secrets sem `POOL_GITHUB_TOKEN` (legado — o script o ignora) e com nota de pool dormente; linha `archive-cursor-agent.yml` na tabela CI; "smoke Neon stage" → sem smoke stage), `README.md` (cheatsheet de 5 linhas reescrito: claim → push → PR via API/MCP → safety net mergea → ci.yml em janela de 30 min + deploy homeserver; Neon → homeserver Postgres; `PROD_DATABASE_URL` = `DATABASE_URL` de `~/stack/teqo-1313.env`; migrações no deploy via `teqo-1313-migrate`; "GitHub mirror" → Forgejo Actions; papéis `coordinator/advisor/leader/candidate`; "electoral nuclei" → municípios; Issues → link Forgejo), `docs/roadmap.md` (só o ponteiro da fonte canônica — o stub continua congelado), `AGENTS.md` (refs Neon → homeserver `teqo_1313`; job do gate `check`→`gate`, key real do YAML; "Vercel preview" → host genérico na rationale do WebAuthn), `.agents/rules/agent-pr-workflow.mdc` (comandos `gh pr create/merge/checks` morrem — `gh` não existe em nenhuma máquina (OPS50); regra descreve API/MCP + safety net; "There is no deploy from CI" corrigido — OPS53 colocou o deploy no `ci.yml`), e as skills `work-issue` (SKILL + `execution-pipeline`), `plan-issue` (frontmatter, gate "nada no Forgejo", dedup via `pnpm issue all`), `project-status`, `capture-review-debts`, `engineering-audit` (`gh issue list` → `pnpm issue all`/API; `issue-done-on-stage-merge.yml`→`on-main`; merge `stage`→`main`), `worktree-next-issue` ("resto do Forgejo") e `agent-pool` (descrição Forgejo Actions; workflow marcado removido OPS65; estado em `pool-state.json`, não mais variables). **Mantido como registro histórico** (exclusões explícitas do plano): menções de Vercel/Neon/POOL_GITHUB que documentam remoções (AGENTS.md L15/L105/L167, AGENT-OPS L94/L98, "Vercel/Git-integration deploy gate" como guardrail no rule). Fonte canônica única: `git.solla.dev/fsolla/teqo`. Sem migration/access/Consent/UI. Gate: grep de saída zero nas classes `gh pr`/`gh issue`/`github.com/fsolla`/`GitHub Issues`/`GitHub mirror`/`POOL_GITHUB`/Neon-em-uso; docs-only → CI roda os docs-guards.

View file

@ -0,0 +1,85 @@
# Impl: OPS68 — docs: drift GitHub-era no AGENT-OPS/README — reescrita da parte 2 do OPS50
Status: aprovado
Atualizado em: 2026-08-19
Issue: #74
Intenção: o body da Issue é a spec (frontmatter `id: OPS68, depends: [OPS61], priority: P3`); o escopo ancora na parte 2 do OPS50 (reescrita de docs canônicos) e no título (drift GitHub-era: `gh pr`, secrets Vercel, fonte canônica GitHub)
Appetite restante: P3, ~meio dia de engenharia (docs-only)
## Leitura da intenção
- **Outcome:** os docs canônicos operacionais do paradigma (AGENT-OPS, README, roadmap, AGENTS.md e as skills/rules always-on de operação) descrevem a realidade Forgejo/homeserver de 2026-08 — zero resíduo GitHub-era (comandos `gh`, links github.com/fsolla, "GitHub Issues" como fonte canônica) e zero resíduo Vercel-era (secrets `VERCEL_*`, "Neon" como banco de prod, "deploy Vercel").
- **O que NÃO negociar:** nada de produto — é docs. Não tocar docs congelados/históricos (`docs/plans/*`, `docs/changelog/*` + `CHANGELOG-AGENTS.md` insert-only, `docs/GUARDRAILS.md` — os links GitHub lá são registro histórico das issues/PRs da época; `docs/CUTOVER-MAIN-ONLY.md` — checklist histórico do cutover, já anotado pelo OPS61; a linha histórica do `nucleos-eleitorais.mdc` que cita "mirror GitHub" descreve uma entrega de 2026-07-23 e permanece como registro). A "fonte canônica" passa a ser Forgejo sem ambigüidade — uma fonte só.
- **O que reavaliar:** a hipótese do título (AGENT-OPS/README) é o núcleo, mas a exploração mostra que a mesma classe de drift vive em mais 5 arquivos operacionais always-on — o sweep segue a classe nomeada no título ("gh pr", "Vercel secrets", "fonte canônica GitHub"), não só os dois arquivos.
## Abordagem recomendada
```mermaid
flowchart LR
A[Baseline grep dos alvos] --> B[Reescrita docs canônicos]
B --> C[Reescrita .agents operacionais]
C --> D[Changelog OPS68 + gates]
D --> E[Grep zero de resíduo = aceite]
```
**Opções consideradas (largura do sweep — decisão cara):**
- **A)** Só `docs/AGENT-OPS.md` + `README.md`. **Rejeitada** — deixa a regra always-on `agent-pr-workflow.mdc` (com `gh pr create/merge/checks` de verdade, e `gh` não existe em nenhuma máquina — achado OPS50) e as skills de fluxo (`work-issue`/`execution-pipeline`/`plan-issue`) instruindo comandos fantasma; o próprio `/work-issue` desta sessão lê `gh issue view` no Passo 1. Um OPS69 nasceria na semana.
- **B)** Canônicos operacionais (recomendada): `docs/AGENT-OPS.md`, `README.md`, `docs/roadmap.md` (só a linha de fonte canônica — o resto continua congelado), `AGENTS.md` (refs Neon/era-Vercel + nome do job do gate), `.agents/rules/agent-pr-workflow.mdc` e as 7 skills de operação com drift (`work-issue/SKILL.md`, `work-issue/execution-pipeline.md`, `plan-issue`, `project-status`, `capture-review-debts`, `engineering-audit`, `worktree-next-issue`, `agent-pool`). Edições **pontuais** (só as linhas do drift), nunca reescrita/refrase de conteúdo são.
- **C)** OPS50 parte 2 integral = varrer também conteúdo genérico de skills de terceiros (`impeccable` hooks.md, `clean-code` references, etc.). **Rejeitada** — aquilo é biblioteca de skill, não operação do Teqo; "GitHub Copilot"/"Jira/GitHub Issues" em conteúdo genérico é referência ao mundo, não drift do repo.
**Recomendação: B** — cobre a classe do título inteira no mínimo de arquivos com risco de diff; o critério de saída é verificável por grep.
### Decisões de engenharia (todas caras o bastante para registar)
1. **Fluxo de PR documentado (o que substitui `gh pr …`).** Realidade verificada: `gh` não existe na máquina do humano nem nos workers (OPS50); o merge é feito pelo safety net `agent-pr-ready-automerge.yml` (`scripts/forgejo-pr-automerge.mjs`, plain Node) quando o rollup `CI (PR) / checks` posta verde — para TODA PR same-repo → main (OPS57), draft não-`cursor/*` = veto; a branch protection no servidor bloqueia merge com o contexto exigido vermelho (OPS61, 405). Criação de PR é via API/MCP do opencode (humano) ou `ManagePullRequest` `draft:false` (Cursor Cloud). Documentar: `pnpm push -u origin HEAD` → PR via API/MCP (Ready, base `main`, `Closes #N`) → safety net espera o rollup e mergea por rebase; `gh pr checks --watch` não tem equivalente (quem espera é o safety net). **Nunca documentar comando que não existe.**
2. **"Só humano" (AGENT-OPS L24 + README L17).** O que sobrou de fato: envs de prod vivem no homeserver (`~/stack/teqo-1313.env`, fora do repo; o deploy lê via BuildKit secrets), reaplicar branch protection é `pnpm configure:branch-protection` (idempotente, já aplicado 2026-08-18), runbook/rollback manual em `docs/ops/teqo-1313-deploy.md`. Segredo de repo único ativo: `FORGEJO_API_TOKEN`; `CURSOR_API_KEY` só existe para o pool dormente. `POOL_GITHUB_TOKEN` é legado — o próprio `scripts/agent-pool.mjs` avisa "legado presente no env — ignorar" (L470); remover da tabela de secrets ou marcar `legado`.
3. **Deploy descrito como ele é (OPS53+OPS65).** `ci.yml` do main em janela fixa de 30 min; job `gate` (o AGENTS.md escreve `check` — corrigir para `gate`, job key real no YAML) classifica mudança de produção via `.dockerignore` + revision do container; job `deploy` (runs-on `host`) streama `scripts/deploy-homeserver.sh` — migrações aplicadas pelo serviço de maintenance `teqo-1313-migrate` antes do rollout. A frase do README "every Vercel deploy automatically applies pending migrations" morre; a do AGENT-OPS L96 ("deploy Vercel removido no OPS50") está correta e fica.
4. **Roadmap congelado, ponteiro vivo.** `docs/roadmap.md` L5: trocar só o ponteiro da fonte canônica (GitHub → Forgejo, `git.solla.dev/fsolla/teqo/issues`); a declaração "congelado em 2026-07-30, não é mais editado" permanece — o ponteiro é instrução de leitura, não conteúdo do roadmap.
5. **Pool dormente.** `agent-pool/SKILL.md` fica (histórico, como o AGENT-OPS já declara), mas a descrição "GitHub Actions" e a referência `.github/workflows/agent-pool.yml` (L14) ganham a marca de removido OPS65 — a skill ainda é descoberta e lida. `archive-cursor-agent.yml` ganha linha na tabela CI do AGENT-OPS (existe no `.forgejo/workflows/` e não está na tabela; dormente com o pool).
### Componentes / mudanças
- **`docs/AGENT-OPS.md`** — L3 "só GitHub"→"só Forgejo"; L21 fonte canônica → Issues do Forgejo; L23 fluxo do agente (sem `gh`); L24 "Só humano" (decisão 2); L59 item 3 do Contrato de PR (decisão 1); L92 tabela secrets (`POOL_GITHUB_TOKEN` → legado; nota pool dormente); tabela CI ganha linha `archive-cursor-agent.yml` (dormente); L96 verificado e mantido.
- **`README.md`** — cheatsheet L7–18 reescrito (5 linhas: claim → implementa+push → PR via API/MCP + safety net → ci.yml janela 30 min → deploy homeserver; secrets humanos `FORGEJO_API_TOKEN` + envs do homeserver; CI `.forgejo/workflows/`); L38 "electoral nuclei"→"municípios" (vocabulário era Praça); L52 Neon→homeserver Postgres; L65 `PROD_DATABASE_URL` = `DATABASE_URL` de `~/stack/teqo-1313.env`; L87 `REVALIDATE_SECRET` no env do homeserver; L100 migrações no deploy → maintenance service `teqo-1313-migrate` (decisão 3); L116 "GitHub mirror"→Forgejo Actions; L127 papéis de campanha atuais (`coordinator|advisor|candidate|leader`); L133 Issues → link Forgejo.
- **`docs/roadmap.md`** — L5 ponteiro da fonte canônica (decisão 4).
- **`AGENTS.md`** — L30/L32/L159 refs "Neon" → homeserver Postgres (era-Vercel, mesma classe do parêntese do título); L15 "(`check`"→"(`gate`" (job key real); L147 "no Neon risk" → "no prod-DB risk" (idem).
- **`.agents/rules/agent-pr-workflow.mdc`** — create (L14), ManagePullRequest (L32), auto-merge (L54), checks (L74/L77): comandos `gh` → API/MCP + safety net; L69 "There is no deploy from CI … outside this repo" → deploy step no `ci.yml` (janela, OPS53/OPS65) — contradição factual com AGENT-OPS L96; L81 promote idem.
- **`.agents/skills/work-issue/SKILL.md`** — L45–48 "consulte o GitHub" / `gh issue view <N>` → Forgejo/API da issue.
- **`.agents/skills/work-issue/execution-pipeline.md`** — L38–40: `gh pr create/merge/checks` → API/MCP + safety net; "ignore Vercel Git" morre.
- **`.agents/skills/plan-issue/SKILL.md`** — L16/L32/L153: "GitHub Issue rastreável"/"Nada no GitHub"/`gh issue create`/`gh pr create`/`gh pr merge --auto` → Forgejo/`pnpm agent:register`/safety net.
- **`.agents/skills/project-status/SKILL.md`** — L3+L8 "GitHub Issues rastreáveis" → "Issues do Forgejo".
- **`.agents/skills/capture-review-debts/SKILL.md`** — L3 "trackable GitHub Issues" → Forgejo.
- **`.agents/skills/engineering-audit/SKILL.md`** — L16/L169 `gh pr merge --auto --rebase` → safety net (mesma mecânica dos outros).
- **`.agents/skills/worktree-next-issue/SKILL.md`** — L34 "a skill lê o resto do GitHub" → Forgejo.
- **`.agents/skills/agent-pool/SKILL.md`** — descrição "GitHub Actions" → Forgejo Actions; L14 `.github/workflows/agent-pool.yml` → marcada removida OPS65 (decisão 5).
- **Migration:** sem migration. **Access/Consent:** N/A. **UI:** A — sem UI (docs-only).
## Fases verificáveis
1. **Baseline** — grep dos alvos registra o inventário do drift (já medido na exploração); o diff do PR prova o que mudou.
2. **Docs canônicos** — AGENT-OPS → README → roadmap → AGENTS.md (verificação de fato contra `.forgejo/workflows/ci.yml`, `deploy-homeserver.sh` e `docs/ops/teqo-1313-deploy.md` durante a escrita).
3. **`.agents` operacionais** — rule + 8 skills, edições pontuais por linha.
4. **Gates + entrega** — `pnpm format:check` (prettier cobre .md), `pnpm changelog:build` + `pnpm changelog:check` após criar `docs/changelog/2026-08-19-ops68.md`; grep de saída zero; `pnpm push -u origin HEAD` → PR via MCP (Ready, base `main`, `Closes #74`); docs-only → CI roda os docs-guards (a suíte unit não roda por scope); safety net mergea.
## Rabbit holes / Não escopo (engenharia)
- Docs congelados/históricos (`docs/plans/*`, changelog insert-only, `GUARDRAILS.md`, `CUTOVER-MAIN-ONLY.md`, linha histórica do `nucleos-eleitorais.mdc`) — reescrever registro para a era Forgejo seria falsificar história.
- Conteúdo genérico de skills de terceiros (`impeccable` hooks, `clean-code` references etc.).
- Comentários de código/scripts com drift (ex.: header do `check-push-chain.mjs` citando Neon; `agent-status.mjs` "gh issue list") — fora: são código, não docs canônicos; se alguém quiser, item futuro.
- Reescrita de frases sãs só para "uniformizar" — diff mínimo é o objetivo.
- Reformatar arquivos inteiros com prettier — editar só as linhas do drift.
## Riscos e mitigação
- **Descrever fluxo errado** (risco principal de docs). Mitigação: cada afirmação nova verificada contra o YAML real dos workflows, `forgejo-api.mjs`/`forgejo-pr-automerge.mjs` e `AGENTS.md` (que está limpo na maior parte); o AGENT-OPS L96 e a tabela CI já estão corretos — não reescrever o que está certo.
- **Diff grande nos `.agents`** convidando refrase acidental. Mitigação: edições linha-a-linha; `git diff` revisado contra o inventário do Passo 1.
- **docs-guards do pre-push (OPS63)** — changelog append-only + agregado sync: a entrada nova é additions-only e `changelog:build` regenera o agregado; nenhuma restauração. Sem escape.
- **P3 docs-only merge rápido** — sem migration/access/Consent, sem risco de prod (`.agents/`, `AGENTS.md`, `docs/` são skip de deploy pelo `.dockerignore`).
## Aceite de engenharia
- [ ] Aceite de produto da intenção coberto: docs canônicos operacionais descrevem Forgejo/homeserver; zero resíduo das classes `gh pr`, `VERCEL_*`/Neon como prod, "GitHub Issues" como fonte canônica nos 12 arquivos-alvo
- [ ] Invariantes AGENTS/engineering-standards (sem segredos; docs congelados intactos)
- [ ] Grep de saída zero nos alvos: `gh pr`, `gh issue`, `github.com/fsolla`, `GitHub Issues`, `VERCEL_`, `POOL_GITHUB`, `Neon` (com as exclusões explícitas de conteúdo genérico/histórico)
- [ ] `pnpm format:check`, `pnpm changelog:build` + `pnpm changelog:check` verdes; CI docs-guards verde no PR

View file

@ -2,6 +2,6 @@
**Congelado em 2026-07-30.** Este arquivo não é mais editado.
- **Fonte canônica de spec/status/deps/prio/modelo:** [GitHub Issues](https://github.com/fsolla/teqo/issues) (frontmatter `id/depends/serializes/priority/model`; estado em labels `ready | in-progress | blocked | done | in-prod`). Fluxo: skills `plan-issue` → `work-issue` → `project-status` (ver `docs/AGENT-OPS.md`). Status rápido: `pnpm agent:status`.
- **Fonte canônica de spec/status/deps/prio/modelo:** [Issues do Forgejo](https://git.solla.dev/fsolla/teqo/issues) (frontmatter `id/depends/serializes/priority/model`; estado em labels `ready | in-progress | blocked | done | in-prod`). Fluxo: skills `plan-issue` → `work-issue` → `project-status` (ver `docs/AGENT-OPS.md`). Status rápido: `pnpm agent:status`.
- **Histórico e entregas:** [`docs/CHANGELOG-AGENTS.md`](CHANGELOG-AGENTS.md) + [`docs/plans/`](plans/).
- **Conteúdo integral deste roadmap:** preservado no git history (última versão viva no commit imediatamente anterior a este stub).