From fb7fc83c564a454f5f5a6064f0526ec440dfa611 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Wed, 19 Aug 2026 20:56:13 -0300 Subject: [PATCH] =?UTF-8?q?feat(OPS72):=20e2e=20local=20afetado=20discrici?= =?UTF-8?q?on=C3=A1rio=20na=20skill;=20CI=20de=20PR=20s=C3=B3=20blast=20ra?= =?UTF-8?q?dius=20(nunca=20full)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ci-pr.yml: step e2e full removido; selected = único modo no PR (diff high-risk não roda e2e — espelho local + deploy verify cobrem) - skills work-issue/agent-work-issue + execution-pipeline: passo e2e local afetado (discricionário) + limitação #72 documentada - gate-ci.mjs: mensagens informativas (texto-only) - docs AGENTS/AGENT-OPS/agent-pr-workflow.mdc + changelog OPS44 --- .agents/rules/agent-pr-workflow.mdc | 2 +- .agents/skills/agent-work-issue/SKILL.md | 7 +- .agents/skills/work-issue/SKILL.md | 7 +- .../skills/work-issue/execution-pipeline.md | 30 +++++++- .github/workflows/ci-pr.yml | 24 +++--- AGENTS.md | 2 +- docs/AGENT-OPS.md | 5 +- docs/CHANGELOG-AGENTS.md | 2 + docs/changelog/2026-08-19-ops72.md | 1 + ...ps72-e2e-local-afetado-ci-sem-full-impl.md | 76 +++++++++++++++++++ scripts/gate-ci.mjs | 20 +++-- 11 files changed, 150 insertions(+), 26 deletions(-) create mode 100644 docs/changelog/2026-08-19-ops72.md create mode 100644 docs/plans/ops72-e2e-local-afetado-ci-sem-full-impl.md diff --git a/.agents/rules/agent-pr-workflow.mdc b/.agents/rules/agent-pr-workflow.mdc index bea459f1..dbbec9db 100644 --- a/.agents/rules/agent-pr-workflow.mdc +++ b/.agents/rules/agent-pr-workflow.mdc @@ -38,7 +38,7 @@ Closes # **Use `pnpm push`, never bare `git push`.** Não depende do Husky estar wired: 1. `ensure-repo-deps` — `pnpm install` se `node_modules` faltar/stale + `pnpm run prepare` (Husky) se hooks não estiverem apontando para `.husky` -2. `pnpm gate:push` — espelho do `ci-pr.yml` sem e2e (e2e roda no CI de PR selected/afetado, e full no `verify` do deploy manual; local opcional: `pnpm test:e2e:affected`), com os `docs-guards` locais (OPS63: changelog append-only, agregado sync e marcadores de conflito — o escape `changelog-rewrite:` fica só no CI, body do PR) +2. `pnpm gate:push` — espelho do `ci-pr.yml` sem e2e (OPS72: o e2e é passo **explícito da skill** — antes do push rode localmente os e2e que criou + os da mesma superfície, discricionário via `pnpm test:e2e:affected`; o CI de PR roda só o blast radius `selected`, e o full fica no `verify` do deploy manual), com os `docs-guards` locais (OPS63: changelog append-only, agregado sync e marcadores de conflito — o escape `changelog-rewrite:` fica só no CI, body do PR) 3. `git push --no-verify` — só porque o gate já rodou no passo 2 (não é escape hatch) ```bash diff --git a/.agents/skills/agent-work-issue/SKILL.md b/.agents/skills/agent-work-issue/SKILL.md index 7d0ea9af..bc253666 100644 --- a/.agents/skills/agent-work-issue/SKILL.md +++ b/.agents/skills/agent-work-issue/SKILL.md @@ -33,7 +33,7 @@ Conduz **uma** Issue já claimada do plano de implementação ao merge em `main` - [ ] 1. Sessão: rename_chat + open_resource na intenção - [ ] 2. Modelo: spawn do pool já fixou (`model:` da Issue) — sem comparação - [ ] 3. Plan mode → plano de implementação (`*-impl.md`) -- [ ] 4. Executar o impl plan (schema/server → UI → gates) +- [ ] 4. Executar o impl plan (schema/server → UI → e2e local afetado → gates) - [ ] 5. /simplify → fixes; capture-review-debts (modo autônomo) - [ ] 6. PR Ready `--base main` (GitHub) + auto-merge nativo — `pnpm push -u origin HEAD` → `node scripts/github-pr.mjs` (ou `ManagePullRequest` `draft: false` no Cloud) ``` @@ -79,6 +79,11 @@ O spawn do pool já fixou o modelo (`model:` da Issue — ver `model-selection`) (executar → simplify → fechar em main), com os deltas do ator pool: - **Branch:** `agent/-` (worktrees Cursor podem já ter criado). +- **E2E local afetado (OPS72):** mesmo passo discricionário do ator humano + (rodar os e2e criados + da mesma superfície — ver `execution-pipeline.md`); + **worktree com browsers Playwright** → rode; **Cursor Cloud sem browsers** → + registre a justificativa no fechamento e siga — o CI de PR roda o blast + radius (selected) como rede. - **UI:** shape → craft → critique → polish (harden/optimize só sob gatilho). - **`capture-review-debts`:** **autônomo** — só `expensive_lock` com score ≥4 (Issues novas com `depends: []` se necessário); score ≤3 / diff --git a/.agents/skills/work-issue/SKILL.md b/.agents/skills/work-issue/SKILL.md index 01941312..e00a3476 100644 --- a/.agents/skills/work-issue/SKILL.md +++ b/.agents/skills/work-issue/SKILL.md @@ -36,7 +36,7 @@ pular a pausa do impl plan; Draft / sem auto-merge. - [ ] 1. Contexto: Issue do prompt/`$ARGUMENTS` da sessão (ausente → UMA pergunta com validação; nunca claim) - [ ] 2. Abrir o plano de intenção do body da Issue (`Plano: docs/plans/...`; sem link → body é spec) - [ ] 3. Plan mode → escrever `docs/plans/-impl.md` → **PARAR e confirmar com o humano** -- [ ] 4. Após “ok”: Passo 4 (execução → /simplify → débitos autônomos → PR, via `execution-pipeline.md`) +- [ ] 4. Após “ok”: Passo 4 (execução → e2e local afetado → /simplify → débitos autônomos → PR, via `execution-pipeline.md`) ``` ## Passo 1 — Contexto da sessão @@ -96,6 +96,11 @@ nunca invente produto novo. fechar em main), com os deltas do ator humano: - **Branch:** `-` do worktree — nunca crie branch nova na sessão. +- **E2E local afetado (OPS72, discricionário):** antes do push, rode + localmente os e2e que você **criou** + os da **mesma superfície afetada** — + você decide quais (`pnpm test:e2e:affected` ou specs diretas). Não está no + `gate:push`; ver a seção "E2E local afetado" do `execution-pipeline.md` + (incl. a limitação da #72: `--no-deps` + projetos paralelos → `--workers=1`). - **UI:** shape → craft → critique → polish. - **`capture-review-debts`:** **autônomo** — colha, dedupe, pontue e **decida você mesmo o destino** (registrar/absorver/deferir/descartar) pela triage diff --git a/.agents/skills/work-issue/execution-pipeline.md b/.agents/skills/work-issue/execution-pipeline.md index 51395140..437770b7 100644 --- a/.agents/skills/work-issue/execution-pipeline.md +++ b/.agents/skills/work-issue/execution-pipeline.md @@ -12,12 +12,40 @@ Ordem: testes de domínio. Invariantes do engineering-brief. 2. **UI** — se Impeccable B/C/D: shape → craft → critique → polish. Tokens `data-theme='campaign'`; shells existentes. -3. **Gates** — `pnpm gate:fast` na iteração; entrega com `pnpm push` (não +3. **E2E local afetado (OPS72, discricionário)** — antes do push, rode + localmente os e2e que você **criou** + os da **mesma superfície afetada** + (você decide quais). Ver §E2E local afetado. +4. **Gates** — `pnpm gate:fast` na iteração; entrega com `pnpm push` (não `git push` nu). Tracer bullet cedo se o item for grande. Inclua o `*-impl.md` no commit da entrega. +## E2E local afetado (OPS72) + +Política de e2e: **local = discricionário, CI/PR = blast radius, main = full +antes do deploy**. + +- **O que rodar:** os specs e2e que o diff **criou** (novos `.e2e.spec.ts`) + + os da mesma superfície trabalhada (o `E2E_AFFECTED_MANIFEST` mapeia + `src/` → specs; a mecânica do CI é a mesma). **É discricionário: você + decide quais rodar** — não é um gate mecânico e não está no `gate:push`. +- **Ferramenta:** `pnpm test:e2e:affected` (espelho local do classifier: + modo `none`/`selected`/`full` conforme o diff; roda `migrate` + + `db:seed:minimal` antes). Alternativa para specs diretas: + `pnpm test:e2e --no-deps -- tests/e2e/.e2e.spec.ts` (ou + `--no-deps --project=`). +- **CI/PR:** roda só o **blast radius** detectado (`ci-scope.mjs`, modo + `selected`) — nunca `full`. Diff high-risk (schema/lockfile/harness) + classifica `full` → o PR não roda e2e: o full fica para o `verify` do + deploy manual (antes de publicar) e, nesse mesmo diff, o espelho local + roda full. +- **Limitação da #72 (S3-FOLLOWUP):** e2e local com `--no-deps` + projetos + paralelos (`--project=a --project=b`) colide no `seedTestUser` (delete + + create concorrentes nos `beforeAll` → `ValidationError: email` e falhas + fantasma). Use `--workers=1` ou a cadeia padrão de projetos. Só afeta o + local — no CI os projetos rodam sequenciais por construção. + ## /simplify + débitos 1. Rode o comando `/simplify` completo (3 reviewers paralelos via Task, diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index b8849ca6..1cd98be1 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -14,8 +14,12 @@ # # Job-level skips come from the `scope` classifier (scripts/ci-scope.mjs) run # as a step; its outputs drive `if:` on the heavy steps (same modes as the -# Forgejo cascade: none/changed/full). e2e policy: selected (affected) specs -# on PRs — OPS72 tunes the local-affected flow afterwards. +# Forgejo cascade: none/changed/full). e2e policy (OPS72): the PR runs ONLY +# the blast radius — the specs the classifier detected as touched (mode +# `selected`), never `full`. A high-risk diff (schema/lockfile/test harness) +# classifies as `full` → the PR simply runs no e2e: the local agent mirror +# (`pnpm test:e2e:affected`) runs full in that same case, and the deploy +# `verify` job runs the full suite before publishing. # # Schema migrations are NOT serialized across PRs (migration-lock removed # 2026-08-12): a stale chain is caught by `pnpm migrate` + `test:int` on every @@ -209,17 +213,15 @@ jobs: run: pnpm build env: NEXT_DIST_DIR: .next-e2e - - name: E2E tests (full suite, single job, 4 workers) - if: steps.scope.outputs.e2e_mode == 'full' + - name: E2E tests (blast radius — manifest-selected specs) + if: steps.scope.outputs.e2e_mode == 'selected' + # OPS72: the PR never runs e2e full — mode `full` (high-risk diff) + # skips this step by design (local mirror + deploy verify cover it). # Explicit CI workers; the local default stays 2 (playwright.config.ts). # 4 workers in one process replace the former 2 shards × 2 workers. - # No `--` separator: pnpm would forward it verbatim to playwright and - # the flag would be parsed as a file pattern ("No tests found"). - env: - PLAYWRIGHT_WORKERS: 4 - run: pnpm test:e2e - - name: E2E tests (manifest-selected specs) - if: steps.scope.outputs.e2e_mode == 'selected' env: PLAYWRIGHT_WORKERS: 4 + # No `--` separator before the scope paths: pnpm would forward it + # verbatim to playwright and the flag would be parsed as a file + # pattern ("No tests found"). run: pnpm test:e2e -- ${{ steps.scope.outputs.e2e_specs }} diff --git a/AGENTS.md b/AGENTS.md index aa98a661..e3600366 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -158,7 +158,7 @@ The artifact is at **v3**: v2 added E8's `campoFederalVotesByYear` / `federalTal 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` / `teqo_wt_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 — **com as envs `S3_*` setadas (valores dummy ok) enquanto o plugin `s3Storage` for condicional: sem elas o importMap fica órfão do `S3ClientUploadHandler` e o admin abre em branco em produção (OPS69, P0 em 2026-08-19)** —, `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 **no CI** — no PR (selected/afetado, via `ci-scope.mjs`) e **full no `verify` do deploy manual** (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 -- ` ou `--no-deps --project=`). +3. Run `pnpm generate:types`, `pnpm generate:importmap` when components changed — **com as envs `S3_*` setadas (valores dummy ok) enquanto o plugin `s3Storage` for condicional: sem elas o importMap fica órfão do `S3ClientUploadHandler` e o admin abre em branco em produção (OPS69, P0 em 2026-08-19)** —, `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. **Política de e2e (OPS72):** o e2e **não roda no gate local** (`gate:push`) — é passo **explícito da skill** (`work-issue`/`agent-work-issue`): antes do push o agente roda localmente os e2e que **criou** + os da **mesma superfície afetada** — **discricionário** (o agente decide quais; ferramenta: `pnpm test:e2e:affected`, espelho local do classifier — 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 -- ` ou `--no-deps --project=`; limitação #72: `--no-deps` + projetos paralelos colide no `seedTestUser` → `--workers=1`). O **CI de PR roda apenas o blast radius** (modo `selected` via `ci-scope.mjs` — **nunca `full`**; diff high-risk classifica `full` e o PR simplesmente não roda e2e). O e2e **full** roda no `verify` do deploy manual (gateia merge e deploy — rede de segurança final antes de publicar). - 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 `) before deleting. - Run gate commands bare — never piped (`pnpm test | tail` swallows the exit code, and a real failure was masked exactly this way during Pass 2). Large outputs are captured by the terminal; read them from there. 4. Scan every edited runtime source file with Aikido and resolve new findings before handoff. Do not expand hardening work into unrelated pre-existing findings without an explicit scope change. diff --git a/docs/AGENT-OPS.md b/docs/AGENT-OPS.md index b02e8217..8320d5de 100644 --- a/docs/AGENT-OPS.md +++ b/docs/AGENT-OPS.md @@ -20,7 +20,7 @@ main → (opcional, deliberado) workflow_dispatch deploy.yml → verify full sui **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` (GitHub) → `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`** (origin = GitHub) → PR via `node scripts/github-pr.mjs` **Ready** (nunca draft) com `Closes #N` → 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. 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. +- **Agente faz sozinho:** claim (pool-supervisor no pool; humano via `pnpm agent:claim` fora da sessão — OPS33: `worktree next` claima) → implementa → **e2e local afetado** (OPS72, discricionário: roda os e2e que criou + os da mesma superfície — `pnpm test:e2e:affected`; ver skill) → **`pnpm push`** (origin = GitHub) → PR via `node scripts/github-pr.mjs` **Ready** (nunca draft) com `Closes #N` → 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. 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), `GITHUB_TOKEN` local (PAT) para os scripts GitHub, secrets no repo GitHub, `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 @@ -72,13 +72,12 @@ Arquivos de "lista" que N agentes escrevem em paralelo (changelog, plans, migra | Workflow | Trigger | Banco | Passos | | ------------------------------------ | --------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ci-pr.yml` | PR → `main` (same-repo) | 2× service Postgres 17 | Job único `checks` sequencial (OPS62 X1): guards → lint→format→typecheck→knip→cycles→unit→int→build→e2e; skips via `ci-scope.mjs` (step `scope`); **fail-fast estrutural** — step vermelho interrompe o job, o run falha; `concurrency` nativo cancela o run em push novo; e2e 1 processo com `PLAYWRIGHT_WORKERS=4`; 2 services por nome na rede do job (`postgres-int`/`postgres-build`, sem publish host); fork-PR excluída no `if:` do job (repo público — não queima minutes) | +| `ci-pr.yml` | PR → `main` (same-repo) | 2× service Postgres 17 | Job único `checks` sequencial (OPS62 X1): guards → lint→format→typecheck→knip→cycles→unit→int→build→e2e; skips via `ci-scope.mjs` (step `scope`); **fail-fast estrutural** — step vermelho interrompe o job, o run falha; `concurrency` nativo cancela o run em push novo; e2e **apenas blast radius** (`selected`, nunca `full` — OPS72) em 1 processo com `PLAYWRIGHT_WORKERS=4`; 2 services por nome na rede do job (`postgres-int`/`postgres-build`, sem publish host); fork-PR excluída no `if:` do job (repo público — não queima minutes) | | `deploy.yml` | **`workflow_dispatch` manual** | 2× service Postgres 17 | **Dois jobs:** `verify` (hosted, suíte **full** sem skips — lint→format→typecheck→knip→cycles→unit→int→build→e2e full) → `deploy` (`needs: [verify]`, `runs-on: [self-hosted, homeserver]`) executa `scripts/deploy-homeserver.sh ` **localmente** no homeserver (sem SSH; idempotente; HEAD guard; flock; migrator→migrate→runner→rollout→smoke) | | `issue-done-on-main-merge.yml` | PR merged → `main` (same-repo) + **dispatch manual (recovery)** | — | `Closes`/`Fixes` → `done` + `in-prod` + **close** **no Forgejo** (PAT `FORGEJO_API_TOKEN`; body do PR via `PR_BODY` do evento GitHub — no dispatch, busca via `gh` com o mesmo contrato: merged + main + same-repo) | | `plan-issue-ready-on-main-merge.yml` | PR merged → `main` (same-repo) + **dispatch manual (recovery)** | — | `Related #N` aguardando plano → `ready` **no Forgejo** (OPS18; soft-skip; mesmo PAT) | | `agent-pr-ready-automerge.yml` | PR same-repo → `main` (open/sync/…) | — | Safety net via `github-pr-automerge.mjs` (plain Node, REST+GraphQL): draft `cursor/*` → Ready (PATCH); arm `enablePullRequestAutoMerge` (rebase) **com o `AUTOMERGE_PAT`** (fail-closed — nunca o `GITHUB_TOKEN` nativo, cujo merge como `github-actions[bot]` não cria runs no `closed`; OPS71-FLIP) — o **servidor** espera o required check `CI (PR) / checks` (o OPS64 "rollup pode mentir" é estrutural aqui: job único posta um check-run honesto); 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). O verificador de main (`ci.yml`) foi **eliminado no OPS71** (gate 2026-08-19): só o `verify` do dispatch manual roda a suíte full. Action runtimes: `actions/checkout@v5`, `actions/setup-node@v5`, `pnpm/action-setup@v6` e `actions/cache@v4`. diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index 38e319ae..a4f1e807 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente --- +**OPS72** — política de e2e: **CI de PR sem e2e full; local afetado como passo da skill; full só antes do deploy**. Decisão do gate (2026-08-19): (1) **local = discricionário** — o agente roda os e2e que criou + os da mesma superfície afetada, decide quais (`pnpm test:e2e:affected` é a ferramenta, não um gate mecânico; não está no `gate:push`); (2) **CI de PR = blast radius** — `ci-pr.yml` perdeu o step `E2E tests (full suite)`; o e2e roda só em modo `selected` (specs que o classifier detectou como tocadas); diff high-risk (schema/lockfile/harness) classifica `full` e o PR simplesmente não roda e2e — deliberado: o espelho local roda full nesse mesmo diff e o `verify` do deploy roda full antes de publicar; (3) **main = full no deploy** — inegociável, já existia no `deploy.yml` (nada mudou lá). Docs: `work-issue` e `agent-work-issue` (checklist + Passo 4), `execution-pipeline.md` (nova seção "E2E local afetado" com o critério + a **limitação da #72**: `--no-deps` + projetos paralelos colide no `seedTestUser` → `--workers=1` ou a cadeia padrão de projetos), `AGENTS.md` (parágrafo OPS59 → política OPS72), `docs/AGENT-OPS.md` (fluxo do agente + linha do `ci-pr.yml` na tabela), `.agents/rules/agent-pr-workflow.mdc` (descrição do `gate:push`), mensagens informativas do `gate-ci.mjs` (texto-only — e2e continua fora do gate) e comentários do workflow. Mecânica intocada por escopo (`ci-scope.mjs`, manifest, `run-e2e-affected.mjs`); o fix da #72 fica na fila (só documentado). Verificação: format/lint/tsc/unit verdes; `test:e2e:affected` com modo `none` (diff sem blast radius e2e); CI do PR valida (e2e não roda — docs/skills/workflow não são superfície e2e). Impl plan: `docs/plans/ops72-e2e-local-afetado-ci-sem-full-impl.md`. + **OPS71-INFRA (2026-08-19):** runner **self-hosted do GitHub instalado no homeserver** (Issue #113) — o job `deploy` do `deploy.yml` (dispatch manual) agora roda localmente no homeserver, sem SSH. Detalhes: `~/actions-runner` v2.336.0 linux-x64 (sha256 conferido contra a API de releases), nome `teqo-1313-runner`, labels `self-hosted` + `homeserver`, registrado com o PAT do OPS71 (registration token TTL 1h gerado e aplicado na sequência). Roda como **systemd user** `actions.runner.fsolla-teqo.teqo-1313-runner.service` (linger já ativo; `svc.sh install` exige root — unit user criado manualmente do template oficial), usuário `fsolla` (docker + `~/stack` envs), workdir `_work`, outbound-only (funciona atrás do Cloudflare tunnel). Verificado `online` na API do GitHub com as labels corretas. Registrado em infra-solla `STATE.md` (seção Teqo: runner + fluxo deploy manual atualizado; linha do repo/CI corrigida para o pós-cutover GitHub). Workspace `~/teqo-deploy` re-aponta para o GitHub no primeiro deploy via `set-url` idempotente do script. Pendência registrada: secret `CURSOR_API_KEY` no GitHub fica dormente (helper `archive-cursor-agent.yml`; a key não existe no ambiente — pool morto OPS65) até o usuário pedir a reativação. **S7** — polimento dos cards da seção "Acompanhe de perto" da home de campanha (jorgesolla1313.com.br), look editorial e direto no lugar do "card de app": cantos retos (**sem `rounded-xl` no card e `rounded-lg` na capa**), capa de **borda a borda** (o `p-3` saiu do contêiner do card e foi para o bloco de texto — título/subtítulo/meta continuam respirando `p-3` das bordas; `mt-auto` da meta e `overflow-hidden` do zoom hover intactos) e, por decisão do humano posterior ao gate, **sem borda (`border --campaign-line`) e sem sombra** (`shadow-sm`/`hover:shadow-md`) — o card branco lê contra a banda do S5 só pela forma. **Chips de badge removidos de todos os cards** (decisão B do gate): nem os tipos de post do site (`POST_TYPE_BADGE_LABELS` deletado de `src/utilities/posts.ts`, junto com o describe no unit test) nem as origens externas YouTube/Instagram (sinalização externa segue por `target="_blank"`/`rel="noopener"`); `badgeLabel` saiu do tipo `CampaignContentCardData`. Superfície única no componente dono (`CampaignContentCard.tsx`) — bento e carrossel herdam; `ContentShareButton` (S4), banda do S5, carrossel e fail-closed da seção intocados. Zero migration/schema/Consent. e2e do describe da seção atualizados para o novo estado: loop de badges → ausência (`toHaveCount(0)` para os 4 tipos + YouTube/Instagram), pins CSS do card (`border-radius`/`border-width`/`box-shadow` + raio da capa) e pin geométrico da capa de borda a borda (boundingBox do card × capa). Verificação: gate:fast verde, unit 2415 + int 791 verdes, e2e frontend 19/19 local, format/knip/cycles limpos. Plano de implementação: `docs/plans/cards-acompanhe-de-perto-polimento-impl.md`. diff --git a/docs/changelog/2026-08-19-ops72.md b/docs/changelog/2026-08-19-ops72.md new file mode 100644 index 00000000..4e020e87 --- /dev/null +++ b/docs/changelog/2026-08-19-ops72.md @@ -0,0 +1 @@ +**OPS72** — política de e2e: **CI de PR sem e2e full; local afetado como passo da skill; full só antes do deploy**. Decisão do gate (2026-08-19): (1) **local = discricionário** — o agente roda os e2e que criou + os da mesma superfície afetada, decide quais (`pnpm test:e2e:affected` é a ferramenta, não um gate mecânico; não está no `gate:push`); (2) **CI de PR = blast radius** — `ci-pr.yml` perdeu o step `E2E tests (full suite)`; o e2e roda só em modo `selected` (specs que o classifier detectou como tocadas); diff high-risk (schema/lockfile/harness) classifica `full` e o PR simplesmente não roda e2e — deliberado: o espelho local roda full nesse mesmo diff e o `verify` do deploy roda full antes de publicar; (3) **main = full no deploy** — inegociável, já existia no `deploy.yml` (nada mudou lá). Docs: `work-issue` e `agent-work-issue` (checklist + Passo 4), `execution-pipeline.md` (nova seção "E2E local afetado" com o critério + a **limitação da #72**: `--no-deps` + projetos paralelos colide no `seedTestUser` → `--workers=1` ou a cadeia padrão de projetos), `AGENTS.md` (parágrafo OPS59 → política OPS72), `docs/AGENT-OPS.md` (fluxo do agente + linha do `ci-pr.yml` na tabela), `.agents/rules/agent-pr-workflow.mdc` (descrição do `gate:push`), mensagens informativas do `gate-ci.mjs` (texto-only — e2e continua fora do gate) e comentários do workflow. Mecânica intocada por escopo (`ci-scope.mjs`, manifest, `run-e2e-affected.mjs`); o fix da #72 fica na fila (só documentado). Verificação: format/lint/tsc/unit verdes; `test:e2e:affected` com modo `none` (diff sem blast radius e2e); CI do PR valida (e2e não roda — docs/skills/workflow não são superfície e2e). Impl plan: `docs/plans/ops72-e2e-local-afetado-ci-sem-full-impl.md`. diff --git a/docs/plans/ops72-e2e-local-afetado-ci-sem-full-impl.md b/docs/plans/ops72-e2e-local-afetado-ci-sem-full-impl.md new file mode 100644 index 00000000..7e9bc271 --- /dev/null +++ b/docs/plans/ops72-e2e-local-afetado-ci-sem-full-impl.md @@ -0,0 +1,76 @@ +# Impl: E2E local apenas para os testes afetados — CI de PR sem e2e full + +Status: aprovado +Atualizado em: 2026-08-19 +Issue: #98 +Intenção: docs/plans/ops72-e2e-local-afetado-ci-sem-full.md +Appetite restante: ~0,5–1 dia eng (herdado) + +## Leitura da intenção + +- **Outcome:** (1) skill `work-issue` (e irmãs do fluxo) exige que o agente rode localmente **os e2e que criou + os da mesma superfície afetada** — **discricionário**: o agente decide quais; `pnpm test:e2e:affected` é a ferramenta, não um gate mecânico; (2) CI de PR roda o **blast-radius** (e2e que o classifier detecta como tocados — modo `selected`, nunca `full`); (3) e2e **full** roda na **main, antes do deploy** (`verify` do deploy manual — rede de segurança pré-publicação); (4) limitação da #72 documentada na skill. +- **O que NÃO negociar:** full no deploy/verify de main; gate local (`gate:push`) sem e2e (passo explícito da skill, não hook); mecânica do classifier/manifest intocada; CI selected (blast radius) mantido como rede — o local é discricionário por design, o CI não. +- **O que reavaliar:** hipótese de "`.github/workflows/ci.yml` (verificador de main)" — **não existe mais**: OPS71 eliminou o `ci.yml`; a suíte full vive só no job `verify` do `deploy.yml` (confirmado). A hipótese do ci-pr.yml já ter os dois steps e2e (`full` + `selected`) dirigidos por `ci-scope.mjs` se confirma — o trabalho é **remover o full** e reorientar o texto. + +## Abordagem recomendada + +```mermaid +flowchart LR + A[Diff do PR] --> B[ci-scope.mjs / e2e-affected.mjs] + B -->|selected| C[CI PR: E2E blast-radius manifest-selected specs] + B -->|full| D[CI PR: sem e2e - full vive no verify do deploy] + B -->|none| E[CI PR: sem e2e - sem blast radius] + F[Agente antes do push] --> G[e2e local dos specs criados + mesma superfície - discricionário] + G -->|decisão do agente| H[pnpm test:e2e:affected ou specs diretas] + I[main antes do deploy] --> J[verify: e2e full] +``` + +**Opções consideradas:** A | B | C +**Recomendação:** A — remover o step e2e `full` do `ci-pr.yml`; quando o classifier emitir `full` (diff high-risk: schema/lockfile/harness), o CI de PR simplesmente não roda e2e (o step `selected` tem `if: e2e_mode == 'selected'` e é pulado; build-for-e2e segue rodando). O espelho local (`run-e2e-affected.mjs`) roda **full localmente** nesse mesmo caso — mesma mecânica, sem gap: o único caminho para "full" é diff high-risk, e aí o agente viu o full na máquina dele e o `verify` do deploy roda full antes de publicar. Ciência: um diff high-risk sem e2e no CI é um risco deliberado (rabbit hole "full só no deploy cria gap de regressão" já aceito no gate). +**Rejeitadas:** B — manter o step `full` no CI (status quo): viola o gate "nunca full por PR" e devolve o custo 40+ min por PR de infra. C — mapear `full` → falha/aviso no CI: criaria bloqueio artificial em PRs legítimos de infra/schema (o diff high-risk é exatamente o que o local roda full); o verify do deploy já é o gate honesto. + +### Componentes / mudanças + +- **`.github/workflows/ci-pr.yml`**: remover o step `E2E tests (full suite, single job, 4 workers)`; atualizar o comentário do header ("e2e policy: selected (affected) specs on PRs — never full; full lives in deploy verify — OPS72"); o step `E2E tests (manifest-selected specs)` fica funcionalmente intacto. +- **`scripts/gate-ci.mjs`**: mensagens informativas do escopo e2e (texto-only, ~linhas 153–162) — "e2e verified in CI" vira "e2e selected no CI (PR); full só no `verify` do deploy; local: `pnpm test:e2e:affected` (passo da skill)". Comportamento intocado (e2e fora do gate — aceite). Mensagens não são pinadas por unit tests (verificado). +- **`.agents/skills/work-issue/execution-pipeline.md`** (corpo compartilhado das duas skills): seção "Executar" item 3 (Gates) ganha o passo **e2e local (discricionário)** antes do `pnpm push`; nova seção curta "E2E local afetado" documentando: o agente roda **os specs e2e que criou + os da mesma superfície afetada**, decidindo quais (discricionário); a ferramenta (`pnpm test:e2e:affected` — espelho do classifier; ou specs diretas via `--no-deps`), e a **limitação da #72** (`--no-deps` + projetos paralelos colidem no `seedTestUser` → `--workers=1` ou a cadeia padrão de projetos; só conveniência local, CI é sequencial por construção). +- **`.agents/skills/work-issue/SKILL.md`**: checklist item 4 e Passo 4 ganham o passo (humano): "rode localmente os e2e que você criou + os da mesma superfície afetada — você decide quais (`pnpm test:e2e:affected`)". +- **`.agents/skills/agent-work-issue/SKILL.md`**: delta pool — mesmo passo **quando o worktree tiver browsers Playwright**; Cursor Cloud sem browser → registra justificativa e segue (CI selected é a rede) — opção B da intenção com registro, não silêncio. +- **`AGENTS.md`**: reescrever o parágrafo "O e2e não roda mais no gate local (OPS59)" → política OPS72: e2e afetado local = passo obrigatório da skill; CI de PR = selected; full = `verify` do deploy manual. +- **`docs/AGENT-OPS.md`**: item "Agente faz sozinho" ganha o passo e2e local afetado; linha do `ci-pr.yml` na tabela CI passa a "e2e selected (nunca full — OPS72)". +- **`.agents/rules/agent-pr-workflow.mdc`**: linha 41 (`pnpm gate:push` descrição) reflete a política nova. +- **Migration:** sem migration. **Access / Consent:** n/a. **UI:** Impeccable A — sem UI. +- **Changelog (OPS44):** `docs/changelog/2026-08-19-ops72.md` + `pnpm changelog:build` + `changelog:check`. + +### Dados → forma (se aplicável) + +- N/a — não apresenta dados; apenas política/CI/skills. + +## Fases verificáveis + +1. **CI** — `.github/workflows/ci-pr.yml`: step full removido + comentários; `scripts/gate-ci.mjs`: mensagens (texto-only). Verificação: `pnpm exec prettier --check` nos arquivos; diff limpo. +2. **Skills** — `execution-pipeline.md` (passo + seção #72), `work-issue/SKILL.md`, `agent-work-issue/SKILL.md`. +3. **Docs** — `AGENTS.md`, `AGENT-OPS.md`, `agent-pr-workflow.mdc`. +4. **Changelog** — entrada OPS72 + build + check. +5. **Gates** — `pnpm gate:fast`; `pnpm format:check`; `pnpm test:unit --changed` (nenhum pin quebra — mensagens de console não são pinadas); e2e local discricionário (este diff não cria specs nem toca superfície e2e — nada a rodar); `pnpm push`; PR Ready `Closes #98`. + +## Rabbit holes / Não escopo (engenharia) + +- Não tocar `ci-scope.mjs`, `e2e-affected.mjs`, `e2e-affected-manifest.mjs`, `test-affected-core.mjs`, `run-e2e-affected.mjs` — mecânica funciona; só muda onde/em que modo roda (fora de escopo da intenção). +- Não implementar o fix da #72 (fica na fila; só documentação). +- Não adicionar e2e ao `gate:push`/`gate:ci` (aceite explícito: passo da skill, não hook). +- Não mexer no `deploy.yml` (já roda full no `verify`). +- Não criar script novo — `pnpm test:e2e:affected` já existe. + +## Riscos e mitigação + +- **Diff high-risk no CI sem e2e** (ex.: PR de schema): o PR não roda e2e no CI. Mitigação: espelho local roda full no mesmo diff; `verify` do deploy roda full antes de publicar; documentado no workflow e no gate-ci. +- **Agente pula o e2e local**: discricionário por design (decisão do usuário) — o agente decide quais specs rodar e pode decidir não rodar. Mitigação: CI selected (blast radius) pega a superfície quando o diff é selecionável; full no `verify` do deploy fecha o ciclo antes de publicar. +- **Mensagem do gate-ci desatualizada vs CI**: texto ajustado na mesma entrega; nada pinado. +- **Serialização**: a intenção serializa `ci-pr.yml` e `.agents/skills/work-issue/`; OPS71 já mergeou — sem concorrência pendente (OPS72 é o primeiro a tocar no ci-pr.yml novo pós-OPS71). + +## Aceite de engenharia + +- [x] Aceite de produto da intenção ainda coberto (e2e local discricionário documentado na skill; CI/PR = blast-radius selected, nunca full; full na main antes do deploy; #72 documentada; gate local intocado) +- [x] Invariantes AGENTS/engineering-standards (edita o dono — execution-pipeline é o corpo compartilhado; nada duplicado) +- [x] Testes de domínio previstos: nenhum pin quebra (mensagens de console não pinadas); `test:e2e:affected` com modo `none` valida o fluxo novo diff --git a/scripts/gate-ci.mjs b/scripts/gate-ci.mjs index 78328a2d..c42926f0 100644 --- a/scripts/gate-ci.mjs +++ b/scripts/gate-ci.mjs @@ -2,8 +2,10 @@ /** * Local mirror of `.github/workflows/ci-pr.yml` (serial): phase-1 cheap checks, * then phase-2 expensive (int/build). e2e is NOT part of the local mirror - * (OPS59): it runs in CI — selected/afetado no PR (`ci-pr.yml` → `checks`) - * e full no verify do `deploy.yml`. Local optional feedback: `pnpm test:e2e:affected`. +* (OPS59 + OPS72): the PR CI runs only the e2e blast radius (mode `selected`, + * never `full`), the deploy `verify` job runs the full suite before + * publishing, and the local affected run is a skill step (discretionary): + * `pnpm test:e2e:affected`. * Docs guards (OPS63): the `docs-guards` checks (changelog append-only, * aggregate sync, conflict markers) also run here — cheap git diffs, same * scripts the CI job runs (the `changelog-rewrite:` escape stays CI-only, @@ -77,7 +79,7 @@ const main = async () => { const scope = readJsonScript('scripts/ci-scope.mjs') console.log( - `[gate:ci] scope base=${scope.base} code=${scope.code.mode} build=${scope.build.mode} test=${scope.test.mode} e2e=${scope.e2e.mode} (e2e runs in CI, not in this gate — OPS59)`, + `[gate:ci] scope base=${scope.base} code=${scope.code.mode} build=${scope.build.mode} test=${scope.test.mode} e2e=${scope.e2e.mode} (e2e not in this gate — OPS72; local affected is a skill step)`, ) // --- Phase 1 (cheap) --- @@ -152,14 +154,18 @@ const main = async () => { if (scope.e2e.mode === 'none') { console.log('\n[gate:ci] ⊘ e2e: CI will skip (no e2e blast radius)') + } else if (scope.e2e.mode === 'selected') { + console.log( + `\n[gate:ci] ▶ e2e: CI runs the blast radius (${scope.e2e.specs.join(', ')}) — not in this gate (OPS72); full lives in deploy verify`, + ) } else { - const specs = - scope.e2e.mode === 'full' ? 'full suite' : `selected: ${scope.e2e.specs.join(', ')}` - console.log(`\n[gate:ci] ▶ e2e: runs in CI (job e2e, ${specs}) — not in this gate (OPS59)`) + console.log( + '\n[gate:ci] ▶ e2e: diff is high-risk (full mode) — no e2e in this PR by design (OPS72); run `pnpm test:e2e:affected` locally; deploy verify runs full before publishing', + ) } console.log( - '\n[gate:ci] ✓ all checks passed (ci-pr mirror: docs guards local — OPS63; e2e verified in CI)', + '\n[gate:ci] ✓ all checks passed (ci-pr mirror: docs guards local — OPS63; e2e: local affected is a skill step — OPS72)', ) }