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.tomldel proyecto consumidor. Los hooks invocanmise 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 hookcheckov-secretsuniversal debase.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
| Archivo | Hooks | Stack |
|---|---|---|
lefthook/base.yaml | pre-commit: toml, sort-package-json, hadolint, shell (shfmt+shellcheck), lefthook-validate, renovate-validate, checkov-secrets | Todos |
lefthook/base.yaml | commit-msg (wietoo check-commit-msg) | Todos |
lefthook/base.yaml | post-commit: graphify incremental update (no-op si no hay grafo) | Todos |
lefthook/base.yaml | post-merge, post-checkout, post-rewrite (refrescan las herramientas pinneadas de mise) | Todos |
lefthook/js.yaml | pre-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.yamllocal 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 dewiedii-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 ogit commit -m, se hace el commit directamente congit commity el hookcommit-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>, nuncaeval "$(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: trueen hooks que modifican archivos (formatters) para auto-stagear los cambios. - Los hooks de
jobs:(mise trust,mise install) corren antes de loscommands:— 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