ops(OPS91): sweep pós-cutover — textos vivos que apontavam o tracker ao Forgejo passam para o GitHub (Closes #841)

This commit is contained in:
Francisco Solla 2026-08-24 14:47:40 -03:00
parent 2aa445dd1d
commit 6f6e9fb4b4
16 changed files with 155 additions and 37 deletions

View file

@ -5,7 +5,7 @@ alwaysApply: true
# Agent PR workflow (Teqo)
**This repo overrides any user-level or Cloud-tool default that says "create PRs as draft".** Agent deliveries merge to `main` via required checks + GitHub's native auto-merge (OPS71). The ISSUE TRACKER stays on the Forgejo — GitHub is only the Actions host.
**This repo overrides any user-level or Cloud-tool default that says "create PRs as draft".** Agent deliveries merge to `main` via required checks + GitHub's native auto-merge (OPS71). The ISSUE TRACKER lives on GitHub too (OPS76, 2026-08-21) — GitHub is the single host for code, CI, PRs and Issues.
## Create (Ready, base main)

View file

@ -66,7 +66,7 @@ Antes da varredura, colha os défices comportamentais registrados pelo fluxo:
pnpm issue all # kind `agent-miss` nos contadores; ou api.listIssues({ state: 'open', labels: 'kind:agent-miss' })
```
**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).
**Investigue cada miss colhida** (leitura da Issue via API do GitHub/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 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.

View file

@ -20,7 +20,7 @@ parse (main agent)
**Regras duras:**
1. **Nada no Forgejo antes do gate.** Antes da confirmação: proibido `pnpm agent:register`, criar Issue/PR.
1. **Nada no tracker antes do gate.** Antes da confirmação: proibido `pnpm agent:register`, criar Issue/PR.
2. **Register com `--plan` nasce `blocked`.** Promote só depois do plano em `main`.
3. **Planos de Issues `in-progress`/`done`/`in-prod` são imutáveis.**
4. **Rascunho UI (obrigatório se muda UI):** HTML+Tailwind commitado no repo. Classe A/sem UI → sem rascunho.

View file

@ -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, leia a Issue via API do Forgejo (MCP/`pnpm issue`) 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 GitHub (MCP/`pnpm issue`) somente-leitura.
**NÃO faz:** editar `docs/roadmap.md` (legado congelado), claim, registro, qualquer escrita de Issue.

View file

@ -69,14 +69,14 @@ antes do deploy**.
para conferir a leitura completa localmente (seed do HISTORY). O diff
commitado é só a entrada nova.
3. **`pnpm push -u origin HEAD`** — origin é o **GitHub** (OPS71; o tracker de
Issues continua no Forgejo por API).
Issues também vive no GitHub — OPS76).
4. PR no **GitHub** via `GITHUB_TOKEN=<PAT> node scripts/github-pr.mjs --head <branch> --title "<id> — <título>" --body-file <arquivo>` — **Ready** (nunca draft; o script não tem flag draft), base `main`, body com `Closes #<N>` (ou `Related #N` em plans-only). Em Cursor Cloud: `ManagePullRequest` com `draft: false`.
5. O safety net `agent-pr-ready-automerge.yml` arma o **auto-merge nativo do
GitHub** (`enablePullRequestAutoMerge`, rebase) — o servidor mergea quando
o required check `CI (PR) / checks` fica verde; nada a armar.
6. No merge, o workflow `issue-done-on-main-merge.yml` flipa `done`/`in-prod`
**no Forgejo** (lê o body do PR via API do GitHub, escreve no tracker por
`FORGEJO_API_TOKEN`). Comente na Issue o desfecho em uma linha.
**no GitHub** (lê o body do PR via API do GitHub, escreve no tracker por
`GITHUB_TOKEN`). Comente na Issue o desfecho em uma linha.
## Deltas por ator

View file

@ -2,7 +2,7 @@
## Deploy & CI
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). **CI/PR/deploy live on GitHub Actions (OPS71, 2026-08-19); the ISSUE TRACKER stays on the Forgejo** (`git.solla.dev/fsolla/teqo` — labels, claims, `pnpm issue`/`agent:*` and the post-merge flips all keep talking to the Forgejo API; GitHub is only the Actions host). The PR CI (`ci-pr.yml`, job único `checks` — OPS62) runs the same cascade as always (guards → lint → format → typecheck → knip → cycles → unit → int → build → e2e selected/afetado, skips via `ci-scope.mjs`); its check-run `CI (PR) / checks` is the required check of `main`. **There is NO main verifier (`ci.yml`) anymore**: publishing is a **manual `workflow_dispatch`** (`.github/workflows/deploy.yml`) with two jobs — `verify` (hosted `ubuntu-latest`, suíte **full** incl. e2e full) → `deploy` (`needs: [verify]`, `runs-on: [self-hosted, homeserver]`): the GitHub self-hosted runner ON the homeserver executes `scripts/deploy-homeserver.sh` **locally** (no SSH; outbound-only runner, works behind the tunnel; the hosted job never touches the homeserver). The remote Docker deployment is gone; the production container runs the monolith directly. The Docker Compose file is used ONLY for local dev databases. The `run-deploy.yml` (OPS38/OPS46) was deleted in OPS71. There is no `run-deploy.yml` or equivalent in GitHub Actions — deploy is `deploy.yml` only.
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). **CI/PR/deploy AND the ISSUE TRACKER live on GitHub (OPS71 + OPS76, 2026-08-21)** (`github.com/fsolla/teqo/issues` — labels, claims, `pnpm issue`/`agent:*` and the post-merge flips talk to the GitHub API; GitHub is the single host). The PR CI (`ci-pr.yml`, job único `checks` — OPS62) runs the same cascade as always (guards → lint → format → typecheck → knip → cycles → unit → int → build → e2e selected/afetado, skips via `ci-scope.mjs`); its check-run `CI (PR) / checks` is the required check of `main`. **There is NO main verifier (`ci.yml`) anymore**: publishing is a **manual `workflow_dispatch`** (`.github/workflows/deploy.yml`) with two jobs — `verify` (hosted `ubuntu-latest`, suíte **full** incl. e2e full) → `deploy` (`needs: [verify]`, `runs-on: [self-hosted, homeserver]`): the GitHub self-hosted runner ON the homeserver executes `scripts/deploy-homeserver.sh` **locally** (no SSH; outbound-only runner, works behind the tunnel; the hosted job never touches the homeserver). The remote Docker deployment is gone; the production container runs the monolith directly. The Docker Compose file is used ONLY for local dev databases. The `run-deploy.yml` (OPS38/OPS46) was deleted in OPS71. There is no `run-deploy.yml` or equivalent in GitHub Actions — deploy is `deploy.yml` only.
**Guarding `pnpm push` (OPS24+):** every PR push goes through `scripts/git-push.mjs`, which runs `ensure-repo-deps` (install + Husky in worktrees — OPS51), then `gate:push` (the pre-push hook), which chains `gate:ci` — a serialized mirror of the CI `checks` job (**without e2e**: OPS72 moved e2e to an explicit skill step) — plus the `docs-guards` (changelog entries additions-only + frozen HISTORY snapshot + uncommitted regenerable aggregate — OPS63/OPS85; conflict markers; the rewrite escape stays in CI only). The hook blocks `git push` on failure; escape: `git push --no-verify` (WIP only). There is no YAML `push` gate — the hook is the gate. **CI red on your PR is yours**, even pre-existing or infra — "out of feature scope" is not a stop condition (policy "Dono do PR, dono do CI" in `docs/AGENT-OPS.md`).

