# Trace Validate

> Generar un reporte de trazabilidad que cruza los criterios de aceptación de un artefacto —una historia de usuario (US-XXX), una tarea de mantenimiento (WI-XXX), un feature ya implementado (FT-XXX) o cualquier documento cuyos criterios tengan identificador codificado (AC-001, 1.1, R-3…)— contra los casos y artefactos de prueba del repositorio. Por cada criterio indica qué lo cubre, su estado (`COVERED` / `PARTIAL` / `UNCOVERED`), si la prueba se ejecutó y con qué resultado, y cierra con un veredicto. No corre la suite: delega la ejecución en quality-check. Activar siempre que el usuario pida validar cobertura, generar una matriz o reporte de trazabilidad, verificar que los criterios de aceptación están probados, comprobar si un trabajo o un feature está cubierto por pruebas, o mencione "trazabilidad", "matriz de cobertura" o "validar criterios de aceptación", aunque no nombre el formato exacto. Traza también trabajo archivado en docs/archive/, dejando el reporte junto al artefacto.

- Skill: `juanca202/trace-validate` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add juanca202/trace-validate`
- Raw SKILL.md: https://api.skillmd.com/api/skills/juanca202/trace-validate/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: juanca202 (https://skillmd.com/u/juanca202)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/juanca202/trace-validate

---


# Skill: Validar trazabilidad de un trabajo

Genera un **reporte de trazabilidad** que cruza los **criterios de aceptación** de un trabajo contra los **casos de prueba** y los **artefactos de prueba automatizada** (unit, integración, e2e) presentes en el repositorio, y emite un **veredicto** sobre si el trabajo queda cubierto.

El trazado primario es para **historias de usuario** (`US-XXX`) con sus **criterios de aceptación**. Sirve igual para cualquier otro artefacto que tenga criterios con **identificador codificado** — el formato es indiferente (`AC-001`, `1.1`, `R-3`…) y se usa **verbatim**, sin normalizar (ver [Tipos de trabajo y criterios](#tipos-de-trabajo-y-criterios)). Es el mismo contrato que produce `test-define`.

> **Qué hace:** lee, mapea, **obtiene los resultados de pruebas delegando en `quality-check`** y reporta. Es una actividad de **verificación**, no de desarrollo.
>
> **No ejecuta pruebas por sí mismo.** La ejecución de la batería de pruebas se **delega en `quality-check`**: si existe una corrida previa fresca de `quality-check` (sin cambios en el código desde entonces), se **reutilizan** sus resultados; si no, se invoca `quality-check` en modo `tests-only` para producirlos. Ver [Resultados de pruebas: delegación en quality-check](#resultados-de-pruebas-delegación-en-quality-check).
>
> **Reporte idempotente.** Si no han cambiado ni el código, ni las pruebas, ni los criterios y casos de prueba del artefacto desde la última vez que se generó el `coverage.md`, **no se genera un documento nuevo**: se devuelven los mismos resultados del reporte existente. Ver [Reutilización del reporte (idempotencia)](#reutilización-del-reporte-idempotencia).
>
> **Qué NO hace:** no escribe ni modifica código de aplicación, no escribe nuevos tests (eso es de `quality-specialist` vía `work-implement`), no edita la especificación de producto (README de la US, `TK-XXX`, `WI-XXX`, `FT-XXX`, `validation.md`, ADRs), **ni corre la suite de pruebas directamente**. Lo único que produce es el **reporte de trazabilidad**. Lo que no se puede determinar de las fuentes va a **Observaciones** o se pregunta al usuario — nunca se inventa cobertura ni resultados.

---

## Alcance del informe

El informe de este skill cubre **un artefacto concreto y sus criterios de aceptación**. La pregunta es «¿cada criterio de *este* trabajo está probado?», así que el universo lo definen los criterios del artefacto, no los archivos que cambiaron ni el repositorio completo.

Consecuencias prácticas:

- **Se validan todos los criterios del artefacto**, aunque en esta rama no se haya tocado el código de alguno de ellos: un criterio cuya prueba nunca se escribió sigue siendo un hueco.
- **No se reportan pruebas ni código ajenos al artefacto.** Tests de otros trabajos, cobertura global o fallos en módulos que no mapean a ningún criterio de este artefacto no entran en la matriz — a lo sumo van a «Observaciones y pendientes».
- **Si la rama abarca varios trabajos**, se valida uno por corrida, con su propio `coverage.md` junto a su artefacto. Los resultados de pruebas sí son compartidos (vienen de la corrida de `quality-check`), pero el **mapeo** es por artefacto.
- **La clave de frescura tiene una mitad de rama y otra de artefacto.** El `FINGERPRINT` cubre el código y los tests de todo el árbol, así que un cambio en *otro* trabajo de la misma rama invalida también este `coverage.md`; el `SPEC_FINGERPRINT` cubre solo la carpeta de **este** artefacto. Es conservador a propósito: prefiere revalidar de más antes que devolver un reporte que ya no corresponde.

---

## Subagente

**Si el proyecto define el subagente `quality-specialist`, ejecutar este skill bajo ese subagente** (es el mismo agente que escribe los tests en el cierre de `work-implement`, por lo que es el contexto natural para validarlos). Si no existe en el proyecto, ejecutar el flujo normalmente.

---

## Tipos de trabajo y criterios

El tipo se determina por el identificador que indique el usuario o por la ruta de trabajo. Cada tipo fija de dónde se leen los criterios de aceptación y con qué códigos se traza.

| Tipo | Identificador | Dónde viven los criterios | Códigos a trazar |
|------|---------------|---------------------------|------------------|
| **Historia de usuario** | `US-XXX` | Sección **Criterios de aceptación** del `README.md` de la US (lista plana, habitualmente `AC-XXX`) | El identificador de cada criterio, en el orden en que aparecen |
| **Tarea de mantenimiento** | `WI-XXX` | Sección **## Criterios de aceptación** del `README.md` del WI (`WI-XXX-[kebab]/README.md`) | El identificador de cada criterio, en el orden en que aparecen |
| **Feature (funcionalidad ya implementada)** | `FT-XXX` | Sección **## Criterios de aceptación** del `README.md` del feature (`docs/specs/features/FT-XXX-[slug]/README.md`) | El identificador de cada criterio, en el orden en que aparecen |
| **Cualquier otro artefacto de especificación** | Ruta o nombre que indique el usuario | La sección de criterios del documento (puede llamarse «Criterios de aceptación», «Requisitos», «Acceptance Criteria»…) | El identificador de cada criterio, tal como aparece |

> **El requisito no es el formato, es que exista identificador.** En todo el flujo, «criterio» se refiere al identificador **tal como está escrito en el artefacto** — `AC-012`, `1.3`, `R-3`, `CA-07` son todos válidos. **Nunca normalizar ni renombrar**: el vínculo de trazabilidad debe ser buscable literalmente tanto en el artefacto como en los TCs que produjo `test-define`. Si el trabajo **no tiene criterios**, o los tiene sin identificador, no hay nada que trazar → **bloquear** (ver «Cuándo bloquear»).

> **FT — validar cobertura de funcionalidad ya implementada.** Un `FT-XXX` es el
> registro de una funcionalidad **ya implementada** (inferida de código legacy por el
> análisis de `work-research`, o simplemente el registro de funcionalidad existente),
> no trabajo por construir. Validarlo responde: *¿esa funcionalidad ya existente está
> cubierta por pruebas?* Un criterio `UNCOVERED` significa que ese comportamiento
> **carece de pruebas** (un hueco a cerrar escribiendo tests), **no** que falte código
> funcional. Sus casos de prueba documentados viven en
> `docs/specs/features/FT-XXX-[slug]/test-cases/`, igual que en una US o un WI.

---

## Modificadores de invocación

Las **claves** son en inglés (estándar); el usuario puede nombrarlas en español y se mapean. Sin modificador, se asume el comportamiento por defecto.

| Modifier | Efecto exacto |
|----------|----------------|
| `revalidate` | **Forzar la regeneración** del `coverage.md` aunque el existente esté fresco: ignorar la comprobación del Paso 0 y ejecutar el flujo completo. Sinónimos aceptados: «revalidar», «forzar», «vuelve a validar». Necesario cuando los criterios viven **fuera del repo** (un ticket, un documento externo) y han cambiado allí — eso el `SPEC_FINGERPRINT` no puede verlo. Mismo modificador y mismo significado que en [`code-review`](../code-review/SKILL.md). |

---

## Cómo preguntar al usuario

Mecanismo, ritmo y fallback compartidos: [`${PLUGIN_ROOT}/reference/asking.md`](../../reference/asking.md).

Cada vez que este skill o sus referencias digan *preguntar*, *pedir*, *confirmar*, *validar* o *sugerir* algo al usuario, asume ese mecanismo; no se repite allí.

---

## Rutas de las referencias compartidas

`${PLUGIN_ROOT}` es la **raíz del plugin instalado** (la carpeta que contiene `skills/`, `agents/` y `reference/`), y toda referencia compartida de este skill se escribe como `${PLUGIN_ROOT}/reference/<archivo>.md`. Resolverla así, **en este orden**: (1) en Claude Code, `${PLUGIN_ROOT}` **es** `${CLAUDE_PLUGIN_ROOT}` — comprobar con `echo "$CLAUDE_PLUGIN_ROOT"` y usar ese valor; (2) en cualquier otro cliente, o si la variable está vacía, la carpeta desde la que se cargó este archivo, dos niveles arriba. El destino de cada enlace markdown (`../../reference/…`) existe solo para navegar el repositorio en GitHub o en un editor: **no** resolverlo desde el directorio de trabajo. **Nunca buscar `reference/` en el proyecto**: un `<proyecto>/reference/language.md` que no existe no es un archivo que falte, es una ruta mal resuelta — corregir la raíz y volver a leer, sin preguntar al usuario ni saltarse la lectura.

## Resolución de idioma

Antes de ejecutar este skill, DEBES leer [`${PLUGIN_ROOT}/reference/language.md`](../../reference/language.md).

Las reglas de `language.md` son obligatorias y tienen prioridad para determinar el idioma de todos los artefactos y mensajes generados por este skill.

No continúes hasta haber leído y aplicado `language.md`.

**Excepción deliberada:** la salida y los mensajes de error de las herramientas de prueba no se traducen; se citan literales.

---

## Límite de intentos y escalamiento

Antes de ejecutar este skill, DEBES leer [`${PLUGIN_ROOT}/reference/escalation.md`](../../reference/escalation.md).

Las reglas de `escalation.md` son obligatorias y determinan, vía `escalation.maxAttempts` y `escalation.onLimit`, cuántos intentos consecutivos se hacen sobre **el mismo** problema que no se resuelve —una corrida de pruebas que no se logra completar o un criterio que no se logra evidenciar— y qué se hace al agotarlos: detener el trabajo sobre ese problema, presentar el **parte de bloqueo** y preguntar al usuario cómo seguir (`ask`), o marcarlo como `BLOCKED` en el informe y continuar con el alcance que no dependa de él (`report`).

El contador es **por problema**, no global, y **el límite es un techo, no una cuota**: si no hay una hipótesis nueva que justifique el siguiente intento, se escala ya. Nunca se «resuelve» un bloqueo desactivando o saltando una prueba, relajando una aserción ni bajando un umbral.

No continúes hasta haber leído y aplicado `escalation.md`.

---

## Información requerida antes de generar el reporte

No inventar nada. Si un dato no es explícito, obtenerlo del repo o preguntar al usuario.

| Dato | Cómo obtenerlo | Si no está disponible |
|------|----------------|-----------------------|
| **Trabajo a validar** | Indicado por el usuario o inferido de la ruta de trabajo; determinar el tipo (`US-XXX` / `WI-XXX` / `FT-XXX` / otro artefacto) | Preguntar qué trabajo validar; sin él no se puede generar el reporte |
| **Criterios de aceptación** | Según el tipo (ver [Tipos de trabajo y criterios](#tipos-de-trabajo-y-criterios)) | Si el trabajo no tiene criterios de aceptación: **bloquear** y reportar — sin criterios no hay nada que trazar |
| **Casos de prueba** | **Fuente primaria:** la carpeta `test-cases/` **junto al artefacto** y su índice `test-cases/README.md`, más la línea `Casos de prueba:` que `test-define` deja bajo cada criterio. **Fallback:** inferir desde los tests del repo | Si no hay casos documentados, derivar la cobertura desde los artefactos de prueba del repo |
| **Artefactos de prueba** | Buscar en el repo archivos de test unit / integración / e2e relacionados con el trabajo (ver el Paso 2 en `references/flow.md`) | Si no se encuentran, marcar criterios sin artefacto como `UNCOVERED` y dejar Observación |
| **Resultados de pruebas** | **Delegados en `quality-check`**: caché fresca `test-run.json` o invocación `tests-only` (ver [Resultados de pruebas: delegación en quality-check](#resultados-de-pruebas-delegación-en-quality-check)) | Si `quality-check` no puede ejecutarlas (sin stack, entorno sin correr, no disponible en la sesión) o el usuario declina la delegación: las filas **con artefacto** van con `Ejecución = —` y `Resultado = `NOT_RUN`, y el motivo a «Observaciones y pendientes» |
| **Alcance** | Todo el trabajo por defecto; el usuario puede acotar a ciertos criterios | Si es ambiguo, preguntar |

