---
name: audit_e2e_coverage
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func AuditE2ECoverage(roots []string) (E2ECoverageReport, error)"
description: "Escanea directorios en busca de app.md y reporta cuales declaran e2e_checks en su frontmatter YAML. Retorna total, with_checks, missing (paths relativos) y coverage_pct (0-100). Impuro por lectura de filesystem."
tags: [e2e_checks, coverage, audit, doctor, infra]
params:
  - name: roots
    desc: "lista de directorios raiz a escanear recursivamente buscando app.md (ej. ['apps', 'cpp/apps', 'projects']). Directorios inexistentes se ignoran silenciosamente."
output: "E2ECoverageReport con total/with_checks/missing/coverage_pct. Error no nil solo ante fallo real de I/O."
uses_functions: []
uses_types: ["e2e_coverage_report_go_infra"]
returns: ["e2e_coverage_report_go_infra"]
returns_optional: false
error_type: "error_go_core"
imports: ["fmt", "math", "os", "path/filepath", "strings"]
tested: true
tests:
  - "directorio vacio sin app.md retorna Total=0 y CoveragePct=0"
  - "todos los app.md con e2e_checks retorna CoveragePct=100"
  - "cobertura parcial calcula CoveragePct correcto y puebla Missing"
test_file_path: "functions/infra/audit_e2e_coverage_test.go"
file_path: "functions/infra/audit_e2e_coverage.go"
---

## Ejemplo

```go
report, err := infra.AuditE2ECoverage([]string{"apps", "cpp/apps", "projects"})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Coverage: %.2f%% (%d/%d apps)\n", report.CoveragePct, report.WithChecks, report.Total)
for _, m := range report.Missing {
    fmt.Println("  missing:", m)
}
```

## Cuando usarla

Usar como backend de `fn doctor e2e-coverage` para detectar apps sin contrato de validacion.
Tambien util antes de crear proposals `e2e_check_add`: llamar primero para obtener la lista
de apps que necesitan cobertura y priorizar las que mas se usan.

## Gotchas

- Solo detecta `e2e_checks:` como substring en el frontmatter — no valida la estructura YAML
  completa del bloque. Un `# e2e_checks:` comentado pasaria como cubierto.
- Los paths en `Missing` son los paths tal como los devuelve `filepath.WalkDir`, que pueden
  ser absolutos si los elementos de `roots` son absolutos. Para comparacion reproducible,
  pasar roots relativas al cwd.
- Un `app.md` no legible (permisos) se cuenta en `Total` y se añade a `Missing` — no aborta
  el scan.
