# 🔊 Fase 7 — Verbose Flag + Adaptadores OSS en Local
## 🤔 ¿Qué hago? ¿Cómo lo hago? ¿Y para qué lo hago?
### ¿Qué hago?
Añado el flag `--verbose` (`-v`) al comando `scan` para dar visibilidad completa de qué detectores corren, cuántos ficheros procesan y qué adaptadores OSS están disponibles o se saltan. Además instalo todos los adaptadores OSS en el entorno local de desarrollo para que el scanner opere al 100%.
### ¿Cómo lo hago?
1. 🏷️ **Sentinel `ErrToolNotFound`** — nuevo tipo de error en `internal/adapter/errors.go` que permite al scanner distinguir "herramienta ausente" de "error de ejecución" o "0 findings".
2. 🔌 **Actualización de adaptadores** — los cuatro adaptadores devuelven `nil, ErrToolNotFound` en lugar de `nil, nil` cuando `exec.LookPath` falla.
3. 🔊 **Flag `--verbose` / `-v`** — añadido al comando `scan`. Cuando activo, instancia `scanner.NewVerbose(cfg, os.Stderr)` en lugar de `scanner.New(cfg)`.
4. 🧠 **`scanner.NewVerbose`** — nuevo constructor que acepta un `io.Writer`. Cuando `log != io.Discard`, el scanner emite líneas de progreso a stderr durante el run.
5. 📦 **Instalación local** — `gocyclo` (Go), `radon` (pip), `jscpd`+`madge` (npm via nvm).
### ¿Para qué lo hago?
Sin verbose, el scanner producía "✅ No anti-patterns found" sin revelar que había saltado todos los adaptadores OSS por no estar instalados. El usuario no podía saber si el repo estaba limpio o si la herramienta no había analizado nada. Con `--verbose`, la visibilidad es completa:
```text
[native] large_function, god_object, magic_numbers → 34 .go file(s)
[native] done → 15 finding(s)
[oss] jscpd: running...
[oss] jscpd: 10 finding(s)
[oss] gocyclo: running...
[oss] gocyclo: 3 finding(s)
[skip] madge: not found in PATH
[skip] radon: not found in PATH
```
---
## 🏗️ Arquitectura del verbose
```mermaid
graph TD
CLI["🖥️ antipatterns scan . --verbose"] --> SCANV["scanner.NewVerbose(cfg, os.Stderr)"]
CLI2["🖥️ antipatterns scan ."] --> SCAN["scanner.New(cfg)\nlog = io.Discard"]
SCANV --> RUN["Scanner.Run(root)"]
SCAN --> RUN
RUN --> COUNT["countGoFiles(root)"]
COUNT --> LOG1["stderr: [native] ... → N .go file(s)"]
LOG1 --> WALK["filepath.WalkDir → native detectors"]
WALK --> LOG2["stderr: [native] done → N finding(s)"]
LOG2 --> ADPLOOP["for each adapter"]
ADPLOOP --> LOG3["stderr: [oss] name: running..."]
LOG3 --> ADPFN["adapter.fn(root, cfg)"]
ADPFN -- "ErrToolNotFound" --> LOG4["stderr: [skip] name: not found in PATH"]
ADPFN -- "nil, findings" --> LOG5["stderr: [oss] name: N finding(s)"]
ADPFN -- "other error" --> LOG6["stderr: [oss] name: error — msg"]
STDOUT["📤 stdout: findings (console/json/sarif)\nsiempre limpio — verbose solo va a stderr"]
```
---
## 🔊 Uso del flag `--verbose`
```bash
# Verbose a consola (stderr + stdout mezclados en terminal)
antipatterns scan ./mi-repo --verbose
# Verbose a consola, findings a fichero JSON
antipatterns scan ./mi-repo --verbose --format json --output findings.json
# → stderr: líneas [native]/[oss]/[skip] en terminal
# → findings.json: JSON puro sin contaminación
# Silenciar el verbose, capturar solo los findings
antipatterns scan ./mi-repo --verbose 2>/dev/null
antipatterns scan ./mi-repo --verbose --format json 2>/dev/null | jq '.[] | .rule'
# Shorthand -v
antipatterns scan ./mi-repo -v
```
> ⚠️ **Separación stdout/stderr**: el verbose siempre va a `stderr`. `stdout` contiene exclusivamente el output de findings en el formato solicitado. Esto garantiza compatibilidad con pipes (`| jq`, `| grep`) sin contaminación.
---
## 📦 Instalación de adaptadores OSS
### `gocyclo` — complejidad ciclomática Go
```bash
# Requiere Go >= 1.22 en PATH
go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
# El binario se instala en $GOPATH/bin (normalmente ~/go/bin)
# Asegurarse de que ~/go/bin está en PATH:
echo 'export PATH=$PATH:$HOME/go/bin' >> ~/.zshrc
```
### `radon` — complejidad ciclomática Python
```bash
# Ubuntu/Debian con Python administrado por el sistema (PEP 668):
pip3 install radon --user --break-system-packages
# El binario queda en ~/.local/bin — asegurarse de que está en PATH:
echo 'export PATH=$PATH:$HOME/.local/bin' >> ~/.zshrc
# Alternativa con pipx (sin --break-system-packages):
pip3 install pipx --user --break-system-packages
pipx install radon
```
### `jscpd` y `madge` — duplicación y deps circulares JS/TS
Requieren Node.js. Se recomienda instalar via **nvm** (sin sudo):
```bash
# 1. Instalar nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
# 2. Cargar nvm en la sesión actual (o abrir nuevo terminal)
export NVM_DIR="$HOME/.nvm" && \. "$NVM_DIR/nvm.sh"
# 3. Instalar Node.js LTS
nvm install --lts
# 4. Instalar los tools
npm install -g jscpd madge
# 5. Añadir nvm al shell permanente (~/.zshrc ya tiene el bloque si usaste el installer)
```
### Verificación de instalación completa
```bash
# Todos los tools deben aparecer en which
which gocyclo radon jscpd madge
# Test rápido con verbose sobre el propio repo
antipatterns scan . --verbose
# Ningún [skip] debe aparecer si todos están instalados
```
---
## ⚙️ Cambios técnicos
### `internal/adapter/errors.go` (nuevo)
```go
package adapter
import "errors"
// ErrToolNotFound is returned when the OSS tool binary is not present in PATH.
var ErrToolNotFound = errors.New("tool not in PATH")
```
### `internal/adapter/*.go` — cambio de contrato
| Situación | Antes (≤v1.0.0) | Después (v1.1.0+) |
| --- | --- | --- |
| Tool no instalado | `nil, nil` | `nil, ErrToolNotFound` |
| Tool instalado, 0 findings | `nil (slice vacío), nil` | `[]Finding{}, nil` |
| Tool instalado, N findings | `[]Finding{N}, nil` | `[]Finding{N}, nil` |
| Error de ejecución | `nil, nil` | `nil, error` |
> 🔒 **Cambio interno únicamente** — `dirAdapterFunc` es un tipo privado del paquete `scanner`. No hay impacto en la API pública del CLI ni en la GitHub Action.
### `internal/scanner/scanner.go` — constructor verbose
```go
// NewVerbose returns a Scanner that writes progress to w (typically os.Stderr).
func NewVerbose(cfg *config.Config, w io.Writer) *Scanner {
s := New(cfg)
s.log = w
return s
}
```
---
## 🧪 Cobertura de tests
| Test | Qué verifica |
| --- | --- |
| `TestGocycloSkipsWhenNotInstalled` | Devuelve `ErrToolNotFound` cuando el tool está ausente |
| `TestJscpdSkipsWhenNotInstalled` | Devuelve `nil` o `ErrToolNotFound` (acepta ambos para compatibilidad CI) |
| `TestMadgeSkipsWhenNotInstalled` | Ídem |
| `TestRadonSkipsWhenNotInstalled` | Ídem |
> Los tests de `jscpd`, `madge` y `radon` aceptan tanto `nil` (tool instalado en CI) como `ErrToolNotFound` (tool ausente en local) para no acoplar los tests a la disponibilidad de herramientas externas en cada entorno.
---
## 📊 Self-scan con todos los adaptadores (v1.1.0)
Con los adaptadores instalados, el self-scan del repo produce más findings que en v1.0.0:
| Detector/Adapter | Findings v1.0.0 | Findings v1.1.0 |
| --- | --- | --- |
| `native` (go/ast) | 16 | 15 (*) |
| `jscpd` | ❌ skip | 10 |
| `madge` | ❌ skip | 0 |
| `radon` | ❌ skip | 0 |
| `gocyclo` | ❌ skip | 3 |
| **Total** | **16** | **28** |
> (*) El conteo nativo pasó de 16 a 15 porque `scanner.go` fue refactorizado (los detectores ahora están en structs `namedDetector`/`namedAdapter`).
---
## 🔗 Documentos relacionados
- [Adaptadores OSS (Fase 2)](fase-2-adaptadores-oss.md) — arquitectura y contrato de los adaptadores
- [Integration Test (Fase 6)](fase-6-integration-test.md) — self-scan en CI contra @v1.0.0
- [Publicación (Fase 5)](fase-5-publicacion.md) — release pipeline y GoReleaser
- [Markdown Report (Fase 8)](fase-8-markdown-report.md) — informe accionable con checkboxes `- [ ]`
- [README principal](../README.md) — roadmap completo del proyecto