$ dharness

Una puerta de commit para proyectos TypeScript.

ESLint, react-doctor y fallow revisan cada commit antes de que entre, y dharness mutate te dice si tus pruebas notarían que el código se rompe.

~/projects/your-appRuta ocultada
$ git commit -m "feat: add discount helper" 🥊 lefthook v2.1.4  hook: pre-commitdharness: eslint exited with code 1 … the cost note on react-doctor and fallow running at @latest, elided … ── eslint ── src/discount.js  1:8  error  This function is declared at the top of the file with nothing saying what it is for  dharness/require-jsdoc ✖ 1 problem (1 error, 0 warnings) eslint failed, so react-doctor and fallow audit and fallow dupes did not run.… the tool’s own --help hint, elided … exit status 1
Salida real de dharness 1.9.2: git commit ejecuta el hook de pre-commit configurado, y ESLint lo bloquea en dharness/require-jsdoc antes de que corran react-doctor o fallow. Se acortó la ruta a su forma relativa y se omitieron dos bloques, ambos señalados.
La puerta

Lo barato primero, y la primera falla corta el resto

Una falla detiene la ejecución y omite las etapas siguientes, así que cada commit paga solo hasta su primer problema. Cada etapa lleva la mediana con la que se midió.

  1. 011008 ms
    ESLint--no-warn-ignored <staged files>

    ¿Este archivo sigue las reglas que recomienda este stack?

    Usa la versión que instaló tu proyecto y el preset que recomienda tu framework, solo sobre lo que preparaste.

    Corre en cuanto tu proyecto lo instala

  2. 022959 ms
    react-doctor--staged --no-dead-code --no-score --no-supply-chain -y

    ¿Este código React es semánticamente correcto?

    Revisa solo los archivos preparados, y su análisis no sale de tu máquina.

  3. 032102 ms
    fallow auditaudit --changed-since HEAD --diff-stdin

    ¿Qué agrega este cambio al grafo del repositorio?

    Juzga el cambio que estás por confirmar y solo bloquea por hallazgos nuevos.

    Corre en cuanto el repositorio tiene un commit contra el cual comparar

  4. 041398 ms

    ¿Cuánta duplicación carga el repositorio?

    El techo de duplicación vive en una configuración que tu proyecto puede leer y sobrescribir.

    Corre en cuanto el repositorio tiene un commit contra el cual comparar

Medianas de tres ejecuciones cada una, un proyecto de referencia y la misma lista explícita de archivos preparados para cada etapa. 12 de agosto de 2026.

Primera fallaexit 1
El veredicto

El código de salida es la respuesta, propagado sin cambios. Una etapa que falla nombra las que omitió, así siempre sabes qué no corrió.

Mutación

Averigua si tus pruebas notarían que el código se rompe

Con las pruebas en verde, Stryker rompe tu código un cambio a la vez. Cada ruptura que ninguna prueba nota vuelve con su línea.

  • $ dharness mutate --staged

    Exactamente las líneas que agregó tu cambio preparado

    Juzga el commit que estás por hacer, con el Stryker que ya instaló tu proyecto.

    Banderas (1)
    --exclude-prefix <p> (repeatable)

    Deja fuera del alcance los archivos preparados bajo ese prefijo.

  • $ dharness mutate <path...>

    Archivos con nombre, o líneas con nombre

    Una ruta puede nombrar líneas, como src/thing.ts:12-40, y el veredicto cubre exactamente esas. Los resultados se reutilizan, así la siguiente ejecución es más rápida.

    Banderas (4)
    --dry-run

    Mide cuántas pruebas ejecuta una corrida acotada, sin mutar nada.

    --concurrency <n> (default 2)

    Cada herramienta calcula su paralelismo sobre la máquina entera, y más workers se midieron más lentos en un alcance pequeño.

    --upgrade
    --fresh

    Mide solo las rutas que nombraste, sin los resultados guardados de ejecuciones anteriores.

Qué decide el código de salida
  • exit 0
    Todos los mutantes son atrapadosEstas pruebas notan que este código se rompe.
  • exit 1
    Un mutante sobreviveSe nombra con su línea y en qué se convirtió.
  • exit 1
    Un mutante que ninguna prueba ejecutóCuenta como no atrapado, nunca como atrapado.
  • exit 1
    Un archivo preparado que ninguna prueba importaSe detiene en related, antes de que arranque Stryker.
