mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
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:
parent
36dde76cc7
commit
bd977cd2ae
4 changed files with 32 additions and 9 deletions
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue