refactor(ops96): simplify — dedup ms/DEFAULT_TOP no core, exit fail-closed sem relatório, precisão no desarme e pins de fallback

This commit is contained in:
Francisco Solla 2026-08-25 02:08:48 -03:00
parent 36dde76cc7
commit bd977cd2ae
4 changed files with 32 additions and 9 deletions

View file

@ -1,6 +1,6 @@
--- ---
name: testing-audit 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 # 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) ## 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 ## 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: 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). 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 <branch-deste-PR> --limit 1` — filtre pela branch/SHA, nunca pelo "último run" global (PRs concorrentes poluem a lista).
3. Desarmar: `gh pr merge <N> --disable-auto`. 3. Desarmar: `gh pr merge <N> --disable-auto`.
4. Verificar: `gh pr view <N> --json isDraft,autoMergeRequest,mergeable,mergeStateStatus` → esperado `{isDraft: false, autoMergeRequest: null, mergeable: MERGEABLE}`. 4. Verificar: `gh pr view <N> --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 <N> --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. 5. **Fallback fail-closed:** desarme falhou ou `autoMergeRequest` persistiu não-nulo → converter para draft (`gh pr ready <N> --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.

View file

@ -5,9 +5,9 @@
* functions; the unit spec pins their behavior against fixture payloads. * 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} */ /** @returns {number} */
const fileDurationMs = (entry) => { 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. * 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 * @param {{ file: string, content: string }} source
* @returns {{ file: string, describes: number, tests: number }} * @returns {{ file: string, describes: number, tests: number }}

View file

@ -20,7 +20,9 @@ import path from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import { import {
DEFAULT_TOP,
inventoryE2ESource, inventoryE2ESource,
ms,
renderE2EInventoryTable, renderE2EInventoryTable,
renderSlowestFilesTable, renderSlowestFilesTable,
summarizeVitestReport, summarizeVitestReport,
@ -28,7 +30,6 @@ import {
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const E2E_DIR = 'tests/e2e' const E2E_DIR = 'tests/e2e'
const DEFAULT_TOP = 15
const usage = () => { const usage = () => {
console.error('usage: node scripts/testing-audit-metrics.mjs <unit|int|e2e-inventory> [--top N]') console.error('usage: node scripts/testing-audit-metrics.mjs <unit|int|e2e-inventory> [--top N]')
@ -56,7 +57,7 @@ const runVitestLayer = async (layer) => {
if (jsonStart === -1) { if (jsonStart === -1) {
console.error(`[metrics] vitest não produziu relatório JSON no stdout (exit ${result.status})`) console.error(`[metrics] vitest não produziu relatório JSON no stdout (exit ${result.status})`)
console.error((result.stderr ?? '').slice(-4000)) console.error((result.stderr ?? '').slice(-4000))
process.exit(result.status ?? 2) process.exit(result.status === 0 ? 2 : result.status)
} }
let report let report
try { try {
@ -76,8 +77,6 @@ const runVitestLayer = async (layer) => {
if (result.status !== 0) process.exit(result.status) if (result.status !== 0) process.exit(result.status)
} }
const ms = (value) => `${(value / 1000).toFixed(1)}s`
const runE2EInventory = () => { const runE2EInventory = () => {
const dir = path.join(repoRoot, E2E_DIR) const dir = path.join(repoRoot, E2E_DIR)
const rows = readdirSync(dir) const rows = readdirSync(dir)

View file

@ -60,6 +60,19 @@ describe('summarizeVitestReport', () => {
it('rejects payloads without testResults', () => { it('rejects payloads without testResults', () => {
expect(() => summarizeVitestReport({})).toThrow('missing 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', () => { describe('renderSlowestFilesTable', () => {
@ -69,6 +82,15 @@ describe('renderSlowestFilesTable', () => {
expect(table.split('\n')).toHaveLength(3) expect(table.split('\n')).toHaveLength(3)
expect(table).toContain('| tests/unit/b.unit.spec.ts | 2 | 1.1s | slow one (0.4s) |') 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', () => { describe('inventoryE2ESource', () => {