~/projects/your-app
$ dharness mutate --stagedoutside Stryker's mutate set: src/price.test.ts… Stryker’s own progress log elided … [Survived] EqualityOperatorsrc/price.ts:11:10-     return price <= 0;+     return price < 0;Tests ran:    isFree… Stryker’s score table elided … 1 file(s), 1 range(s), 8 in-scope mutant(s): 7 killed, 1 survived, 0 no coverage, 0 timeout, 0 errors, 0 ignored 1 mutant(s) survived — a test would not have noticed:   src/price.ts:11 EqualityOperator → price < 0    If equivalent, wrap the statement in `// Stryker disable EqualityOperator: <reason>` … `// Stryker restore EqualityOperator`; next-line does not reach call arguments such as dependency arrays.phases: snapshot 0.0981005s · classify 0.1508243s · discover 1.0291609s · related 0.5316295s · stryker 2.8742501000000003sdharness: 1 mutant(s) survived: a test would not have noticed this code breaking
Salida real de dharness 1.9.2, exit 1. El isFree preparado devuelve price <= 0 y su prueba revisa -1 y 5, así que nada nota que el límite se mueve. Se omitieron dos bloques del registro propio de Stryker, ambos señalados.
Configuración

Un comando configura el proyecto, y lo mantiene así

Cada ejecución deriva su plan del repositorio tal como está ahora, así que ejecutarlo meses después reporta la deriva. Una ejecución que falla deja el repositorio como lo encontró.

~/projects/your-appRuta ocultada
$ dharness sync dharness 1.9.2 · sync · ~/projects/your-app   js project       repository root  package manager  npm  test runner      vitest ■ 11 steps · 5 applied · 3 delegated · 3 satisfied · 0 failed   1.43s ── Applied (5) ── ✓ 1/11 install what this project is missing              1.40s ✓ 2/11 write the files dharness owns                     0.02s ✓ 3/11 point .fallowrc.json at the file dharness owns    0.00s ✓ 6/11 point eslint.config.js at the file dharness owns  0.00s ✓ 8/11 give the agent fallow's own tools                 0.00s… the files each step wrote, elided … ── Left to you (3) ── ! 9/11   wire the gate into git… its reason, and steps 10 and 11, elided … ✓ 5 applied · 3 delegated · 3 satisfied · 0 failed   1.43s  exit 0
Salida real de dharness 1.9.2 sobre un proyecto nuevo con npm y vitest: detectó ambos, aplicó cinco de sus once pasos y devolvió tres. Se reemplazó la ruta y se omitieron tres bloques, todo señalado.
Los once pasos, tal como los imprime
  1. 1/11
    install what this project is missing

    ✓ Lo aplica

  2. 2/11
    write the files dharness owns

    ✓ Lo aplica

  3. 3/11
    point .fallowrc.json at the file dharness owns

    ! Te lo devuelve cuando tu .fallowrc.json ya tiene configuración propia

  4. 4/11
    resolve the keys this project and dharness both declare

    ! Te lo devuelve cuando tú y dharness declaran la misma clave de fallow

  5. 5/11
    point lefthook.yml at the file dharness owns

    ! Te lo devuelve cuando tu lefthook.yml ya tiene tareas propias

  6. 6/11
    point eslint.config.js at the file dharness owns

    ! Te lo devuelve cuando tu configuración de ESLint es TypeScript, legacy o de una forma que no puede leer

  7. 7/11
    fix the lint config react-doctor silently drops

    ! Te lo devuelve cuando existe un .eslintrc.json legacy

  8. 8/11
    give the agent fallow's own tools

    ✓ Lo aplica

  9. 9/11
    wire the gate into git

    ! Te lo devuelve cuando el proyecto todavía no eligió lefthook o husky

  10. 10/11
    install react-doctor's agent skill

    ! Te lo devuelve cuando siempre, con el comando exacto a ejecutar

  11. 11/11
    decide this project's architecture

    ! Te lo devuelve cuando siempre, porque las zonas son un análisis que escribe tu agente

