Saltar al contenido principal

Wiedii — Política de Git Hooks con Lefthook

Todo repositorio debe tener lefthook.yaml con hooks activos. Los hooks se instalan automáticamente via [hooks].postinstall en mise.toml al ejecutar mise install --yes, con detección de CI para no ejecutarse en pipelines.

Configuraciones compartidas — wiedii-configs

La mayoría de los hooks están centralizados en wildcat/wiedii-configs y se consumen vía remotes. El lefthook.yaml local solo define los linters y herramientas específicas del proyecto.

Requisito: todas las herramientas que usan los hooks deben estar declaradas en el mise.toml del proyecto consumidor. Los hooks invocan mise exec -- <herramienta> — si la herramienta no está en el entorno mise del proyecto, el hook fallará.

Herramientas mínimas requeridas en mise.toml: bun, lefthook, taplo, checkov (este último por el hook checkov-secrets universal de base.yaml — escaneo de secretos en todo repo). Opcionales según contenido del repo: hadolint (Dockerfiles), shfmt + shellcheck (scripts .sh).

Ver wiedii-configs para la tabla completa con versiones.

# lefthook.yaml — todos los repos Wiedii
remotes:
- git_url: git@gitlab.wiedii.co:wildcat/wiedii-configs.git
ref: main
configs:
- lefthook/base.yaml # suficiente para la mayoría de proyectos
ArchivoHooksStack
lefthook/base.yamlpre-commit: toml, sort-package-json, hadolint, shell (shfmt+shellcheck), lefthook-validate, renovate-validate, checkov-secretsTodos
lefthook/base.yamlcommit-msg (wietoo check-commit-msg)Todos
lefthook/base.yamlpost-commit: graphify incremental update (no-op si no hay grafo)Todos
lefthook/base.yamlpost-merge, post-checkout, post-rewrite (refrescan las herramientas pinneadas de mise)Todos
lefthook/js.yamlpre-commit (JS/TS) + post-merge, post-checkout, post-rewrite (bun install al cambiar el lockfile)JS / TS
lefthook/python.yaml(próximamente)Python
lefthook/go.yaml(próximamente)Go

No duplicar en el lefthook.yaml local los hooks que ya proveen los remotes. Duplicarlos genera doble ejecución y divergencia. Si un hook local replica uno compartido, se quita el local y se consume el de wiedii-configs.

Excepción — sobreescritura justificada. Si el repo necesita sobreescribir deliberadamente un hook compartido por una razón de peso (un caso del proyecto que el hook compartido no cubre, o que resuelve mal), se conserva la sobreescritura local. Debe ser consciente y documentada (un comentario que explique por qué se aparta del compartido), no una copia accidental: lo prohibido es re-declarar un hook compartido sin motivo. Ante la duda, consumir el compartido.


lefthook.yaml estándar por tipo de proyecto

JS / TS con Bun (consumiendo wiedii-configs)

remotes:
- git_url: git@gitlab.wiedii.co:wildcat/wiedii-configs.git
ref: main
configs:
- lefthook/base.yaml

pre-commit:
parallel: true
jobs:
- run: mise trust
- run: mise install --yes
commands:
prettier:
priority: 1
glob: '*.{js,cjs,mjs,ts,json,md,yaml}'
run: mise exec -- bun prettier --write --cache {staged_files}
stage_fixed: true
eslint:
priority: 2
glob: '*.{js,cjs,mjs,ts,json}'
run: mise exec -- bun eslint --config eslint.config.mjs --cache --fix {staged_files}
stage_fixed: true

Los hooks commit-msg, sort-package-json, post-merge/post-checkout/post-rewrite y todos los validadores universales vienen de base.yaml — no se incluyen localmente.

Sin remotes (repo standalone sin wiedii-configs)

Solo usar este patrón si el repo no puede consumir wiedii-configs. Incluir manualmente todos los hooks:

pre-commit:
parallel: true
jobs:
- run: mise trust
- run: mise install --yes
commands:
prettier:
priority: 1
glob: '*.{js,cjs,mjs,ts,json,md,yaml}'
run: mise exec -- bun prettier --write --cache {staged_files}
stage_fixed: true
eslint:
priority: 2
glob: '*.{js,cjs,mjs,ts,json}'
run: mise exec -- bun eslint --config eslint.config.mjs --cache --fix {staged_files}
stage_fixed: true
sort-package-json:
priority: 3
glob: '**/package.json'
run: |
PKG_DIR=$(find . -maxdepth 2 -name 'package.json' ! -path '*/node_modules/*' | sort | head -1 | xargs dirname 2>/dev/null || echo ".")
if [ -f "$PKG_DIR/node_modules/.bin/sort-package-json" ]; then
mise exec -- bun "$PKG_DIR/node_modules/.bin/sort-package-json" {staged_files}
else
echo "sort-package-json: skipping — add sort-package-json to devDependencies"
fi
stage_fixed: true
toml:
priority: 4
glob: '*.toml'
run: mise exec -- taplo fmt {staged_files}
stage_fixed: true
hadolint:
priority: 5
glob: 'Dockerfile'
run: mise exec -- hadolint {staged_files}
shfmt:
priority: 6
glob: '*.sh'
run: mise exec -- shfmt --write {staged_files}
stage_fixed: true
shellcheck:
priority: 7
glob: '*.sh'
run: mise exec -- shellcheck {staged_files}
renovate-validate:
priority: 8
glob: '{renovate.json,.renovaterc,.renovaterc.json}'
run: |
if (git diff --name-only --cached --diff-filter=ACMRD || true) | grep -E "(renovate\.json|\.renovaterc)" >/dev/null \
|| (git ls-files -m || true) | grep -E "(renovate\.json|\.renovaterc)" >/dev/null; then
mise exec -- bunx --yes --package renovate@latest -- renovate-config-validator --strict
else
echo "renovate-validate: no config changes, skipping"
fi

