feat(OPS72): e2e local afetado discricionário na skill; CI de PR só blast radius (nunca full)
- 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
This commit is contained in:
parent
e504491dd8
commit
fb7fc83c56
11 changed files with 150 additions and 26 deletions
|
|
@ -38,7 +38,7 @@ Closes #<N>
|
|||
**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
|
||||
|
|
|
|||
|
|
@ -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/<id>-<slug>` (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: [<id-pai>]` se necessário); score ≤3 /
|
||||
|
|
|
|||
|
|
@ -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/<slug>-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:** `<Code>-<slug>` 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
|
||||
|
|
|
|||
|
|
@ -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/<spec>.e2e.spec.ts` (ou
|
||||
`--no-deps --project=<família>`).
|
||||
- **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,
|
||||
|
|
|
|||
24
.github/workflows/ci-pr.yml
vendored
24
.github/workflows/ci-pr.yml
vendored
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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<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 — **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 -- <path>` ou `--no-deps --project=<família>`).
|
||||
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 -- <path>` ou `--no-deps --project=<família>`; 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 <symbol>`) 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.
|
||||
|
|
|
|||
|
|
@ -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 <sha>` **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`.
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
1
docs/changelog/2026-08-19-ops72.md
Normal file
1
docs/changelog/2026-08-19-ops72.md
Normal file
|
|
@ -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`.
|
||||
76
docs/plans/ops72-e2e-local-afetado-ci-sem-full-impl.md
Normal file
76
docs/plans/ops72-e2e-local-afetado-ci-sem-full-impl.md
Normal file
|
|
@ -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
|
||||
|
|
@ -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)',
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue