# 🔌 Fase 2 — Adaptadores OSS
## 🤔 ¿Qué hago? ¿Cómo lo hago? ¿Y para qué lo hago?
### ¿Qué hago?
Integro cuatro herramientas OSS de análisis de código como adaptadores de Klaus-antipatterns-search, normalizando sus hallazgos al modelo `Finding` universal del proyecto.
### ¿Cómo lo hago?
Cada adaptador sigue el mismo patrón:
1. 🔍 Verifica si la herramienta está en `PATH` (`exec.LookPath`)
2. ⚡ Si no está instalada → devuelve `nil, adapter.ErrToolNotFound` (sentinel distinguible de "0 findings")
3. 🚀 Si está instalada → ejecuta con `exec.CommandContext` + timeout
4. 📊 Parsea el output (JSON o texto) → `[]model.Finding`
El scanner usa `errors.Is(err, adapter.ErrToolNotFound)` para distinguir "herramienta ausente" de "error de ejecución" y emitir la línea `[skip]` correcta con `--verbose`.
Los adaptadores operan sobre el **directorio raíz completo** (a diferencia de los detectores nativos que van fichero a fichero).
### ¿Para qué lo hago?
Porque las herramientas OSS detectan anti-patrones que no son triviales de reimplementar en Go/AST: duplicación de código multi-lenguaje, dependencias circulares en JS/TS, complejidad ciclomática en Python. Integrarlas permite cobertura multi-stack sin reinventar la rueda.
---
## 🏗️ Arquitectura
```mermaid
graph TD
SCANNER["🎛️ Scanner.Run(root)"] --> NATIVE["🔍 File-level detectors\n(LargeFunction · GodObject · MagicNumbers)"]
SCANNER --> OSS["🔌 Dir-level adapters\n(Jscpd · Madge · Radon · Gocyclo)"]
OSS --> LOOKUP{"exec.LookPath"}
LOOKUP -- "❌ no instalado" --> SKIP["nil, ErrToolNotFound\n(sentinel distinguible)"]
LOOKUP -- "✅ instalado" --> EXEC["exec.CommandContext\n+ timeout"]
EXEC --> PARSE["parseFn(output, cfg)"]
PARSE --> MODEL["📦 []model.Finding"]
NATIVE --> MODEL
MODEL --> REPORT["📊 Renderers"]
```
---
## 📦 Adaptadores implementados
### 🔁 `jscpd` — Duplicación de código
| Campo | Detalle |
| --- | --- |
| **Herramienta** | [`jscpd`](https://github.com/kucherenko/jscpd) |
| **Anti-patrón** | Clones tipo-1/tipo-2 (duplicación exacta o normalizada) |
| **Lenguajes** | Multi-lenguaje (Go, Python, JS/TS, Java, ...) |
| **Regla emitida** | `duplication` |
| **Severidad** | Configurable via `severities.duplication` |
| **Umbral** | `thresholds.duplication_pct` (%) |
**Comando ejecutado:**
```bash
jscpd <root> --reporters json --output <tmpdir> --silent
```
**Findings emitidos:**
- 1 finding de resumen si el porcentaje supera `duplication_pct`
- 1 finding por cada par de clones detectados
---
### 🔄 `madge` — Dependencias circulares JS/TS
| Campo | Detalle |
| --- | --- |
| **Herramienta** | [`madge`](https://github.com/pahen/madge) |
| **Anti-patrón** | Dependencias circulares en grafos de imports |
| **Lenguajes** | JavaScript, TypeScript |
| **Regla emitida** | `circular_dependency` |
| **Severidad** | Configurable via `severities.circular_deps` |
**Comando ejecutado:**
```bash
madge --circular <root>
```
**Parsing:** texto `N) a -> b -> c` → un finding por ciclo.
---
### 🌀 `radon` — Complejidad ciclomática Python
| Campo | Detalle |
| --- | --- |
| **Herramienta** | [`radon`](https://radon.readthedocs.io) |
| **Anti-patrón** | Complejidad ciclomática excesiva |
| **Lenguajes** | Python |
| **Regla emitida** | `cyclomatic_complexity` |
| **Severidad** | Configurable via `severities.cyclomatic` |
| **Umbral** | `thresholds.cyclomatic` |
**Comando ejecutado:**
```bash
radon cc -j <root>
```
**Parsing:** JSON `{"file.py": [{"name": "fn", "complexity": N, "lineno": L}]}`
---
### ⚙️ `gocyclo` — Complejidad ciclomática Go
| Campo | Detalle |
| --- | --- |
| **Herramienta** | [`gocyclo`](https://github.com/fzipp/gocyclo) |
| **Anti-patrón** | Complejidad ciclomática excesiva en Go |
| **Lenguajes** | Go |
| **Regla emitida** | `cyclomatic_complexity` |
| **Severidad** | Configurable via `severities.cyclomatic` |
| **Umbral** | `thresholds.cyclomatic` |
**Comando ejecutado:**
```bash
gocyclo -over <threshold> <root>
```
**Parsing:** texto `<complexity> <pkg> <func> <file>:<line>:<col>`
> ⚠️ `gocyclo` complementa al detector nativo de `large_function`. El detector nativo mide LOC; gocyclo mide complejidad ciclomática (rutas de ejecución).
---
## ⚙️ Configuración
Todos los umbrales son configurables en `.antipatterns.yml`:
```yaml
thresholds:
cyclomatic: 15 # umbral para radon y gocyclo
duplication_pct: 5 # umbral de % para jscpd
severities:
cyclomatic: medium
duplication: medium
circular_deps: high
```
---
## 🔧 Instalación de herramientas (opcional)
Los adaptadores hacen **skip elegante** si la herramienta no está instalada. Para activarlos:
```bash
# jscpd (Node.js requerido)
npm install -g jscpd
# madge (Node.js requerido)
npm install -g madge
# radon (Python requerido)
pip install radon
# gocyclo (Go requerido)
go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
```
---
## 🧪 Cobertura de tests
| Adaptador | Tests | Estrategia |
| --- | --- | --- |
| `jscpd` | 5 | `parseJscpdOutput` con fixtures JSON + test de skip |
| `madge` | 4 | `parseMadgeOutput` con fixtures texto + test de skip |
| `radon` | 6 | `parseRadonOutput` con fixtures JSON + test de skip |
| `gocyclo` | 6 | `parseGocycloOutput` con fixtures texto + test de skip |
Los tests de parsing cubren: output vacío, por debajo del umbral, por encima del umbral, input malformado y skip cuando la herramienta no está instalada.
---
## 🔗 Documentos relacionados
- [Detectores nativos (Fase 1)](fase-1-detectores-nativos.md) — LargeFunction, GodObject, MagicNumbers
- [Verbose flag + OSS local (Fase 7)](fase-7-verbose-oss-local.md) — instalación de adaptadores y flag `--verbose`
- [README principal](../README.md) — roadmap completo del proyecto