From bd977cd2aee38e5fd86161680d83eb22ce2bd0da Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Tue, 25 Aug 2026 02:08:48 -0300 Subject: [PATCH] =?UTF-8?q?refactor(ops96):=20simplify=20=E2=80=94=20dedup?= =?UTF-8?q?=20ms/DEFAULT=5FTOP=20no=20core,=20exit=20fail-closed=20sem=20r?= =?UTF-8?q?elat=C3=B3rio,=20precis=C3=A3o=20no=20desarme=20e=20pins=20de?= =?UTF-8?q?=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/skills/testing-audit/SKILL.md | 6 ++--- scripts/lib/testing-audit-metrics-core.mjs | 6 +++-- scripts/testing-audit-metrics.mjs | 7 +++--- .../unit/testingAuditMetricsCore.unit.spec.ts | 22 +++++++++++++++++++ 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/.agents/skills/testing-audit/SKILL.md b/.agents/skills/testing-audit/SKILL.md index da48ba23..5e52d172 100644 --- a/.agents/skills/testing-audit/SKILL.md +++ b/.agents/skills/testing-audit/SKILL.md @@ -1,6 +1,6 @@ --- name: testing-audit -description: Auditoria autônoma noturna da suíte de testes do Teqo — retrato honesto (contagens, tempos, duplicatas por camada), melhorias seguras aplicadas com prova verde antes/depois e revert fail-closed, entregues num único PR ready SEM auto-merge armado cuja descrição é o relatório final. +description: 'Auditoria autônoma noturna da suíte de testes do Teqo — retrato honesto (contagens, tempos, duplicatas por camada), melhorias seguras aplicadas com prova verde antes/depois e revert fail-closed, entregues num único PR ready SEM auto-merge armado cuja descrição é o relatório final.' --- # Skill: testing-audit @@ -28,7 +28,7 @@ Intocáveis estruturais (nenhum diff toca): `scripts/ci-scope.mjs`, `scripts/lib ## Critérios de nível (aponte, não redefina) -A pirâmide das três camadas já está codificada: `.agents/skills/test-driven-development/SKILL.md` §144–198 e `docs/TESTING.md`. As disposições da auditoria julgam contra esses critérios existentes — nunca criem critérios novos paralelos. +A pirâmide das três camadas já está codificada: `.agents/skills/test-driven-development/SKILL.md` (seção da pirâmide, "as três camadas do repo" — hoje §144–198) e `docs/TESTING.md`. As disposições da auditoria julgam contra esses critérios existentes — nunca criem critérios novos paralelos. ## Tetos duros da noite @@ -135,7 +135,7 @@ Evidência por passo no relatório: timestamp, hash curto, linhas-resumo antes/d O safety-net arma o automerge no evento de abertura e REARMA a cada push. Sequência obrigatória: 1. Confirmar que nenhum push novo acontecerá (todo o trabalho já foi empurrado na Fase 4). -2. Aguardar o run do workflow `PR Ready + auto-merge` **do SHA final** terminar (foi ele que armou): `gh run list --workflow agent-pr-ready-automerge.yml --limit 1`. +2. Aguardar o run do workflow `PR Ready + auto-merge` **do SHA final** terminar (foi ele que armou): `gh run list --workflow agent-pr-ready-automerge.yml --branch --limit 1` — filtre pela branch/SHA, nunca pelo "último run" global (PRs concorrentes poluem a lista). 3. Desarmar: `gh pr merge --disable-auto`. 4. Verificar: `gh pr view --json isDraft,autoMergeRequest,mergeable,mergeStateStatus` → esperado `{isDraft: false, autoMergeRequest: null, mergeable: MERGEABLE}`. 5. **Fallback fail-closed:** desarme falhou ou `autoMergeRequest` persistiu não-nulo → converter para draft (`gh pr ready --undo`; draft fora de `cursor/*` é veto estrutural do safety-net) e registrar no relatório que o humano vira ready pela manhã. Draft protegido é preferível a main mergeado sem querer. diff --git a/scripts/lib/testing-audit-metrics-core.mjs b/scripts/lib/testing-audit-metrics-core.mjs index b556b49c..9dec2da1 100644 --- a/scripts/lib/testing-audit-metrics-core.mjs +++ b/scripts/lib/testing-audit-metrics-core.mjs @@ -5,9 +5,9 @@ * functions; the unit spec pins their behavior against fixture payloads. */ -const DEFAULT_TOP = 15 +export const DEFAULT_TOP = 15 -const ms = (value) => `${(value / 1000).toFixed(1)}s` +export const ms = (value) => `${(value / 1000).toFixed(1)}s` /** @returns {number} */ const fileDurationMs = (entry) => { @@ -80,6 +80,8 @@ export const renderSlowestFilesTable = (summary, { top = DEFAULT_TOP } = {}) => /** * Static inventory of an e2e spec source — counting only, never execution. + * Deliberate approximation: `test.each`/`test.skip` variants are not counted + * (the audit's portrait is structural, not exhaustive). * * @param {{ file: string, content: string }} source * @returns {{ file: string, describes: number, tests: number }} diff --git a/scripts/testing-audit-metrics.mjs b/scripts/testing-audit-metrics.mjs index c0ee0176..5fec20ee 100644 --- a/scripts/testing-audit-metrics.mjs +++ b/scripts/testing-audit-metrics.mjs @@ -20,7 +20,9 @@ import path from 'node:path' import { fileURLToPath } from 'node:url' import { + DEFAULT_TOP, inventoryE2ESource, + ms, renderE2EInventoryTable, renderSlowestFilesTable, summarizeVitestReport, @@ -28,7 +30,6 @@ import { const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') const E2E_DIR = 'tests/e2e' -const DEFAULT_TOP = 15 const usage = () => { console.error('usage: node scripts/testing-audit-metrics.mjs [--top N]') @@ -56,7 +57,7 @@ const runVitestLayer = async (layer) => { if (jsonStart === -1) { console.error(`[metrics] vitest não produziu relatório JSON no stdout (exit ${result.status})`) console.error((result.stderr ?? '').slice(-4000)) - process.exit(result.status ?? 2) + process.exit(result.status === 0 ? 2 : result.status) } let report try { @@ -76,8 +77,6 @@ const runVitestLayer = async (layer) => { if (result.status !== 0) process.exit(result.status) } -const ms = (value) => `${(value / 1000).toFixed(1)}s` - const runE2EInventory = () => { const dir = path.join(repoRoot, E2E_DIR) const rows = readdirSync(dir) diff --git a/tests/unit/testingAuditMetricsCore.unit.spec.ts b/tests/unit/testingAuditMetricsCore.unit.spec.ts index b1bb202c..439b2fdf 100644 --- a/tests/unit/testingAuditMetricsCore.unit.spec.ts +++ b/tests/unit/testingAuditMetricsCore.unit.spec.ts @@ -60,6 +60,19 @@ describe('summarizeVitestReport', () => { it('rejects payloads without testResults', () => { expect(() => summarizeVitestReport({})).toThrow('missing testResults') }) + + it('falls back to counted totals and a placeholder when fields are absent', () => { + const payload = { + testResults: [{ assertionResults: [{ title: 't' }] }], + } + const summary = summarizeVitestReport(payload) + + expect(summary.totals.files).toBe(1) + expect(summary.totals.tests).toBe(1) + expect(summary.totals.failedTests).toBe(0) + expect(summary.rows[0].file).toBe('(desconhecido)') + expect(summary.rows[0].slowestTest).toBeNull() + }) }) describe('renderSlowestFilesTable', () => { @@ -69,6 +82,15 @@ describe('renderSlowestFilesTable', () => { expect(table.split('\n')).toHaveLength(3) expect(table).toContain('| tests/unit/b.unit.spec.ts | 2 | 1.1s | slow one (0.4s) |') }) + + it('renders a placeholder for rows without a slowest test', () => { + const table = renderSlowestFilesTable( + summarizeVitestReport({ testResults: [{ name: '/x/y.unit.spec.ts', assertionResults: [] }] }), + { top: 5 }, + ) + + expect(table).toContain('| /x/y.unit.spec.ts | 0 | 0.0s | — |') + }) }) describe('inventoryE2ESource', () => {