View file

@ -6,11 +6,11 @@
> **Cheatsheet de operação agentic (humanos)** — o fluxo em 5 linhas:
>
> 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`). O **tracker de Issues vive no Forgejo** (`git.solla.dev/fsolla/teqo`); o código/PR/CI vive no **GitHub**.
> 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`). O **tracker de Issues, o código, os PRs e o CI vivem no GitHub** (`github.com/fsolla/teqo`).
> 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 (`CI (PR) / checks`) → o safety net `agent-pr-ready-automerge.yml` arma o **auto-merge nativo** (rebase) — o servidor só mergea com o required check verde.
> 4. Publicar é **manual**: dispatch de `deploy.yml` roda a suíte full (`verify`) e o job `deploy` publica no homeserver (`jorgesolla1313.com.br`, runner self-hosted) — migrações aplicadas antes do rollout. Nada é automático pós-merge.
> 5. Secrets humanos (uma vez): `FORGEJO_API_TOKEN` no GitHub (flips pós-merge no Forgejo); envs de prod em `~/stack/teqo-1313.env` no homeserver. Branch protection de `main` já aplicada (reaplicar: `pnpm configure:branch-protection`).
> 5. Secrets humanos (uma vez): `GITHUB_TOKEN` (PAT com escopo `repo` + `issues: write`) para os scripts de agente/PR (`pnpm issue`/`agent:*`, `github-pr.mjs`); 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 | 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`.
@ -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 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`.
Quality gates (also enforced by CI on GitHub Actions — `ci-pr.yml`): `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
@ -130,4 +130,4 @@ Internal campaign tool for municípios (435 pré-definidos), local leaderships,
## Roadmap
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).
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).

View file

@ -0,0 +1 @@
**OPS91 — sweep pós-cutover OPS76: textos vivos que ainda apontavam o tracker de Issues ao Forgejo (2026-08-24):** docs-only, zero mudança de comportamento. O cutover OPS71+OPS76 (2026-08) tornou o GitHub o host único (CI + tracker de Issues em `github.com/fsolla/teqo/issues`, labels/claims/flips via API do GitHub com `GITHUB_TOKEN`); o Forgejo ficou congelado. A varredura da sessão OPS89 (#836) virou esta Issue (#841): **23 alvos de prosa/comentário corrigidos in place em 14 arquivos (30+/30−)**, cada arquivo dono da própria frase, com `AGENTS.md:15` como fraseado canônico. **Classe (a) — tracker/API apontando o Forgejo → GitHub (12):** `.agents/rules/agent-pr-workflow.mdc:8` ("ISSUE TRACKER stays on the Forgejo" → "lives on GitHub too (OPS76) — single host"); `.agents/skills/work-issue/execution-pipeline.md:69,75-76` (flip pós-merge "no Forgejo" via `FORGEJO_API_TOKEN` → "no GitHub" via `GITHUB_TOKEN`); `.agents/skills/project-status/SKILL.md:25` e `engineering-audit/SKILL.md:69` (leitura da Issue "via API do Forgejo" → GitHub); `.agents/skills/plan-issue/SKILL.md:23` ("Nada no Forgejo antes do gate" → "Nada no tracker"); `docs/roadmap.md:5` (ponteiro da fonte canônica → Issues do GitHub; stub segue congelado — precedente OPS68); `AGENTS-infra.md:5` (espelho do AGENTS.md com a realidade invertida — o mais grave, camada AGENTS); `README.md:9,13,133` (tracker no Forgejo / `FORGEJO_API_TOKEN` como instrução ativa / link do tracker); `scripts/github-pr.mjs:4` (comentário de cabeçalho). **Classe (b) — resíduo de host da era Forgejo, eixo diferente (11, decisão do gate):** `README.md:116` ("CI on the Forgejo Actions" → GitHub Actions `ci-pr.yml`); cabeçalho de `scripts/lib/agent-forgejo.mjs:3,7` (citava `forgejo-api.mjs` removido → `github-api.mjs` real; rename de pureza segue vetado na intenção); `scripts/issue-transition-on-merge.mjs` (usage + 6 prefixos de log `forgejo-issue-transition` → nome real do arquivo, era instrução de arquivo inexistente); `scripts/agent-status.mjs:26` (die "Forgejo issue list failed" → GitHub); `scripts/lib/cli.mjs:34,50` (comentários da allowlist LOCAL_HOSTS emoldurados como era-Forgejo — CI GitHub hosted usa localhost publicado OPS71); `scripts/lib/github-api.mjs:9,183,211` (refs a lib removida). /simplify (2 revisores): P1 aplicado — emoldurar os comentários de `cli.mjs` como histórico (o job-network/bridge gateway era Forgejo; o GitHub hosted publica portas), não como mecanismo vivo; P3 aplicado — `README.md:13` cortou "flips pós-merge" do PAT humano (flips usam o token built-in do Actions com `issues: write`). Débitos: nada registrado (triage: 2 já_resolvidos, 4 descartados score ≤2, 1 defer com gatilho — resíduo Forgejo no pool dormente será limpo se o pool for reativado). Fora de escopo respeitado: histórico correto (`agent-pr-workflow.mdc:60,85`, runbook `docs/ops/teqo-1313-deploy.md`, `AGENT-OPS.md:95` legado, pool dormente, `docs/CUTOVER-MAIN-ONLY.md`) e docs congelados (`docs/plans/*`, `docs/changelog/*`, `docs/CHANGELOG-AGENTS.md`) intactos. Verificação: grep de saneamento com zero refs vivas a tracker/API do Forgejo fora das classes históricas; `pnpm gate:fast` verde (256 arquivos/2443 testes), lint/format verdes; impl plan `docs/plans/ops91-sweep-pos-cutover-forgejo-textos-vivos-impl.md` (self-score 5/5).

View file

@ -0,0 +1,113 @@
# Impl: Sweep pós-cutover OPS76: textos vivos ainda apontam o tracker ao Forgejo
Status: rascunho
Atualizado em: 2026-08-24
Issue: #841
Intenção: docs/plans/ops91-sweep-pos-cutover-forgejo-textos-vivos.md
Appetite restante: herdado (~0,5 dia — só edição de prosa; sem estouro possível)
## Leitura da intenção
- **Outcome:** todo texto vivo do repo que instrui leitura/escrita de Issues no tracker do Forgejo (`git.solla.dev/fsolla/teqo/issues`, API, `FORGEJO_API_TOKEN`) passa a apontar o GitHub (`github.com/fsolla/teqo/issues`, API do GitHub, `GITHUB_TOKEN`) como tracker único; menções históricas corretas permanecem intactas.
- **O que NÃO negociar:** zero mudança de comportamento em scripts (só prosa/comentários); não reescrever história (runbook de rollback, marcações "legado/removido/congelado", `docs/plans/**`, `docs/changelog/**`, `CHANGELOG-AGENTS.md`); não renomear `scripts/lib/agent-forgejo.mjs`.
- **O que reavaliar:** o aceite "grep de saneamento: nenhuma ref viva a tracker/API do Forgejo fora das classes FORA" exige decidir o destino dos 11 itens **BORDA** (eixo diferente do tracker — CI, comentários de lib, prefixos de log) — sem essa decisão o grep de saída fica contaminado. Decidido abaixo (mesma classe de resíduo pós-cutover; incluir no mesmo diff, marcado como decisão do gate).
## Abordagem recomendada
```mermaid
flowchart LR
A[Achados explorador: 12 vivo-errado + 11 borda] --> B[Fase única: edição de prosa in place]
B --> C[Classe a: tracker/API Forgejo → GitHub<br/>12 alvos]
B --> D[Classe b: eixo CI/scripts → GitHub<br/>11 alvos, decisão do gate]
C --> E[Verificação: grep saneamento + pnpm gate:fast]
D --> E
E --> F[Changelog OPS91 + push]
```
**Opções consideradas:**
- **A) In place, fase única, BORDA dentro** — cada arquivo dono da própria frase; os 11 itens BORDA entram no mesmo diff.
- **B) In place, BORDA fora** — só os 12 vivo-errado; BORDA vira Issue nova.
- **C) Documento novo (lista de exceções)** — não editar, documentar o resíduo.
**Recomendação: A** — porque é a mesma classe de resíduo pós-cutover (leitor novo tropeça igual em "tracker no Forgejo" e em "Forgejo Actions" na linha do CI); a edição é prosa/comentário sem comportamento, o diff inteiro é atomicamente revertível, e o grep de saneamento só fecha sem exceções. O appetite (~0,5 dia) comporta os 23 alvos com folga. O BORDA fica **marcado no PR como decisão do gate** (o humano pode vetar os 11 e reduzir o diff para a classe a sem custo — arquivos independentes).
**Rejeitadas:** B porque espalha a correção em duas entregas com zero isolamento de risco (nenhum alvo é código) e deixa o grep de saída do PR A com exceções; C porque deixa instrução viva errada no repo (o aceite da intenção exige corrigir, não documentar).
**Decisões de engenharia:**
- **Fraseado canônico:** espelhar `AGENTS.md:15` como fonte de verdade de fraseado ("CI/PR/deploy AND the ISSUE TRACKER live on GitHub (OPS71 + OPS76, 2026-08-21) (`github.com/fsolla/teqo/issues` … GitHub is the single host)") — evita que as camadas AGENTS (`.mdc`, `AGENTS-infra.md`) divirjam entre si. Alternativa (tradução livre por arquivo) rejeitada: cada reescrita divergente seria o próximo drift.
- **`docs/roadmap.md` (stub congelado):** editar só a linha-ponteiro da fonte canônica — precedente exato do OPS68 ("só o ponteiro da fonte canônica — o stub continua congelado"); o congelamento cobre conteúdo, não ponteiro vivo para o tracker morto. Alternativa "não tocar" rejeitada: o stub diz explicitamente "Status rápido: `pnpm agent:status`" e o ponteiro é o texto vivo que o leitor usa.
### Componentes / mudanças
- **Migration:** sem migration.
- **Access / Consent:** N/A — nenhuma mudança de schema ou escrita em dados.
- **UI:** N/A — Impeccable A, zero superfície.
- **Testes:** nenhum novo; verificação por grep + `pnpm gate:fast` (nenhum string editado é pinado em spec — verificado: `grep` em `tests/` por `forgejo-issue-transition|Forgejo issue list failed|forgejo-api|Forgejo Actions` = 0 matches).
### Lista arquivo:linha → texto alvo
**Classe (a) — VIVO-ERRADO (core da Issue):**
| # | Arquivo:linha | Atual | Alvo |
| --- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| a1 | `.agents/rules/agent-pr-workflow.mdc:8` | "…native auto-merge (OPS71). The ISSUE TRACKER stays on the Forgejo — GitHub is only the Actions host." | "…native auto-merge (OPS71). The ISSUE TRACKER lives on the GitHub too (OPS76, 2026-08-21) — GitHub is the single host for code, CI, PRs and Issues." |
| a2 | `.agents/skills/work-issue/execution-pipeline.md:69` | "(OPS71; o tracker de Issues continua no Forgejo por API)." | "(OPS71; o tracker de Issues também vive no GitHub, OPS76)." |
| a3 | `.agents/skills/work-issue/execution-pipeline.md:74–76` | "flipa `done`/`in-prod` **no Forgejo** (lê o body do PR via API do GitHub, escreve no tracker por `FORGEJO_API_TOKEN`)" | "flipa `done`/`in-prod` **no GitHub** (lê o body do PR via API do GitHub, escreve no tracker por `GITHUB_TOKEN`)" |
| a4 | `.agents/skills/project-status/SKILL.md:25` | "leia a Issue via API do Forgejo (MCP/`pnpm issue`)" | "leia a Issue via API do GitHub (MCP/`pnpm issue`)" |
| a5 | `.agents/skills/engineering-audit/SKILL.md:69` | "(leitura da Issue via API do Forgejo/MCP — disponível no Cloud)" | "(leitura da Issue via API do GitHub/MCP — disponível no Cloud)" |
| a6 | `.agents/skills/plan-issue/SKILL.md:23` | "1. **Nada no Forgejo antes do gate.**" | "1. **Nada no tracker antes do gate.**" |
| a7 | `docs/roadmap.md:5` | "[Issues do Forgejo](https://git.solla.dev/fsolla/teqo/issues)" | "[Issues do GitHub](https://github.com/fsolla/teqo/issues)" (só o ponteiro; stub segue congelado) |
| a8 | `AGENTS-infra.md:5` | "**CI/PR/deploy live on GitHub Actions (OPS71, 2026-08-19); the ISSUE TRACKER stays on the Forgejo** (`git.solla.dev/fsolla/teqo` — labels, claims, `pnpm issue`/`agent:*` and the post-merge flips all keep talking to the Forgejo API; GitHub is only the Actions host)." | "**CI/PR/deploy AND the ISSUE TRACKER live on GitHub (OPS71 + OPS76, 2026-08-21)** (`github.com/fsolla/teqo/issues` — labels, claims, `pnpm issue`/`agent:*` and the post-merge flips talk to the GitHub API; GitHub is the single host)." (espelho verbatim de `AGENTS.md:15`) |
| a9 | `README.md:9` | "O **tracker de Issues vive no Forgejo** (`git.solla.dev/fsolla/teqo`); o código/PR/CI vive no **GitHub**." | "O **tracker de Issues, o código, os PRs e o CI vivem no GitHub** (`github.com/fsolla/teqo`)." |
| a10 | `README.md:13` | "Secrets humanos (uma vez): `FORGEJO_API_TOKEN` no GitHub (flips pós-merge no Forgejo); envs de prod em `~/stack/teqo-1313.env` no homeserver." | "Secrets humanos (uma vez): `GITHUB_TOKEN` (PAT com escopo `repo` + `issues: write`) para os scripts de agente/PR e flips pós-merge; envs de prod em `~/stack/teqo-1313.env` no homeserver." |
| a11 | `README.md:133` | "tracked [Forgejo Issues](https://git.solla.dev/fsolla/teqo/issues)" | "tracked [GitHub Issues](https://github.com/fsolla/teqo/issues)" |
| a12 | `scripts/github-pr.mjs:4` | "delivery flow (OPS71: PRs live on GitHub; the tracker stays on Forgejo)." | "delivery flow (OPS71: PRs live on GitHub; the tracker lives on GitHub too — OPS76)." |
**Classe (b) — BORDA (decisão do gate: incluir no mesmo diff):**
| # | Arquivo:linha | Atual | Alvo |
| --- | --------------------------------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| b1 | `README.md:116` | "Quality gates (also enforced by CI on the Forgejo Actions)" | "Quality gates (also enforced by CI on GitHub Actions — `ci-pr.yml`)" |
| b2 | `scripts/lib/agent-forgejo.mjs:3` | "through the Forgejo REST API (scripts/lib/forgejo-api.mjs)" | "through the GitHub REST API (scripts/lib/github-api.mjs)" |
| b3 | `scripts/lib/agent-forgejo.mjs:7` | "Issue contract (spec + status + deps in Forgejo Issues, per the parallel" | "Issue contract (spec + status + deps in GitHub Issues, per the parallel" |
| b4 | `scripts/issue-transition-on-merge.mjs:40` | "Usage: node scripts/forgejo-issue-transition.mjs --pr <N>" | "Usage: node scripts/issue-transition-on-merge.mjs --pr <N>" (nome morto da era pré-rename — bug de doc no usage) |
| b5 | `scripts/issue-transition-on-merge.mjs:51,55,64,80,84,90` | prefixo de log `[forgejo-issue-transition]` (6 ocorrências) | prefixo `[issue-transition]` (6 ocorrências) |
| b6 | `scripts/agent-status.mjs:26` | `die("Forgejo issue list failed: ${error.message}")` | `die("GitHub issue list failed: ${error.message}")` |
| b7 | `scripts/lib/cli.mjs:34` | "names of the self-hosted Forgejo runner (OPS62 X1:" | "names of the self-hosted GitHub Actions runner (OPS62 X1:" |
| b8 | `scripts/lib/cli.mjs:50` | "self-hosted Forgejo runner publishes each job's `services:` Postgres" | "self-hosted GitHub Actions runner publishes each job's `services:` Postgres" |
| b9 | `scripts/lib/github-api.mjs:9` | "Retry (same policy as forgejo-api OPS67)" | "Retry (OPS67 policy)" (ref a lib removida; sem comportamento) |
| b10 | `scripts/lib/github-api.mjs:183` | "normalized to the agent-script contract (same as forgejo-api)." | "normalized to the agent-script contract." |
| b11 | `scripts/lib/github-api.mjs:211` | "filter them out (the Forgejo `type: issues` query had no GitHub equivalent)." | "filter them out (the GitHub issues endpoint mixes PRs; there is no `type` filter)." |
## Fases verificáveis
**Fase única — edição de prosa (23 alvos):** aplicar a lista acima in place (edição linha-a-linha; nenhum arquivo reescrito inteiro). Não há ordem de dependência entre alvos; commits agrupam por arquivo na branch `OPS91-<slug>`.
**Verificação (mesma fase):**
1. Grep de saneamento (com as exclusões FORA): zero matches vivos em superfícies não-congeladas de `git.solla.dev/fsolla/teqo`, `tracker stays on the Forgejo`, `no Forgejo`, `FORGEJO_API_TOKEN` (como instrução ativa — `AGENT-OPS.md:95` e runbook seguem como refs históricas/legado), `Forgejo Actions`, `forgejo-api` (comentários de lib), `[forgejo-issue-transition]`, `Forgejo issue list failed`.
2. `pnpm gate:fast` (lint/format cobrem os `.md`/`.mdc`/`.mjs` editados; nenhum string é pinado em spec — verificado).
3. `pnpm changelog:check` após registrar a entrada `docs/changelog/2026-08-24-ops91.md` + `pnpm changelog:build` (insert-only — o diff será só a entrada nova + o agregado).
## Rabbit holes / Não escopo (engenharia)
- Rename de pureza de `scripts/lib/agent-forgejo.mjs` (e `agent-pool-forgejo.mjs`) — fora de escopo declarado na intenção (score 1, churn alto).
- Reabrir histórico: `docs/plans/*`, `docs/changelog/*`, `docs/CHANGELOG-AGENTS.md`, `docs/ops/teqo-1313-deploy.md` (runbook de rollback cita o Forgejo de propósito), marcações "legado/removido/congelado" já corretas (`AGENT-OPS.md:95`, `agent-pr-workflow.mdc:60,85`), skill `agent-pool` dormente.
- Reescrever os scripts que já falam GitHub (`issue-transition-on-merge.mjs` além dos 7 alvos de prosa) — zero mudança de comportamento.
## Riscos e mitigação
- **Lint/format em `.mdc`/`.md`:** gates cobrem; se Prettier divergir no alvo (linha longa no `README.md:9` reescrita), `pnpm format` antes do push.
- **docs-guards de changelog:** entrada OPS91 obrigatória antes do push (guard append-only); sem ela o push falha — passo explícito na verificação.
- **Veto do gate no BORDA:** se o humano vetar, descartar os 11 alvos b\* — os arquivos são independentes, o diff da classe a fecha sozinho; registrar no comentário da PR.
- **Deriva de fraseado entre camadas:** mitigada espelhando `AGENTS.md:15` nos alvos em inglês de camada AGENTS (a8, a1) e mantendo link/URL iguais nos alvos em pt (a7, a11).
## Aceite de engenharia
- [ ] Aceite de produto da intenção coberto: 23 alvos corrigidos; grep de saneamento sem ref viva a tracker/API do Forgejo fora das classes FORA
- [ ] Invariantes AGENTS/engineering-standards: edição in place, dono por arquivo, zero comportamento alterado, zero twin
- [ ] Testes de domínio previstos (unit/int): N/A — prosa; verificação por grep + `pnpm gate:fast` verde
- [ ] Changelog `docs/changelog/2026-08-24-ops91.md` + `pnpm changelog:build` + `changelog:check`
**Self-score decision-quality: 5/5** — (1) decisões caras (BORDA no diff, roadmap stub, fraseado canônico) têm opções e rejeitadas registradas; (2) cabe no appetite (prosa, 23 edições pontuais, fase única); (3) rabbit holes nomeados (rename de pureza, histórico, scripts já corretos); (4) depth check: zero arquivo/helper novo, edita o dono existente; (5) aceite da intenção permanece satisfeito — reescrita nunca reescreveu o outcome, só o corrigiu.

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:** [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`.
- **Fonte canônica de spec/status/deps/prio/modelo:** [Issues do GitHub](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`.
- **Histórico e entregas:** [`docs/changelog/`](changelog/) (registro por-arquivo) + [`docs/plans/`](plans/) — leitura agregada sob demanda: `pnpm changelog:read` (ou `pnpm changelog:build`, gitignored).
- **Conteúdo integral deste roadmap:** preservado no git history (última versão viva no commit imediatamente anterior a este stub).

View file

@ -23,7 +23,7 @@ let issues
try {
issues = await api.listIssues({ state: 'all', limit: 200 })
} catch (error) {
die(`Forgejo issue list failed: ${error.message}`)
die(`GitHub issue list failed: ${error.message}`)
}
const rows = issues

View file

@ -1,7 +1,7 @@
/**
* Plain-Node CLI (no pnpm) — create a GitHub PR (Ready, never draft) for
* agent deliveries. Replaces the Forgejo MCP `create_pull_request` in the
* delivery flow (OPS71: PRs live on GitHub; the tracker stays on Forgejo).
* delivery flow (OPS71: PRs live on GitHub; the tracker lives on GitHub too — OPS76).
*
* GITHUB_TOKEN=<PAT> node scripts/github-pr.mjs \
* --head <branch> --title "<título>" --body-file <path>

View file

@ -37,7 +37,7 @@ const parseArgs = (argv) => {
const flags = parseArgs(process.argv)
const prNumber = Number(flags.pr)
if (!Number.isInteger(prNumber) || prNumber <= 0) {
console.error('Usage: node scripts/forgejo-issue-transition.mjs --pr <N>')
console.error('Usage: node scripts/issue-transition-on-merge.mjs --pr <N>')
process.exit(1)
}
@ -48,11 +48,11 @@ const pr =
? { merged: true, body: bodyFromEnv }
: await api.getPullRequest(prNumber)
if (!pr) {
console.error(`[forgejo-issue-transition] PR #${prNumber} não encontrada`)
console.error(`[issue-transition] PR #${prNumber} não encontrada`)
process.exit(1)
}
if (!pr.merged) {
console.log(`[forgejo-issue-transition] PR #${prNumber} não mergeada — skip`)
console.log(`[issue-transition] PR #${prNumber} não mergeada — skip`)
process.exit(0)
}
@ -61,7 +61,7 @@ const numbers = [
]
if (numbers.length === 0) {
console.log(`[forgejo-issue-transition] PR #${prNumber}: nenhum Closes/Fixes #N — nada a fazer`)
console.log(`[issue-transition] PR #${prNumber}: nenhum Closes/Fixes #N — nada a fazer`)
process.exit(0)
}
@ -77,17 +77,17 @@ for (const number of numbers) {
// (`Closes #N`) só acontece para issues AINDA abertas citadas no body —
// o flip de labels (done/in-prod/remover in-progress) é explícito aqui.
await api.closeIssue(number)
console.log(`[forgejo-issue-transition] #${number}: done + in-prod + fechada`)
console.log(`[issue-transition] #${number}: done + in-prod + fechada`)
} catch (error) {
flipFailed = true
console.error(
`[forgejo-issue-transition] #${number}: FLIP FALHOU — ${error instanceof Error ? error.message : error}`,
`[issue-transition] #${number}: FLIP FALHOU — ${error instanceof Error ? error.message : error}`,
)
}
}
if (flipFailed) {
console.error(
`[forgejo-issue-transition] PR #${prNumber}: ${numbers.length} Issue(s) citada(s), pelo menos 1 flip falhou — os labels de status podem estar mentindo.`,
`[issue-transition] PR #${prNumber}: ${numbers.length} Issue(s) citada(s), pelo menos 1 flip falhou — os labels de status podem estar mentindo.`,
)
process.exit(1)
}

View file

@ -1,10 +1,10 @@
/**
* Shared helpers for the agent ops scripts (`pnpm agent:*`). Everything goes
* through the Forgejo REST API (scripts/lib/forgejo-api.mjs) — agents never
* through the GitHub REST API (scripts/lib/github-api.mjs) — agents never
* get a token of their own beyond the env, and these scripts never touch
* stage/prod database URLs.
*
* Issue contract (spec + status + deps in Forgejo Issues, per the parallel
* Issue contract (spec + status + deps in GitHub Issues, per the parallel
* paradigm plan): each trackable issue carries a YAML-ish frontmatter block
* at the top of the body:
*

View file

@ -31,9 +31,10 @@ export const loadCliEnv = () => {
/**
* Local database hosts — docker-compose service name, the job-network service
* names of the self-hosted Forgejo runner (OPS62 X1: services are reached by
* name, no host publish), and the bridge gateway (OPS50: legacy per-port
* publish kept in the allowlist). THE one list.
* names of the Forgejo-era runner (OPS62 X1: services by name, no host publish —
* the GitHub hosted CI reaches services on published localhost ports, OPS71),
* and the bridge gateway (OPS50: legacy per-port publish kept in the
* allowlist). THE one list.
*/
export const LOCAL_HOSTS = new Set([
'localhost',
@ -46,12 +47,15 @@ export const LOCAL_HOSTS = new Set([
])
/**
* Gateway of the default route, read from /proc/net/route (Linux). The
* self-hosted Forgejo runner publishes each job's `services:` Postgres on the
* bridge gateway IP (192.168.x.1 — differs per job), so a job reaches it via
* that IP instead of localhost. The same octet order the ci-pr.yml awk
* produces (the /proc fields are little-endian). Returns null when there is
* no default route or the file is unreadable (macOS/Windows dev boxes).
* Gateway of the default route, read from /proc/net/route (Linux). In the
* Forgejo-era CI the self-hosted runner published each job's `services:`
* Postgres on the bridge gateway IP (192.168.x.1 — differs per job), so a job
* reached it via that IP instead of localhost; the GitHub hosted CI (OPS71)
* reaches services on published localhost ports instead, and the bridge
* gateway stays in the allowlist for Docker-based runners. The same octet
* order the ci-pr.yml awk produces (the /proc fields are little-endian).
* Returns null when there is no default route or the file is unreadable
* (macOS/Windows dev boxes).
*/
export const defaultGatewayHost = () => {
try {

View file

@ -6,7 +6,7 @@
* Auth: `GITHUB_TOKEN` (PAT locally; the built-in Actions token in
* workflows). Base URL: `GITHUB_API_URL`, else `https://api.github.com`.
*
* Retry (same policy as forgejo-api OPS67): transient failures retry with
* Retry (OPS67 policy): transient failures retry with
* exponential backoff — a fetch that rejects (network-level, DNS/TCP/reset)
* retries on ANY method; a 5xx retries on GET only, so read polls survive
* while write endpoints keep failing closed instead of risking duplicated
@ -180,7 +180,7 @@ export const createApi = ({
base: { ref: pr.base?.ref ?? '' },
})
/** Issue shape normalized to the agent-script contract (same as forgejo-api). */
/** Issue shape normalized to the agent-script contract. */
const normalizeIssue = (issue) => ({
number: issue.number,
title: issue.title,
@ -208,8 +208,8 @@ export const createApi = ({
const issues = await request(`/repos/${owner}/${name}/issues`, {
query: { state, labels, per_page: limit, page },
})
// GitHub returns PRs in the issues list; filter them out (the Forgejo
// `type: issues` query had no GitHub equivalent).
// GitHub returns PRs in the issues list; filter them out (the GitHub
// issues endpoint mixes PRs; there is no `type` filter).
return (Array.isArray(issues) ? issues : [])
.filter((issue) => !issue.pull_request)
.map(normalizeIssue)