commit-msg:
parallel: false
jobs:
- run: mise trust
- run: mise install --yes
commands:
wietoo:
priority: 1
run: mise exec -- wietoo check-commit-msg "$1"

post-merge:
parallel: false
jobs:
- run: mise trust
- run: mise install --yes
commands:
bun-install:
glob: '**/{package.json,bun.lock,bunfig.toml}'
run: |
PKG_DIR=$(find . -maxdepth 2 -name 'package.json' ! -path '*/node_modules/*' | sort | head -1 | xargs dirname 2>/dev/null || echo ".")
if [ ! -d "$PKG_DIR/node_modules" ] || git diff --name-only ORIG_HEAD HEAD | grep -qE "(package\.json|bun\.lock|bunfig\.toml)$"; then
echo "bun-install: running bun install --frozen-lockfile"
mise exec -- bun install --cwd "$PKG_DIR" --frozen-lockfile
else
echo "bun-install: no dependency file changes detected, skipping"
fi

post-checkout:
parallel: false
jobs:
- run: mise trust
- run: mise install --yes
commands:
bun-install:
glob: '**/{package.json,bun.lock,bunfig.toml}'
run: |
PKG_DIR=$(find . -maxdepth 2 -name 'package.json' ! -path '*/node_modules/*' | sort | head -1 | xargs dirname 2>/dev/null || echo ".")
if [ ! -d "$PKG_DIR/node_modules" ] || git diff --name-only ORIG_HEAD HEAD | grep -qE "(package\.json|bun\.lock|bunfig\.toml)$"; then
echo "bun-install: running bun install --frozen-lockfile"
mise exec -- bun install --cwd "$PKG_DIR" --frozen-lockfile
else
echo "bun-install: no dependency file changes detected, skipping"
fi

Adiciones por tipo de proyecto

Astro / frontend — extender el glob de prettier:

prettier:
glob: '*.{js,cjs,mjs,ts,json,md,yaml,astro}'

Python — agregar a los comandos de pre-commit:

lint-py:
priority: 10
glob: "*.py"
run: mise exec -- ruff check --fix {staged_files}
stage_fixed: true
format-py:
priority: 11
glob: "*.py"
run: mise exec -- ruff format {staged_files}
stage_fixed: true
types-py:
priority: 12
glob: "*.py"
run: mise exec -- mypy {staged_files}

Go — agregar a los comandos de pre-commit:

lint-go:
priority: 10
glob: "*.go"
run: mise exec -- golangci-lint run {staged_files}
format-go:
priority: 11
glob: "*.go"
run: mise exec -- gofmt -w {staged_files}
stage_fixed: true

Caveat: proyectos con múltiples package.json

Los hooks de base.yaml (y el ejemplo standalone) detectan package.json con:

find . -maxdepth 2 -name 'package.json' ! -path '*/node_modules/*' | sort | head -1 | xargs dirname

Esto toma el package.json más superficial (raíz antes que src/). Si el proyecto tiene dos package.json —por ejemplo uno en raíz para herramientas de CI y otro en src/ con el código real— los hooks siempre usarán el de raíz.

Consecuencia: las devDependencies de hooks JS (p. ej. sort-package-json) deben instalarse en el package.json raíz, no en src/package.json. Los commits los valida wietoo (binario de mise) — no depende de package.json.

Si el proyecto tiene package.json únicamente en src/ (sin raíz), la detección lo encontrará correctamente.

wietoo commit: el wizard interactivo requiere una TTY real. Desde Claude Code, CI o git commit -m, se hace el commit directamente con git commit y el hook commit-msg (wietoo check-commit-msg) lo valida igual. Ver wietoo-cli.


Verificar que los hooks funcionan

lefthook install # (re)instalar hooks en .git/hooks/
lefthook run pre-commit # probar sin hacer commit

Reglas de escritura de hooks

  • Siempre usar mise exec -- <programa>, nunca eval "$(mise env)" ni llamadas directas al binario.
  • Los hooks deben ser idempotentes: comprobar si hay cambios relevantes antes de ejecutar comandos costosos.
  • Usar glob: para limitar la ejecución a los tipos de archivos afectados.
  • Usar stage_fixed: true en hooks que modifican archivos (formatters) para auto-stagear los cambios.
  • Los hooks de jobs: (mise trust, mise install) corren antes de los commands: — usarlos para preparar el entorno.

Referencias

  • wiedii-configs — documentación del repo de hooks compartidos: estructura, requisitos, cómo contribuir
  • Lefthook — guía de instalación, integración con mise y troubleshooting
  • commits — política de mensajes de commit que valida el hook commit-msg