Lo que agrega cada preset (4)
  • Next.js
    • eslint-config-next/core-web-vitals
    • eslint-config-next/typescript
    • eslint-plugin-react-doctor · recommended, next

    La capa de TypeScript se suma en cuanto existe un tsconfig.json.

  • Expo
    • eslint-config-expo/flat.js
    • eslint-plugin-react-doctor · recommended, react-native
  • Wails
    • fallow ignorePatterns · wailsjs/**

    Se lee del wailsjsdir que declara tu wails.json.

  • Todo proyecto
    • fallow duplicates · mode semantic
    • minOccurrences 3
    • threshold 3
El paquete complementario

Tamaño, documentación y forma de carpetas, verificados

dharness-eslint-plugin

El tamaño, la documentación y la forma de las carpetas necesitan un número o una política contra la cual verificarse, y este paquete es donde un proyecto la declara.

Un solo registro, todos los archivos que ESLint toca

Un único registro `plugins: { dharness: plugin }` en la configuración que dharness posee es todo el cableado, y pone las seis reglas en cualquier lugar donde ESLint ya se ejecute en ese proyecto. El paquete publica una entrada CommonJS y otra ESM, así que carga en cualquiera de los dos sistemas de módulos.

Ver el código
Las seis reglas que publica
  1. dharness/max-file-lines

    suggestion

    Un archivo que pasa el techo que fijó este proyecto.

    Cada archivo se mantiene lo bastante corto para leerlo de una vez. Las líneas en blanco y los comentarios cuentan, así que el techo mide lo que el lector realmente recorre.

    Dónde mira

    Cada archivo que ESLint revisa

    Ejecutada sobre un fixture
    ✕ Reportadosrc/large.ts
    .dharness/rules.json
    1{ "maxFileLines": 4 }
    src/large.ts
    1const line0 = 0;2const line1 = 1;3const line2 = 2;4const line3 = 3;5const line4 = 4;6const line5 = 5;7const line6 = 6;8const line7 = 7;9const line8 = 8;
    1. 1:1This file has 9 lines, over the 4 this project allows. Split it.
    Dónde viven los números
    {  "schema": "dharness.rules/v1",  "maxFileLines": 500,  "roleSuffixes": [".types.ts", ".constants.ts", ".helpers.ts", ".schema.ts"]}

    Los números viven en `.dharness/rules.json`, así que un proyecto puede diferir de otro sin publicar una versión nueva. Un archivo ausente o ilegible cae a los valores por defecto, y todas las demás reglas siguen corriendo.

dharness/max-file-lines

suggestion

Un archivo que pasa el techo que fijó este proyecto.

Cada archivo se mantiene lo bastante corto para leerlo de una vez. Las líneas en blanco y los comentarios cuentan, así que el techo mide lo que el lector realmente recorre.

Dónde mira

Cada archivo que ESLint revisa

Ejecutada sobre un fixture
✕ Reportadosrc/large.ts
.dharness/rules.json
1{ "maxFileLines": 4 }
src/large.ts
1const line0 = 0;2const line1 = 1;3const line2 = 2;4const line3 = 3;5const line4 = 4;6const line5 = 5;7const line6 = 6;8const line7 = 7;9const line8 = 8;
  1. 1:1This file has 9 lines, over the 4 this project allows. Split it.
Dónde viven los números
{  "schema": "dharness.rules/v1",  "maxFileLines": 500,  "roleSuffixes": [".types.ts", ".constants.ts", ".helpers.ts", ".schema.ts"]}

Los números viven en `.dharness/rules.json`, así que un proyecto puede diferir de otro sin publicar una versión nueva. Un archivo ausente o ilegible cae a los valores por defecto, y todas las demás reglas siguen corriendo.

Primeros pasos

De la instalación a un commit verificado en cinco pasos

Ejecuta cada paso desde la raíz del repositorio que quieres proteger. Se compila con Go 1.26 o posterior; los archivos de cada versión para Linux, macOS y Windows, en amd64 y arm64, evitan la compilación.

Lo que necesitaAbrir el repositorio
  • Un proyecto JavaScript o TypeScript con su lockfile en git: bun, pnpm, yarn o npm
  • lefthook o husky, para llevar la puerta a git
  • Para la mutación: una configuración JSON de Stryker con vitest o jest
  • Los proyectos con Yarn Plug’n’Play usan nodeLinker: node-modules antes de mutar
  1. 01

    Instala el binario

    go install github.com/Disble/dharness/cmd/dharness@latest

    Un binario en tu PATH. Si prefieres no compilarlo, las versiones publicadas traen archivos para Linux, macOS y Windows.

  2. 02

    Configura el proyecto

    dharness sync

    Instala lo que falta, escribe .dharness/ y apunta tus configuraciones hacia ahí. Lo que no le corresponde decidir vuelve bajo Left to you, con el motivo.

  3. 03

    Cierra lo que te devuelve

    dharness sync

    Agrega lefthook o husky, pásale a tu agente el paso de arquitectura y vuelve a ejecutarlo. Cada ejecución lista solo lo que sigue abierto y nombra lo siguiente.

  4. 04

    Haz commit como siempre

    git commit

    El hook ejecuta dharness check sobre lo que preparaste. Una falla bloquea el commit y te señala la ayuda de esa misma herramienta.

  5. 05

    Pon a prueba lo que agregaste

    dharness mutate --staged

    Con las pruebas en verde, muta exactamente las líneas que preparaste y nombra cada cambio que tus pruebas dejarían pasar.