impactwave
v1.3.0
Published
Blast radius analyzer CLI for TypeScript: find which exported symbols your Git changes modify, who consumes them, what tests cover them, and get a deterministic risk score before merging.
Maintainers
Readme
ImpactWave
🌐 English | Español
Analizador de blast radius para TypeScript.
ImpactWave es una CLI que analiza tus cambios en Git antes de hacer merge y responde una pregunta:
"¿Qué puedo romper con este cambio y qué debería probar?"
El problema
En un codebase real, un cambio "pequeño" puede romper cosas muy lejos del archivo editado. Los revisores lo detectan por intuición, los tests globales no te dicen qué probar primero, y el impacto real se descubre en producción.
ImpactWave convierte esa intuición en datos: qué símbolos exportados tocaste físicamente, quién los consume de verdad, qué áreas afectadas quedan sin tests y cuánto riesgo acumula el cambio — con un score determinístico y razones explicables.
Qué hace
Dado un rango de commits (rama base → HEAD), genera un reporte en consola con:
- Símbolos modificados — funciones, clases, métodos públicos, interfaces, tipos… detectados vía AST (ts-morph), no texto plano: solo cuenta lo que el diff tocó físicamente.
- Consumidores reales — cada uso activo de los símbolos modificados, con archivo, línea y snippet. Un
importpuro no ejecuta nada y no cuenta como impacto. - Blast radius — todos los archivos alcanzados a través del grafo de dependencias (incluye barrel files), agrupados por nivel de cascada.
- Cobertura de impacto — qué porcentaje de las áreas afectadas está cubierto por tests, y la lista exacta de las que no.
- Risk score 0–100 — determinístico (misma entrada → mismo score) y explicable: cada punto viene acompañado de su razón.
¿Qué es el blast radius?
El radio de explosión es el conjunto de código que puede verse afectado cuando cambias un archivo: quienes lo importan directamente, quienes importan a esos, y así en cascada. Conocerlo antes del merge significa saber exactamente dónde mirar y qué tests ejecutar — no descubrirlo por un bug report.
Instalación
npm install -g impactwave # o úsalo sin instalar:
npx impactwaveRequisitos: Node ≥ 22.12, un repositorio Git local y un proyecto TypeScript.
Uso
Ejecútalo en la raíz de tu proyecto:
cd mi-proyecto
impactwave
analyzees el comando por defecto:impactwaveyimpactwave analyzeson equivalentes. Solo se analizan cambios commiteados (base..HEAD); el working tree sin commitear no entra en el análisis.
$ impactwave --help
ImpactWave answers one question before you merge:
"What can I break with this change, and what should I test?"
It combines your Git diff with AST analysis to find the exported symbols you
modified, who really consumes them, whether tests cover the affected areas,
and computes a deterministic risk score (0-100) with explainable reasons.
Usage: impactwave [options] [command]
Analyze the blast radius of your code changes before merging
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
analyze [options] Analyze changed code impact in this repository
help [command] display help for command
Documentation: https://github.com/paleto30/impactwave#readme
Tip: running bare "impactwave" inside a Git repository is equivalent to
"impactwave analyze". Run "impactwave analyze --help" for options and examples.Opciones
| Opción | Descripción |
|---|---|
| -b, --base <branch> | Rama base a comparar (autodetección: origin/HEAD → main/master → HEAD~1) |
| --risk-weights <json> | Pesos personalizados de los factores de riesgo. Ver modelo de riesgo |
| --json | Reporte como JSON en stdout, con contrato versionado (meta.schemaVersion) y esquema publicado. Ideal para CI |
Ejemplos
# HEAD contra la rama base autodetectada
impactwave
# Comparar contra una rama base explícita
impactwave analyze -b main
# Dar más peso a los huecos de cobertura de tests
impactwave --risk-weights '{"callerImpact":30,"testGaps":35}'
# Puntuar solo por consumidores directos de los símbolos modificados
impactwave analyze --risk-weights '{"callerImpact":100}'
# Salida machine-readable para pipelines (stdout puro, warnings en stderr)
impactwave --json | jq '.risk'
# Gate de merge: falla si el nivel no es LOW ni MEDIUM
impactwave analyze --json -b main | jq -e '.risk.level | inside("LOW|MEDIUM")' > /dev/nullCómo funciona
- Git: detecta el repo, la rama base y los archivos modificados (A/M/D).
- AST: ts-morph extrae exports e imports de los archivos cambiados, usando un único proyecto con los
compilerOptionsde tutsconfig.jsonraíz y los archivos TypeScript (.ts,.tsx,.mts,.cts) descubiertos por un recorrido propio del árbol (tolerante a directorios ilegibles). - Símbolos modificados: intersecta los rangos de líneas de cada símbolo exportado con las líneas del diff.
- Consumidores reales:
findReferencesencuentra los usos activos de cada símbolo (los imports puros no cuentan como impacto). - Grafo de dependencias: índice inverso y directo de imports relativos —incluidas las cargas dinámicas
import(...)/require(...)con argumento estático— + recorrido transitivo (BFS) con profundidad. - Test mapping: detecta archivos
*.test.ts/*.spec.tsy mapea qué código cubren. - Risk engine: score determinístico 0-100 con razones explicables.
Modelo de riesgo
Cinco factores con umbrales de saturación, más uno opcional. Pesos por defecto (configurables con --risk-weights):
| Factor | Peso | Señal |
|---|---|---|
| callerImpact | 30 | consumidores directos de símbolos modificados (umbral 10) |
| affectedFiles | 20 | archivos alcanzados transitivamente (umbral 15) |
| dependencyDepth | 15 | niveles de profundidad máxima (umbral 4) |
| testGaps | 20 | proporción de áreas afectadas sin tests |
| changeSize | 15 | líneas modificadas (umbral 200) |
| testCallerImpact | — | opcional: si se define, los consumidores que son tests salen de callerImpact y saturan este peso (0 los exime del score) |
Niveles: 0-25 LOW · 26-50 MEDIUM · 51-75 HIGH · 76-100 CRITICAL.
Los nombres de los factores son las claves JSON de --risk-weights (todas opcionales; las omitidas valen 0). Los puntos de cada razón del reporte suman exactamente el score, salvo cuando satura en 100.
Dos de los factores (affectedFiles y dependencyDepth, 35 puntos) se calculan sobre el grafo estático de dependencias, no sobre los consumidores reales: miden cuánta superficie del proyecto queda expuesta al archivo cambiado.
El reporte
Cada análisis imprime contexto Git, evaluación de riesgo con score y razones, cobertura de impacto, y por cada archivo cambiado: símbolos exportados (marcando los modificados), usos downstream con línea y snippet, blast radius en cascada y tests relacionados:

╭─ Risk Assessment ────────────────────────────────────────╮
│ 🟡 MEDIUM RISK (score: 31/100) │
│ Changes affect a few dependent modules. Verify them... │
│ 4 unique dependent files at risk │
│ Reasons: │
│ • 4 consumers of modified symbols (12 pts) │
│ • 4 affected files (transitive reach) (5 pts) │
│ • Impact reaches depth 1 dependency level (4 pts) │
│ • 1 affected area without detected tests (10 pts) │
│ • 1 line modified │
╰──────────────────────────────────────────────────────────╯📖 Cómo leer el reporte completo, sección por sección → docs/GUIA.md
Ejemplo visual
Ejecución real de ImpactWave antes de hacer merge a main: análisis del refactor de un módulo de usuarios en un proyecto NestJS.

Nota: esta captura se tomó durante el desarrollo de la herramienta con una utilidad que guarda la salida completa de la terminal como imagen, por lo que pierde parte del diseño y formato del reporte. No representa exactamente el reporte final — es solo un ejemplo visual del análisis de impacto antes del merge; el reporte real se renderiza directamente en tu terminal.
Salida JSON para CI
impactwave analyze --json emite el mismo reporte como un único documento
JSON en stdout (las advertencias van a stderr). El formato está versionado
(meta.schemaVersion) y publicado como JSON Schema:
dentro de una versión solo hay cambios aditivos. Los usos de símbolos viajan
sin filtrar, marcados con importOnly: true cuando son solo cableado de
contrato (import, re-exports).
Alcance
ImpactWave es un prototipo con un alcance deliberadamente acotado: hace bien una cosa y declara el resto fuera de alcance en vez de aproximarlo.
Dentro del alcance:
- Proyectos TypeScript:
.ts,.tsx,.mts,.cts. - Cambios ya commiteados, comparando
base..HEADen un repositorio Git local. - Imports relativos dentro del proyecto, estáticos y dinámicos con argumento estático.
- Análisis local, sin red: tu código nunca sale de tu máquina.
Fuera del alcance (por decisión, no por descuido):
- JavaScript (
.js,.jsx,.mjs,.cjs). No se descubren ni se parsean. Si un cambio los toca, se omiten y el reporte lo dice con la advertenciaunsupported-source-files: un reporte limitado es útil, uno que parece completo sin serlo es peligroso. - Otros lenguajes.
- Cambios sin commitear en el working tree.
node_modules, path aliases no relativos y monorepos con varios tsconfigs (solo se usa el de la raíz).- Análisis semántico del cambio: la herramienta dice qué símbolo cambió y quién lo usa, no si el cambio rompe el contrato.
Limitaciones conocidas
- Granularidad del "símbolo modificado": es la intersección del diff con el rango de líneas de la declaración. Un cambio no funcional (un comentario, un reformateo) dentro de ese rango marca el símbolo como modificado.
- Archivos eliminados: no se analizan sus símbolos ni sus consumidores. Si borras un archivo que otros siguen importando, el reporte no lo señala.
- Renombrados: se reportan como una modificación de la ruta nueva (con
previousPath), comparada contra el contenido anterior. - La cobertura de tests es transitiva: un test cubre los archivos que alcanza a través del grafo de dependencias hasta 4 saltos (
DEFAULT_TEST_COVERAGE_DEPTH, configurable por llamada), evitando que un test que importa la raíz del proyecto reclame cobertura sobre todo el codebase. - Los imports dinámicos con argumento no estático (template literals con variables, concatenaciones) no son resolubles: se registran y se reportan con la advertencia
unresolved-dynamic-imports. - El descubrimiento de fuentes recorre todo el árbol omitiendo
node_modules/dist/build, directorios ocultos y symlinks. Los directorios sin permiso de lectura (ej. elpg_datade Docker) se omiten; no abortan el análisis.
Desarrollo
npm test # suite de tests (node:test)
npm run build # compilación a dist/
npm run dev # ejecutar en desarrolloLos fixtures en test/fixtures/ validan el análisis contra proyectos artificiales: simple-project (cadena A→B→C), circular-dependencies (X↔Y), barrel-exports (re-exports por barrel), test-coverage y test-coverage-transitive (servicios con y sin tests, cadenas largas y ciclos), dynamic-imports (cargas dinámicas resolubles y no resolubles) e import-shapes (imports y re-exports multilínea).
Para contribuir: abre un issue o envía un PR. Las prioridades y mejoras candidatas están documentadas en docs/ROADMAP.md; el historial de versiones, en CHANGELOG.md.
