test-audit
Audita la suite de tests para hallar cobertura de bajo valor, acoplamiento a la implementacion y tests duplicados sin bajar la confianza real. Usar cuando la suite es lenta o sospechas de tests cosmet
Files7
View on GitHubSKILL.mdskills/test-audit/SKILL.md
GitHubtest-audit
Skill para auditar la suite de tests del proyecto e identificar candidatos a
eliminacion/consolidacion sin perder cobertura real. Origen externo: patron
adaptado de openclaw/openclaw (.agents/skills/test-audit/SKILL.md, MIT) —
ver bloque Credits al final de este documento.
Optimiza para confianza, no para numero de tests eliminados. Un test que falla en la baseline es un posible bug de producto, no un candidato a borrar.
Por que existe
La suite canonica de este repo tiene medido [snapshot 2026-09-24] 336
ficheros bajo tests/ y 6569 tests pasando (--level all, 875.56s). El coste
no es solo de wall-clock: cada test es superficie de mantenimiento que hay que
releer en cada refactor. No todos los tests protegen un contrato — algunos
solo repiten en el test lo que ya dice el codigo (self-comparison), duplican
un contrato ya cubierto en una frontera mas fuerte, o existen solo para
mantener vivo un seam de produccion que ningun caller real usa.
Esta skill NO asume que "menos tests" es la meta. Es una auditoria de VALOR: cada test debe justificar su coste protegiendo un comportamiento observable, una regresion creible o un contrato independiente.
Relacion con skills hermanas (no duplicar, no fusionar)
Cada skill responde una pregunta distinta; fusionarlas mezclaria un flujo read-only-por-defecto (auditoria) con uno prescriptivo-en-caliente (autoria), y diluiria el trigger que cada una necesita. Esta skill REFERENCIA a las demas, nunca las reimplementa:
skills/code-audit/SKILL.md: dead code de PRODUCCION (vulture/deadcode/ruff- antiguedad git). Comparten el principio "triangular antes de borrar", pero
code-auditNUNCA decide sobre tests — solo sobre codigo que los tests podrian dejar huerfano tras un test-audit.
- antiguedad git). Comparten el principio "triangular antes de borrar", pero
skills/test-driven-development/SKILL.md: gobierna la escritura de tests NUEVOS (Red-Green-Refactor) durante desarrollo activo. Esta skill audita tests YA EXISTENTES; su "authoring gate" (ver mas abajo) es el mismo tipo de filtro que TDD aplicaria al escribir, aplicado retroactivamente.skills/systematic-debugging/SKILL.md: si un test retenido falla en la baseline, se trata como bug real via esa skill, no se repara aqui a mano.prompts/audit_agent_output.md(CEM v0): gobierna el contrato de auditoria esceptica en general (evidencia antes que relato, etiquetas VERIFICADO/INFERIDO/NO VERIFICADO). Esta skill lo aplica al dominio de tests; no lo reproduce.prompts/suite_optimization.md: optimiza VELOCIDAD de una suite cuyo valor ya se asume correcto (reordena/paraleliza, nunca borra). Esta skill optimiza VALOR (que se ejecuta), no cuanto tarda en ejecutarse. Son complementarias y pueden correr en secuencia (primero valor, luego velocidad de lo que queda), pero no son la misma pasada ni deben fusionarse.
When to activate
- Cuando se pide auditar/reducir la suite de tests o su tiempo de ejecucion.
- Antes de una campana de refactor grande, para saber que tests son contrato real y cuales son ruido que puede reescribirse sin miedo.
- Cuando
run_history.jsonlmuestra que la duracion de la suite crece sin que haya crecido la cobertura de contratos reales. - Al escribir un test nuevo, para pasarlo por el authoring gate ANTES de
commitearlo (modo autoria, ver
references/gate-checklist.md).
When NOT to activate
- Para optimizar la VELOCIDAD de una suite ya de alto valor (usa
prompts/suite_optimization.md— es RECOLECTOR->JUEZ sobre timing, no sobre valor de contrato). - Como sustituto de
code-auditpara decidir sobre codigo de produccion muerto. - Para borrar tests sin pasar por
references/candidate-evidence.mdcompleto: un candidato sin evidencia registrada NO esta listo para editar. - Si no hay autorizacion explicita para tocar tests existentes: esta skill por
defecto es READ-ONLY (audita y propone), igual que
repo-compare. Solo edita si el Manager/usuario aprueba el batch propuesto.
Dos modos: campaign vs focal
Igual que openclaw/openclaw distingue modo autoria de modo campana, esta
skill soporta dos escalas de auditoria (decision de diseno 2026-09-24):
- Modo FOCAL (default, bajo riesgo): audita los tests tocados por un diff
concreto, un directorio pequeno, o un lote explicito de candidatos que ya se
sospechan (p.ej. los del
top_slowestderun_history.jsonl). Es la entrada natural cuando el disparador es "este test tarda mucho" o "este fichero se acaba de tocar". - Modo CAMPAIGN (alto volumen, deliberado): barre UN subsistema completo
(todos los ficheros de test que posee un area) en lanes paralelos de
discovery, igual que openclaw separa core/packages, plugins, UI/tooling y un
barrido de patron transversal. Para este repo, los lanes naturales medidos
son:
tests/unit/(210 ficheros[snapshot 2026-09-24]) — logica de scripts/bus aislada.tests/raiz (110 ficheros) — gates/guards de pre-commit y pre-push (test_prepush_*,test_check_*).tests/integration/+tests/evals/(7 ficheros) — contratos end-to-end y evals de seguridad (test_eval_guard_paths.pyetc.) — LEER CON CUIDADO: aqui vive mas contrato de seguridad real por test que entests/unit/.tests/sandbox/+tests/debug/(8 ficheros) — segun.claude/rules/00-startup.md, son scripts de un solo uso; su bar de valor es distinto (pueden no perseguir un contrato productivo en absoluto). Un campaign NUNCA se hace en una sola PR: cada lane cierra su propio batch coherente (verreferences/edit-shape.md), se valida, y solo entonces se continua con el siguiente lane.
Workflow (7 pasos)
Paso 1: Preflight — evidencia real, no intuicion
- Ejecutar
python scripts/run_pytest_safe.py --statuspara confirmar que no hay lock activo y ver ellast-run.jsonmas reciente. - Leer
.agent/runtime/pytest-safe/run_history.jsonl(motor) para:- la duracion real de la ultima corrida
level=all+args_mode=default_discoverycompleta (NO una corrida filtrada — verWOT-2026-044oen AGENTS.md); - el campo
top_slowestde esa corrida como lista de candidatos objetivos para modo FOCAL (un test lento no es automaticamente de bajo valor, pero es donde el ROI de auditar primero es mayor).
- la duracion real de la ultima corrida
- Si el objetivo es un subsistema (modo CAMPAIGN), listar sus ficheros:
find tests/<lane> -name "*.py" | wc -ly anclar el denominador ANTES de auditar (mismo principio quecheck_distribution_agnostic.py: un censo que no publica su denominador no cuenta).
Paso 2: Validar input y modo
- Si viene un path/diff concreto -> modo FOCAL sobre ese subconjunto.
- Si viene "audita
tests/unit/" o similar -> modo CAMPAIGN sobre ese lane. - Si no hay ninguno explicito -> usa el
top_slowestdel preflight (Paso 1) como arranque de modo FOCAL, declarandolo explicitamente ("sin scope dado, arranco FOCAL sobre los N tests mas lentos de la ultima corrida completa"). NO te detengas a pedir permiso para esto — es el default seguro (FOCAL, bajo riesgo) y queda declarado en el reporte, no ejecutado en silencio. - Si te piden "audita toda la suite" sin mas detalle -> CAMPAIGN, lane por lane, nunca de una vez.
Paso 3: Authoring gate check (retroactivo)
Para cada test candidato, aplicar las 4 preguntas de
references/gate-checklist.md como si el test se fuera a escribir hoy.
Cuidado con la polaridad: las preguntas 1-3 exigen respuesta CONCRETA
(no un si/no), y la pregunta 4 es una trampa invertida — "si" a la 4 es MALO
(necesita un seam artificial). Un test PASA el gate cuando 1-3 tienen
respuesta concreta Y la 4 responde "no". Un test que NO pasa el gate (falta
respuesta en 1-3, o la 4 responde "si") es candidato a la Fase 4; uno que si
pasa, se descarta como candidato salvo que ademas matchee un
references/junk-patterns.md.
Paso 4: Cotejar contra junk patterns
Aplicar el checklist completo de references/junk-patterns.md. Un match no es
veredicto automatico: pasa a la Fase 5 (value bar) antes de proponerse.
Paso 5: Value bar + retention bar
Aplicar references/value-bar.md. Regla dura de este repo (2026-09-24,
decision de usuario): no hay jerarquia de dominio predefinida — un gate de
seguridad no vale automaticamente mas que un test de schema de CLI. Cada
candidato se juzga por evidencia propia: que contrato protege, que regresion
creible detecta, si existe un owner mas fuerte que ya lo cubre. Antes de
juzgar, leer el test completo, su owner de produccion, callers, callees,
hermanos y AGENTS.md/CLAUDE.md relevantes (mismo principio que M4 de este
repo: "prompt/contrato citado => leelo entero", aplicado a "test citado =>
leelo entero + su produccion").
Paso 6: Mutation-verify obligatorio antes de proponer eliminacion
Barrera dura, no opcional. Este repo YA exige mutation-verify para cualquier guard/test nuevo (AGENTS.md, seccion CEM v0: "Barrera verificada"). Aplicado a un test EXISTENTE que se propone eliminar, la carga de la prueba se invierte: hay que demostrar que su desaparicion NO pierde deteccion real.
- Herramienta ya existente en este repo (
scripts/mutation_cycle.py): protege el ciclo mutar->probar->restaurar con snapshot byte a byte. El ORDEN es contra-intuitivo y hay que respetarlo al pie de la letra — verificado leyendomain()completo y su propio test de referencia (tests/unit/test_mutation_cycle.py::test_fix_survives_destructive_revert): el snapshot se toma DENTRO demain(), al arrancar el proceso, es decir DESPUES de invocar el CLI, no antes. Si mutas el fichero a mano ANTES de lanzarmutation_cycle.py, el snapshot fotografia el ESTADO YA MUTADO, y elfinally"restaura" ese mutante — el defecto queda vivo en produccion tras el ciclo, exactamente al reves de lo que la barrera deberia garantizar. NUNCA mutes a mano antes de invocar el CLI.- Secuencia CORRECTA: la mutacion se aplica DENTRO del
command(tras el segundo--), en el mismo proceso donde corre el test — igual que hace el test de referencia del propio script: el snapshot se toma con el fichero LIMPIO, y es el comando ejecutado dentro derun_cycleel que escribe la mutacion antes de correr la comprobacion. - Forma practica para un mutante de texto simple (
python -cinline que aplica el parche, corre pytest, y propaga su rc):python scripts/mutation_cycle.py -- <rutas de produccion a proteger> -- python -c " import pathlib, subprocess, sys p = pathlib.Path('<ruta mutada>') original = p.read_text(encoding='utf-8') p.write_text(original.replace('<fragmento original>', '<fragmento mutado>'), encoding='utf-8') rc = subprocess.run([sys.executable, 'scripts/run_pytest_safe.py', '--level', 'all', '--', '<nodeids del candidato y de su posible sustituto>']).returncode sys.exit(rc) " --level alles obligatorio dentro de ese comando, NUNCA--level unit. Verificado enscripts/run_pytest_safe.py::normalize_pytest_args:--level unitantepone SIEMPRE-m "not integration"cuando los args no traen ya un-mexplicito, y ese filtro se aplica incluso sobre nodeids explicitos por linea de comandos. Si el candidato o su sustituto viven entests/integration//tests/evals/(markersintegration/eval/slowdepytest.ini), quedan DESELECCIONADOS en silencio — pytest reporta "N deselected", no un error — y el mutation-verify se interpreta como "RETENER" (el candidato "no fallo") cuando en realidad NUNCA SE EJECUTO.--level allno añade ningun filtro de marker. Antes de interpretar el resultado, confirma en el output de pytest que el conteocollectedincluye los nodeids que pediste (no solo "N deselected").- Interpreta el resultado: si el candidato falla con la mutacion presente, detecta la mutacion; si no falla, no la detecta.
- Tras el ciclo, verifica SIEMPRE que el fichero de produccion quedo
restaurado a su version original (
git diff --stat <ruta>debe salir vacio) — es la comprobacion barata de que la secuencia se siguio bien y de que no quedo un mutante vivo en el arbol.
- Secuencia CORRECTA: la mutacion se aplica DENTRO del
- Repite el ciclo por CADA caso/aserción distinta que el candidato ejercita, no solo una mutacion "representativa". Dos tests pueden detectar la misma mutacion gruesa (borrar la validacion entera) y solo uno detectar un caso limite (off-by-one, string vacio, unicode); verificar solo la mutacion gruesa sobreestima la redundancia del candidato.
- Por cada caso, hay TRES veredictos posibles (no dos):
- RETENER ese caso: el candidato es el UNICO que lo detecta. Es un
false-positive del junk-pattern-match — promuevelo con nota en
references/candidate-evidence.md. - ELIMINAR ese caso: el owner-boundary sustituto tambien lo detecta.
- NINGUNO DETECTA ese caso: ni el candidato ni el sustituto fallan con esa mutacion. Esto NO es evidencia de que el candidato sea eliminable — es evidencia de que ESE CASO no tiene cobertura real en ningun lado. Abre un hallazgo de cobertura FALTANTE aparte (fuera del batch de limpieza) si el caso importa; no declares el candidato eliminable por un caso donde tampoco hay sustituto.
- RETENER ese caso: el candidato es el UNICO que lo detecta. Es un
false-positive del junk-pattern-match — promuevelo con nota en
- El veredicto FINAL del candidato es ELIMINAR solo si TODOS sus casos salen
ELIMINAR. Un solo caso en RETENER o NINGUNO DETECTA bloquea el ELIMINAR
global (ver
references/candidate-evidence.mdpara el formato exacto). - Registrar el resultado exacto (mutado: fail esperado / sin mutar: pass) DE CADA CASO como parte de la evidencia del candidato — sin este recibo, el candidato NO esta listo para editar (mismo principio que CEM: "un exit 0 no basta, hace falta el artefacto").
Paso 7: Output y batch
- Reportar con la plantilla de
references/candidate-evidence.md— un bloque por candidato, TODOS los campos rellenos o el candidato no esta listo. - Agrupar candidatos en UN batch coherente por owner boundary (ver
references/edit-shape.md); nunca mezclar limpiezas de lanes distintos en el mismo batch. - Persistir el reporte en
.agent/runtime/audit/test_audit/<lane-o-scope>-<YYYY-MM-DD>.md(gitignored, mismo patron que.agent/runtime/compare/derepo-compare). - No editar tests/produccion en la misma pasada que genera el reporte, salvo que el usuario ya haya aprobado modo ejecucion para ese batch especifico (ver Constraints).
Constraints
- Skill hibrida, no pura: a diferencia de
repo-compare(nunca escribe codigo), esta skill SI puede terminar editando tests/produccion — pero solo en la Fase de edicion, tras el batch aprobado. El descubrimiento (Pasos 1-7) es SIEMPRE read-only por defecto respecto al arbol persistente: la mutacion TEMPORAL del Paso 6 (mutation-verify) es la unica excepcion declarada, y no cuenta como "editar produccion" en el sentido que esta regla prohibe, porquemutation_cycle.pyla restaura SIEMPRE antes de que el Paso 6 termine (ver verificaciongit diff --statal final de ese paso). Si esa restauracion fallase (exit 126 del helper), el arbol SI quedaria sucio — DETENTE y repara antes de continuar el descubrimiento; no es un estado aceptable para reportar un candidato. - Nunca editar tests mientras la suite corre en el mismo checkout (mismo
riesgo de carrera que documenta openclaw para Vitest; aqui aplica a
run_pytest_safe.py— comprobar--statusantes de tocar ficheros). - No mezclar limpieza con refactor de produccion no relacionado: si una eliminacion de test destapa un seam de produccion muerto, eliminarlo es DESEABLE (LOC de produccion negativa) pero se reporta como parte del mismo batch, con su propio diff separado en el numstat final.
- Nunca aumentar el conteo de tests para "quedar bien": no se escriben tests de reemplazo que repiten la misma implementacion para compensar una eliminacion. Si hace falta cobertura nueva real, se abre como hallazgo aparte (Contract Formation), no como parte del mismo batch de limpieza.
- Gate de evidencia = el mismo umbral que el resto del repo (AGENTS.md, CEM v0): sin SHA/diff/exit-code/nodeid citado, el hallazgo se descarta o degrada, nunca se promociona a memoria ni a backlog.
- Un test retenido que falla en la baseline es un bug de producto: se reproduce y se repara en el owner, nunca se borra para "limpiar la suite".
- Validacion final obligatoria antes de cerrar cualquier batch:
python scripts/run_pytest_safe.py --level allen verde,ruff check .en verde, ypython .agent/agent_controller.py --validate --jsonen0/0si el batch toco.agent/collaboration/.
References
references/gate-checklist.md— Las 4 preguntas + junk patterns aplicados a un test NUEVO (uso preventivo, antes de commitear).references/junk-patterns.md— Checklist compartido entre autoria y auditoria.references/value-bar.md— Barra de valor + retention bar (que SI se conserva aunque parezca ruido).references/candidate-evidence.md— Campos obligatorios por candidato antes de editar.references/edit-shape.md— Como agrupar el batch de edicion y que validar.PROMPT_TEMPLATE.md— Prompt completo agnostico de backend, listo para pasarle al Builder.
Troubleshooting
P: ¿Que pasa si run_history.jsonl no tiene ninguna corrida level=all +
default_discovery completa reciente?
R: El disparador de "test lento" queda NO VERIFICABLE para modo FOCAL por
timing; se puede seguir en modo FOCAL por sospecha manual (test citado por el
usuario) o iniciar CAMPAIGN igualmente, ya que CAMPAIGN no depende del timing.
P: ¿Que pasa si un candidato usa un mock que hace imposible saber si prueba
algo real?
R: Es exactamente el junk pattern "mocks that implement the asserted behavior"
— léase references/junk-patterns.md. Mutation-verify (Paso 6) lo confirma:
si mutar el owner real no hace fallar el test, el mock esta ocultando la falta
de cobertura.
P: ¿Se puede automatizar el conteo de candidatos con vulture/deadcode como
en code-audit?
R: No directamente — esas herramientas detectan codigo de PRODUCCION no
referenciado, no valor de un test. Un test puede tener 100% de "uso" (se
ejecuta, pasa) y seguir siendo basura porque no protege nada. El filtro aqui
es semantico (junk patterns + value bar), no de referencia estatica.
Credits — origen externo
| WOT | Source | Pattern | License | Adapted vs Ported |
|---|---|---|---|---|
| WOT-XXXX-XXX (TBD) | openclaw/openclaw@main | Authoring gate + junk patterns + value/retention bar + candidate evidence + edit shape para auditoria de tests | MIT (verificar LICENSE del repo source antes de citar en CREDITS.md) |
Adapted — estructura y checklist tomados del original; comandos, lanes de campaign, mutation-verify y umbrales de evidencia reescritos para el contrato CEM v0 y las herramientas propias de este repo (mutation_cycle.py, run_pytest_safe.py, run_history.jsonl). |
Cuando se adopte esta skill formalmente (abrir el ticket que la incorpora),
pegar la fila anterior (con el WOT-id real y la licencia verificada) en
CREDITS.md de la raiz del repo, siguiendo el contrato de
AGENTS.md seccion "Atribuciones externas (CREDITS.md)".
Version: 1.0.0 Autor: agent (adaptado de openclaw/openclaw, MIT) Ultima actualizacion: 2026-09-24