> Leer **siempre** el documento de criterios completo (el `README.md` del trabajo, o el archivo del artefacto externo) antes de generar el reporte. No asumir criterios que no estén escritos.

---

## Flujo

Resumen de los pasos. El detalle íntegro de cada paso está en **`references/flow.md`** (leerlo antes de ejecutar el flujo).

0. **Comprobar frescura del reporte** — si ya existe `coverage.md` con un fingerprint guardado y no hubo cambios en los archivos desde entonces, **devolver el reporte existente sin regenerarlo** (ver [Reutilización del reporte (idempotencia)](#reutilización-del-reporte-idempotencia)). Solo si hay cambios (o el usuario pide revalidar) continuar con los pasos siguientes.
1. **Localizar y leer el trabajo** — resolver tipo y ubicación; extraer todos los criterios con su identificador **verbatim**. Sin criterios (o sin identificador) → bloquear (ver «Cuándo bloquear»).
2. **Inventariar casos y artefactos** — leer la carpeta `test-cases/` del artefacto y su índice como fuente primaria, y clasificar los tests del repo por tipo, con ruta y criterio.
3. **Mapear cobertura fila a fila** — expandir cada criterio en sus filas (criterio × TC × tipo declarado), rellenar `Evidencia` y derivar el estado del criterio (ver «Estados de cobertura») con sus observaciones. No forzar mapeos inciertos.
4. **Obtener resultados de pruebas (delegando en `quality-check`)** — reutilizar la caché `test-run.json` si está fresca, o invocar `quality-check` en modo `tests-only`; mapear por suite a las filas y rellenar `Ejecución` (`quality-check` / `Manual` / `—`) y `Resultado` (`PASS` / `FAIL` / `NOT_RUN` / `UNCOVERED` / `N/A`). **No** correr pruebas directamente. Nunca fabricar resultados (ver [Resultados de pruebas: delegación en quality-check](#resultados-de-pruebas-delegación-en-quality-check) y `references/flow.md`).
5. **Redactar el reporte** desde `assets/coverage-template.md` (leerla antes de redactar): cabecera, Resumen (prosa con la procedencia + tabla de indicadores), **cobertura por criterio** (una fila por criterio, con su Estado), **matriz de trazabilidad** (una fila por criterio × TC × tipo declarado, con Evidencia, Ejecución y Resultado) y, si los hay, los caveats globales en «Observaciones y pendientes». Ver [Vistas del reporte](#vistas-del-reporte-cobertura-por-criterio-y-matriz).
6. **Emitir el veredicto** (ver «Veredicto») respondiendo si todos los criterios quedan cubiertos.
7. **Entregar y guardar** el reporte en la ubicación del tipo (ver «Ubicación de archivos»), **grabando el fingerprint** del estado actual para la próxima comprobación de frescura; no modificar otros artefactos.

---

## Vocabulario de veredictos y estados

Antes de redactar cualquier informe, DEBES leer [`${PLUGIN_ROOT}/reference/verdicts.md`](../../reference/verdicts.md).

Las reglas de `verdicts.md` son obligatorias: el valor canónico y el símbolo son estables, y la **etiqueta que lee la persona se redacta siempre en el idioma resuelto** por `language.md`. Ninguna etiqueta de este skill se fija en un idioma concreto.

No continúes hasta haber leído y aplicado `verdicts.md`.

---

## Estados de cobertura

Los valores de la columna `Estado` son **canónicos**: en el reporte se escribe su símbolo seguido de la etiqueta en el idioma resuelto.

| Estado (canónico) | Símbolo | Cuándo aplicarlo |
|-------------------|---------|------------------|
| `COVERED` | `✅` | El criterio tiene al menos un caso de prueba **y** el artefacto que le corresponde, y lo valida de forma completa. Si se ejecutó automáticamente, pasó. **Un criterio cuyos TCs son `Manual` por diseño también es `COVERED`** si esos TCs lo validan por entero — ver la nota de abajo. |
| `PARTIAL` | `⚠️` | El criterio está cubierto solo en parte: hay prueba pero no abarca todo el criterio, el artefacto existe pero no se pudo ejecutar, el resultado fue parcial, existe solo validación manual **no declarada como tal** (TC automatizable aún sin automatizar), o la suite que agrupa su test dio `FAIL` pero no se pudo aislar si el test específico del criterio fue el que falló (ver «Mapeo a la matriz» más abajo). Detallar el límite en Observaciones. |
| `UNCOVERED` | `❌` | No existe caso de prueba ni artefacto que valide el criterio, o la prueba asociada **falló** y se pudo aislar que fue la suya. |

> **Cobertura ≠ ejecución.** Que exista prueba que valida el criterio es una cosa; que haya corrido y con qué resultado, otra. Un criterio cuya prueba **falló** —y se pudo aislar que fue la suya— se reporta `UNCOVERED` con el fallo en Observaciones; si la suite falló pero no se puede aislar el test, es `PARTIAL` (ver «Mapeo a la matriz»).
>
> **El `status` del TC filtra la cobertura.** La fuente primaria es la **columna `Estado` del índice** `test-cases/README.md`; si el índice no la trae, abrir el `TC-XXX-*.md` y leer su campo `Estado`, con la marca oculta `<!-- tc:status=Draft|Ready|Obsolete · testType=… -->` como desempate. Los **valores** (`Draft`/`Ready`/`Obsolete`) son canónicos y no se traducen; lo que va en el idioma resuelto es la etiqueta del campo. Un TC **`Obsolete`** no cuenta como cobertura (dejar Observación); uno **`Draft`** cuenta como cobertura `PARTIAL` (aún no está listo para respaldar el criterio); solo los **`Ready`** cuentan como cobertura plena. Si el TC no trae el campo, tratarlo como `Ready`. Esto es distinto del `Estado:` del **artefacto**, que este skill no exige (ver «Cuándo bloquear»).
>
> **Manual por diseño vs. pendiente de automatizar.** `test-define` distingue los dos casos en el campo **Tipo de prueba** del TC: `Manual` significa *no se automatiza por decisión de diseño*; cualquier otro valor (`Unit`, `Integration`, `API Test`, `Visual Test`, `E2E`) significa *debería existir artefacto automatizado*. Respetar esa declaración: un criterio con TCs `Manual` que lo cubren por completo es `COVERED` (sus filas van con `Ejecución = Manual` y `Resultado = N/A`), no una deuda; un criterio con TCs automatizables **sin** artefacto es `PARTIAL` o `UNCOVERED`. No penalizar una decisión de diseño ni disimular una automatización pendiente.
>
> **Un TC con varios tipos declarados se cubre por tipo.** Si un TC declara `Unit, E2E` y solo existe el test unitario, el criterio **no** es `COVERED`: la fila `E2E` de la matriz queda en `UNCOVERED` y el criterio baja a `PARTIAL`, con el tipo faltante nombrado en Observaciones. La matriz muestra esa asimetría fila a fila (ver [Vistas del reporte](#vistas-del-reporte-cobertura-por-criterio-y-matriz)).

---

## Vistas del reporte: cobertura por criterio y matriz

El reporte tiene **dos tablas complementarias**, no una. Separarlas evita el problema de meter en una sola fila
información de granularidades distintas (un criterio puede tener varios TCs, y un TC varios tipos de prueba).

El **Resumen** las precede con una tabla de indicadores de **cuatro columnas y una sola fila de cifras**
—criterios de aceptación, cubiertos, parciales, no cubiertos— cuyos tres últimos valores deben sumar el total. No lleva indicadores de
pruebas (fallidas, no ejecutadas): esa granularidad ya está en la matriz y mezclar los dos ejes confunde.
La **procedencia** de los resultados y el `result` **por suite** van en la línea «Pruebas» del Resumen;
`test-run.json` no trae un agregado global, así que no se inventa uno.

**1. Cobertura por criterio** — la vista de veredicto. Una fila por criterio:

| Criterio | Descripción | Estado | Observaciones |
|----------|-------------|--------|---------------|
| AC-2.1 | El usuario recibe confirmación por correo | `PARTIAL` | E2E declarado en TC-001 sin automatizar |
| AC-2.2 | La plantilla del correo respeta la identidad visual | `COVERED` | Validación manual por diseño (TC-004) |

**2. Matriz de trazabilidad** — la vista auditable. **Una fila por cada combinación criterio × TC × tipo de
prueba declarado**: si un TC declara `Unit, E2E`, produce dos filas.

| Criterio | TC | Tipo | Evidencia | Ejecución | Resultado |
|----------|-----|------|-----------|-----------|-----------|
| AC-2.1 | TC-001 | Unit | `tests/unit/notify.test.ts` | quality-check | Paso |
| AC-2.1 | TC-001 | E2E | — | — | `UNCOVERED` |
| AC-2.2 | TC-004 | Manual | `test-cases/TC-004-revision-visual.md` | Manual | N/A |

Semántica de las columnas:

| Columna | Qué contiene | Valores |
|---------|--------------|---------|
| **Criterio** | Identificador **verbatim** del artefacto, repetido en cada fila suya | `AC-012`, `1.3`, `R-3`… |
| **TC** | Caso de prueba que produjo `test-define` | `TC-XXX` · `—` si el criterio no tiene TC documentado (fila **derivada** de un test hallado, o hueco total) |
| **Tipo** | El `Tipo de prueba` **declarado en el TC**; en filas derivadas, el tipo del artefacto hallado | `Manual` · `Unit` · `Integration` · `API Test` · `Visual Test` · `E2E` · `—` (sin TC y sin artefacto) |
| **Evidencia** | La prueba concreta que respalda la fila | Ruta del artefacto automatizado · ruta del TC para filas `Manual` · `—` si esa intención no está materializada |
| **Ejecución** | **Quién** produjo el resultado | `quality-check` · `Manual` · `—` (no se ejecutó) |
| **Resultado** | Qué dio esa prueba. Valores **canónicos**; en el reporte va la etiqueta en el idioma resuelto | `PASS` · `FAIL` · `NOT_RUN` (hay artefacto, no corrió) · `UNCOVERED` (no hay evidencia para ese tipo) · `N/A` (manual por diseño) |

> **Por qué se eliminó la columna `Automática`.** Un `Sí` no decía nada útil: no revelaba qué tipo de prueba
> respaldaba el criterio, dónde vivía, ni quién produjo el resultado. `Tipo` + `Evidencia` + `Ejecución` dan
> esa información sin ambigüedad, y la granularidad por tipo hace **visible el hueco** (la fila `E2E` sin
> evidencia) en lugar de esconderlo tras un `Sí` que solo hablaba del test unitario.

> **`Evidencia = —` implica `Ejecución = —` y `Resultado = `UNCOVERED`.** No hay excepción: si no hay
> artefacto, no hubo ejecución ni puede haber resultado. Al revés no: puede haber evidencia con
> `Ejecución = —` y `Resultado = `NOT_RUN` (el artefacto existe pero `quality-check` no pudo correrlo;
> la razón va a «Observaciones y pendientes»).

> **Criterio sin TC documentado pero con test hallado.** Si el mapeo se infirió desde el repo (fallback del
> Paso 2), la fila se escribe igual con `TC = —` y el **tipo del artefacto hallado**, y se anota en
> Observaciones que el mapeo es inferido, no declarado. Solo el criterio **sin TC y sin artefacto** produce
> la fila vacía `— | — | — | — | `UNCOVERED`.

**Derivación del `Estado` del criterio** a partir de sus filas. Evaluar **en orden** y detenerse en la primera que aplique:

| # | Filas del criterio | Estado |
|---|--------------------|--------|
| 1 | **Ninguna** fila aporta cobertura — todas en `UNCOVERED`, o el criterio no tiene TC ni artefacto, o sus únicos TCs son `Obsolete` — **o** alguna fila dio `FAIL` **aislado a su test** | `UNCOVERED` |
| 2 | **Alguna** fila en `UNCOVERED`/`NOT_RUN`, algún TC en `Draft` u `Obsolete` conviviendo con cobertura válida, o un `FAIL` de suite **no aislable** | `PARTIAL` |
| 3 | **Todas** con `Resultado` ∈ {`PASS`, `N/A`} y todos los TCs en `Ready` (o sin campo `Estado`) | `COVERED` |

> **Dos destinos de observación, no intercambiables.** La prueba para elegir: *¿se puede atribuir a un
> criterio concreto?* Si sí, va en la **columna `Observaciones`** de la tabla 1; si no, en la **sección
> «Observaciones y pendientes»**. La matriz no lleva columna de observaciones.
>
> | Destino | Qué recibe |
> |---------|------------|
> | **Columna `Observaciones`** (tabla 1) | Tipo declarado sin automatizar · TC en `Draft`/`Obsolete` · `FAIL` no aislable · suite efectiva distinta del tipo declarado · mapeo inferido en vez de declarado · **cobertura apoyada en TCs `Manual`** (aunque sea por diseño: es el caveat que justifica el `APPROVED_WITH_NOTES`) · límite de la cobertura |
> | **Sección «Observaciones y pendientes»** | Suite `coverage` en `FAIL` · `workingTreeClean: false` (árbol sucio) · clases de prueba ausentes en el repo · ejecución no delegable y su motivo · tests que no se pudieron vincular con certeza a ningún criterio · tests o fallos ajenos al artefacto |
>
> La sección global **se omite entera** si no hay ninguno. Cuando el skill diga «dejar Observación» sin más,
> aplicar esta prueba.

> **La derivación no es cerrada sobre la matriz: dos datos viven fuera de ella.** (a) el `Estado` del TC
> (`Draft`/`Obsolete`/`Ready`, que viene del propio TC) y (b) si un `FAIL` pudo **aislarse** al test del
> criterio. Ambos se anotan siempre en la columna **Observaciones de la tabla 1** — sin esa anotación, dos
> criterios con filas idénticas quedarían con estados distintos sin explicación. Un `FAIL` no aislable se
> escribe igual (`Resultado = FAIL`) en la matriz; lo que lo distingue es la Observación «suite `X` en
> `FAIL`, no se pudo aislar el test» y el `Estado = `PARTIAL` resultante.
>
> **TCs `Obsolete`.** Sus filas se conservan en la matriz por trazabilidad con su valor real, pero **no
> cuentan como cobertura** al derivar el estado: si son los únicos TCs del criterio, este cae en la fila 1
> aunque sus filas digan `PASS`; si conviven con TCs `Ready` que sí lo cubren, el criterio es como máximo
> `PARTIAL` (fila 2). En ambos casos, Observación explicando que el TC está obsoleto.

---

## Reutilización del reporte (idempotencia)

Mismo principio de caché que `quality-check` y [`code-review`](../code-review/SKILL.md#reutilización-del-informe-idempotencia)
—las **tres** puertas del cierre lo aplican, cada una sobre su propio artefacto—: **si no hubo cambios en los
archivos desde la última generación, no se produce un reporte nuevo** — se devuelven los mismos resultados del
`coverage.md` existente. Esto evita rehacer el mapeo y volver a delegar la ejecución de pruebas cuando nada
cambió.

> **Contexto de ejecución.** Como `quality-check`, este skill es una **compuerta de cierre** (al integrar o
> antes del PR), no corre por tarea ni durante la implementación. La frescura se evalúa sobre la rama
> **consolidada** del cierre. En el pipeline típico de cierre `pr-create` corre `quality-check` **primero**
> (produce `test-run.json` fresco) y luego `trace-validate`, que reutiliza esa corrida — sin doble
> ejecución de pruebas; y si el código tampoco cambió desde el último `coverage.md`, este Paso 0 lo
> devuelve sin regenerarlo.

**Clave de frescura — dos hashes, porque el reporte depende de dos cosas distintas.**

| Clave | Qué cubre | Cómo se calcula |
|-------|-----------|-----------------|
| `FINGERPRINT` | **El código y los tests.** El fingerprint canónico de la tubería, idéntico al de `quality-check` y `code-review`: excluye toda carpeta oculta, cualquier `docs/`, toda la documentación en texto (`*.md`, `*.rst`, `*.adoc`, `LICENSE*`, `CHANGELOG*`…) y el `.gitignore`, para que ni escribir un artefacto generado ni editar documentación invalide la caché: **solo se mueve cuando cambia el código**. Receta exacta en [`quality-check`](../quality-check/SKILL.md#caché-de-corrida-de-pruebas-compartida-con-trace-validate). | Sobre todo el árbol, menos las exclusiones |
| `SPEC_FINGERPRINT` | **Los criterios y los casos de prueba** del artefacto que se valida: su `README.md` y su carpeta `test-cases/`. Viven bajo `docs/specs/`, que el `FINGERPRINT` excluye — sin esta segunda clave, reescribir un criterio no invalidaría nada. | Sobre la **carpeta del artefacto**, excluyendo su propio `coverage.md` |

`bash
NO_REPORT=":(exclude)${ARTEFACTO%/}/coverage.md"
SPEC_FINGERPRINT=$( { git -C "$ROOT" ls-files -s              -- "$ARTEFACTO" "$NO_REPORT"; \
                      git -C "$ROOT" status --porcelain -uall -- "$ARTEFACTO" "$NO_REPORT"; \
                      git -C "$ROOT" diff                     -- "$ARTEFACTO" "$NO_REPORT"; \
                    } | git hash-object --stdin )
`

donde `$ARTEFACTO` es la carpeta del trabajo (`docs/specs/user-stories/US-042-…/`) o, si el artefacto es un archivo suelto, su ruta. Se reutiliza el reporte **solo si coinciden los dos**.

> **La exclusión del propio reporte no es opcional.** El `coverage.md` vive **dentro** de `$ARTEFACTO`: sin `$NO_REPORT`, escribirlo en el Paso 7 movería el `SPEC_FINGERPRINT`, la marca de pie guardaría el hash de *antes* de escribir, y ninguna corrida posterior coincidiría — la idempotencia no se dispararía **nunca**. La clave cubre las **entradas** del reporte, no su salida; es el mismo motivo por el que el `FINGERPRINT` excluye `**/coverage.md`.
>
> **Y tiene que ser una ruta literal interpolando `$ARTEFACTO`, no un glob.** `':(exclude,glob)**/coverage.md'` junto a un pathspec **positivo** hace que git excluya **todo**: las tres órdenes devuelven vacío, la clave queda fija en el hash del blob vacío, y entonces la idempotencia se dispara siempre y un criterio editado nunca invalida el reporte. El `EXC` del `FINGERPRINT` puede usar globs porque ahí no hay pathspec positivo.

> **Por qué dos claves y no una.** Meter `docs/` entero en el `FINGERPRINT` haría que escribir cualquier informe invalidara las tres cachés — el problema que las exclusiones resuelven. Acotar la segunda clave a **la carpeta del artefacto** captura exactamente lo que este reporte traza, sin arrastrar el resto de la documentación. Y a diferencia de «detectar que el usuario editó los criterios», es una comprobación **determinista**: no depende de que el cambio haya ocurrido en esta sesión, ni de que alguien lo mencione.

> **Lo que sigue sin cubrirse:** criterios que viven **fuera del repo** (un ticket de un tracker, un documento externo). Ahí no hay nada que hashear; si el artefacto es externo, decirlo en el reporte y pedir `revalidate` cuando cambie.

> **Un solo cálculo por corrida, con una excepción.** Ambos hashes se computan una vez, en el Paso 0. El
> `FINGERPRINT` sirve para las **dos** comprobaciones de frescura —la del `coverage.md` (Paso 0) y la del
> `test-run.json` (Paso 4, delegación)—; el `SPEC_FINGERPRINT` solo para la primera. Los dos se regraban en el
> Paso 7. La delegación en `tests-only` no toca código (y el `.gitignore` que puede normalizar está excluido de
> la receta), así que el `FINGERPRINT` calculado en el Paso 0 sigue siendo válido al grabar: **no hace falta
> recalcularlo**. Si aun así el valor guardado por `quality-check` en `test-run.json` no coincide con el del
> Paso 0, algo cambió el código en medio: tratar la caché como obsoleta, no como fresca.

**Comportamiento (Paso 0 del flujo):**

1. Resolver la ubicación del trabajo y **calcular `FINGERPRINT` y `SPEC_FINGERPRINT`**. Siempre, haya reporte
   previo o no: el Paso 7 los necesita para grabar la marca de pie, también en la primera validación.
2. Buscar su `coverage.md`. Si no existe → generar normal (no hay caché).
3. Si existe, leer los hashes guardados en su marca de pie
   (`<!-- trace-validate:verdict=<canónico> · fingerprint=<hash> · spec=<hash> · generated=YYYY-MM-DD -->`) y reutilizarlo **solo si
   se cumple todo**: coinciden **los dos** hashes, el reporte **no** registra una ejecución fallida, y el
   usuario no pasó `revalidate`.
   - **Se cumple** → **no regenerar**: devolver el veredicto y el resumen del reporte existente tal cual,
     e indicar al usuario que no hubo cambios desde la última validación ({{fecha guardada}}). No reescribir
     el archivo, no delegar en `quality-check`.
   - **Falla algo**, no hay marca de pie (reportes antiguos), o el usuario pide `revalidate` →
     ejecutar el flujo completo (Pasos 1-7) y **regrabar** ambos hashes al guardar (Paso 7).
4. La marca de pie **se conserva** en el documento publicado (no se elimina como el bloque de instrucciones
   de la plantilla). Si está **ilegible o incompleta** (un hash que no parsea, marca truncada, o falta el
   campo `spec=`), no intentar repararla ni adivinar: tratar
   la caché como ausente, regenerar y regrabarla.

> **Un reporte que no pudo ejecutar las pruebas nunca se sirve desde caché.** Si el reporte existente tiene
> filas en `NOT_RUN` porque la delegación no fue posible (sin red, sin runner, el usuario declinó), su
> `⚠️` describe un fallo **de entorno**, no del código. Congelarlo significaría arrastrar ese «no se pudo
> comprobar» para siempre aunque el entorno ya funcione. Regenerar y volver a intentar la delegación.

> Un cambio en una **carpeta oculta** (`.sdd-devkit/`, `.github/`…), en `docs/` fuera de la carpeta del
> artefacto, o en un `coverage.md` **no** cuenta como cambio: están excluidos, así que ningún artefacto
> que generen este skill, `quality-check`, `code-review` o `arch-audit` desplaza la clave. Los criterios y los
> `TC-XXX` **sí** cuentan, vía `SPEC_FINGERPRINT`.

---

## Resultados de pruebas: delegación en quality-check

`trace-validate` **no ejecuta la suite de pruebas**. La ejecución es responsabilidad de `quality-check`,
que la persiste en un artefacto reutilizable `test-run.json` (esquema `test-run/v1`). Como el review es
una **corrida completa** de la rama, este artefacto vive en una **ubicación fija**, no por unidad:
**`.sdd-devkit/test-run.json`**, en la raíz del repositorio.

**Cómo obtener los resultados (Paso 4 del flujo):**

1. **Reusar el fingerprint canónico** ya calculado en el Paso 0 (mismo valor; no recalcular).
2. **Si existe `test-run.json`, su `schema` es `test-run/v1`, su `generatedBy` es `"quality-check"`, su `git.fingerprint` coincide y su
   `suites[]` cubre el conjunto vigente** (las dos fijas, más e2e si el repo la ejecuta, las que
   declare el estándar de testing y la entrada `architecture` si el repo tiene runner de arquitectura) → caché **fresca**: no hubo cambios desde la corrida de `quality-check`. **Reutilizar**
   los resultados por suite sin ejecutar nada. Si `generatedBy` trae cualquier otro valor,
   **descartar la caché** y delegar: `quality-check` es el único productor autorizado. **Si el estándar de
   testing cambió** desde la corrida (suites de más o de menos), la caché es obsoleta **aunque el fingerprint
   coincida** —el estándar vive en `docs/`, excluido del fingerprint—: delegar. Anotar la procedencia en la línea «Pruebas» del **Resumen**: «resultados tomados de
   la corrida de `quality-check` del {{commit/fecha}}».
3. **Si no existe o el fingerprint difiere** (hubo cambios, o nunca corrió) → **delegar en `quality-check`
   en modo `tests-only`**, que ejecuta solo los checks de pruebas, escribe `test-run.json` y devuelve los
   resultados. Luego consumir esa caché ya fresca.
4. **Si `quality-check` no puede ejecutarlas** (stack no detectable, entorno sin poder correr, dependencias
   ausentes, o el usuario declina la delegación) → las filas con artefacto van con `Ejecución = —` y
   `Resultado = `NOT_RUN`, con la razón en «Observaciones y pendientes», y se entrega igualmente la matriz con los
   artefactos hallados. **Nunca fabricar resultados.**

**Mapeo a la matriz.** Cada entrada `suites[]` de `test-run.json` trae `type` y `result`
(`PASS`/`FAIL`/`SKIPPED`/`N/A`). Las **dos fijas** —`unit`, `coverage`— vienen siempre; `e2e` solo si el repo la ejecuta, y el resto son
**suites configuradas** en el estándar de testing del repo, cuyo `type` es el `ID` del requisito que las
declara (p. ej. `integration-testing`, `contract-testing`) y que traen su referencia global en `standard`
(p. ej. `testing/integration-testing`). **Pueden no existir**: si el estándar no declara integración, no hay
entrada para ella —y **no** hay que buscar una clave fija llamada `integration`—, así que una fila de tipo
`Integration` se resuelve contra la suite donde el repo la tenga (típicamente `unit`) o queda
`NOT_RUN`. **No inferir una suite ausente ni inventar su resultado.** La suite **`coverage` no se mapea a ningún
criterio**: es cobertura de líneas/ramas, una métrica del repo que juzga `quality-check`, no cobertura
funcional; si viene en `FAIL`, mencionarlo en «Observaciones y pendientes» y nada más. **La entrada
`architecture` tampoco se mapea**: es la corrida del runner de validaciones de arquitectura del repo,
cacheada en el mismo archivo para `arch-audit`. **No es una clase de prueba**, no cubre ningún criterio de
aceptación y **no se lista en la matriz**; si viene en `FAIL`, como mucho una línea en «Observaciones y
pendientes». Su presencia o ausencia **nunca** degrada una fila a `NOT_RUN`. Mapeo al reporte: `PASS`→`PASS`,
`FAIL`→`FAIL`, `SKIPPED`→`NOT_RUN`, `N/A` (el repo no tiene esa suite)→`NOT_RUN`, dejando en
«Observaciones y pendientes» que esa clase de prueba no existe en el repo. Un criterio cuya prueba asociada dio `FAIL` **y
se pudo aislar que fue la suya** se reporta `UNCOVERED` con el fallo en Observaciones; si la suite falló
sin poder aislar el test, es `PARTIAL` (ver «Estados de cobertura» y la nota de granularidad más abajo).

**El mapeo es por fila, no por criterio.** Cada fila de la matriz (criterio × TC × tipo) se resuelve así:

| Situación de la fila | Evidencia | Ejecución | Resultado |
|----------------------|-----------|-----------|-----------|
| Tipo declarado sin artefacto en el repo | `—` | `—` | `UNCOVERED` |
| Artefacto hallado y la suite que lo corre dio `PASS` | ruta | `quality-check` | `PASS` |
| Artefacto hallado y su test dio `FAIL` (aislable) | ruta | `quality-check` | `FAIL` |
| Artefacto hallado, suite en `FAIL` **sin** poder aislar el test | ruta | `quality-check` | `FAIL` (+ Observación «no aislable» → criterio `PARTIAL`) |
| Artefacto hallado, suite `SKIPPED` / ausente / no ejecutable | ruta | `—` | `NOT_RUN` |
| TC `Manual` por diseño | ruta del TC | `Manual` | `N/A` |

`Ejecución` dice **quién** produjo el resultado, no de qué clase es la prueba: eso ya lo dice la columna
`Tipo` y la ruta de `Evidencia`. La **suite efectiva** que corrió el test (dónde vive realmente en el repo)
solo se menciona cuando **no** coincide con el tipo declarado —un `API Test` que corre en `unit`, por
ejemplo—, y va en Observaciones, no en la matriz.

> **Granularidad suite vs. criterio:** `result` es por **suite completa**, no por test individual. Si
> varios criterios mapean a tests dentro de la misma suite y esa suite da `FAIL`, no propagar `FAIL`/`UNCOVERED` a todos ellos sin más: si el `summary` u otro detalle de `quality-check` permite aislar qué test
> falló, marcar solo ese criterio; si no hay forma de aislarlo, marcar los criterios afectados como
> `PARTIAL` (no `UNCOVERED`) con la ambigüedad explicada en Observaciones. Detalle completo en
> `references/flow.md` (Paso 4).

> Si el proyecto no usa `quality-check` (no está disponible en la sesión), degradar con elegancia: reportar
> las filas **con artefacto** en `Ejecución = —` / `Resultado = `NOT_RUN` («ejecución delegada no disponible») —las que no tienen artefacto siguen en `UNCOVERED`— y entregar la cobertura estática. No
> reintroducir un runner propio en `trace-validate`.

---

## Cuándo bloquear

Parar y reportar (sin generar reporte parcial) cuando:

- El trabajo no existe o no se encuentra su documento de criterios.
- No hay sección de criterios de aceptación, está vacía, o los criterios **no tienen identificador codificado** (el formato es indiferente; lo que no puede faltar es el identificador): no hay nada que trazar de forma trazable; sugerir alinear el trabajo con su skill de definición/planificación antes de validar (para un `FT-XXX`, completar su especificación con el flujo «Analizar legado» de `work-research`).

> **No bloquear** por que el artefacto no siga las convenciones del plugin: identificadores fuera del formato `AC-XXX`, ubicación fuera de `docs/specs/` o ausencia de campo `Estado:` **no** son motivos de bloqueo.

`
WARNING No es posible generar el reporte de trazabilidad:
- <razón concreta>
- <acción sugerida: p. ej. definir los criterios de aceptación del trabajo antes de validar>
`

---

## Ubicación de archivos

Layout completo del harness, identificadores y contrato de archivado: [`${PLUGIN_ROOT}/reference/artifacts.md`](../../reference/artifacts.md).

Lo propio de este skill:

| Rol | Ruta |
|-----|------|
| Artefacto a trazar (entrada) | La carpeta de la `US-XXX` / `WI-XXX` / `FT-XXX`; para cualquier otro artefacto, la ruta que indique el usuario |
| Casos de prueba documentados (entrada, los produce `test-define`) | `test-cases/` **dentro de la carpeta del artefacto**, con su índice `test-cases/README.md` |
| Caché de co

…(truncated)
