Communitygithub.com

lguidolin/commit-history-rewrite

Se utiliza cuando un repositorio existente tiene un historial de commits desordenado que necesita ajustarse a commits convencionales antes de adoptar release-please, o cuando se deben limpiar commits intermedios de WIP/fixup/fusión.

¿Qué es commit-history-rewrite?

Esta habilidad permite reescribir el historial de commits de un repositorio existente para que cumpla con el estándar de commits convencionales (Conventional Commits). Es especialmente útil antes de adoptar herramientas de automatización de versiones como release-please, o cuando el historial contiene mensajes como "fix", "update", "wip" o fusiones que dificultan la trazabilidad y la generación automática de changelogs. La herramienta analiza cada commit usando dos señales: el contenido del mensaje y los archivos modificados. Cuando el mensaje es ambiguo, el diff se convierte en la fuente de verdad para clasificar el commit como `feat`, `fix`, `refactor`, `chore`, `test`, `docs` o `ci`. También identifica commits candidatos a squash, como aquellos con mensajes "WIP", "fixup" o fusiones, y los combina con el commit significativo más cercano. El proceso incluye siete pasos: evaluación del repositorio, clasificación, redacción de mensajes de calidad, presentación del plan de reescritura, creación de una rama de respaldo, ejecución de la reescritura (usando rebase interactivo para menos de 50 commits o git-filter-repo para más) y verificación de que el árbol de archivos permanezca intacto. La habilidad está diseñada para agentes como Claude Code, Cursor y Codex, y prioriza la seguridad mediante aprobación explícita del usuario, ramas de respaldo y el uso de `--force-with-lease` en lugar de `--force`.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/lguidolin/agent-skills/tree/main/skills/commit-history-rewrite

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Reescritura del Historial de Commits

Reescribe el historial de commits de un repositorio existente para que cumpla con commits convencionales. Analiza commits, los clasifica por tipo, identifica candidatos a squash y ejecuta la reescritura con barreras de seguridad completas.

Descripción General

El historial es documentación. Cada mensaje de commit en la rama main es un registro permanente que futuros ingenieros (y agentes) utilizan para entender qué cambió y por qué. Mensajes como "arreglo", "actualización", "wip" destruyen esta documentación. Release-please analiza los mensajes de commit para determinar incrementos de versión; los mensajes no conformes producen versiones incorrectas o ninguna.

El código es una responsabilidad. La ley de Hyrum también se aplica al historial de git: con suficientes herramientas que dependen de mensajes de commit (changelogs, automatización de versiones, git bisect, git blame), el formato de esos mensajes se convierte en un contrato. Limpiar el historial antes de adoptar automatización evita que los mensajes antiguos contaminen todas las herramientas posteriores.

Esta habilidad reescribe los mensajes, no el código. El árbol de archivos debe ser idéntico antes y después; solo cambian los metadatos del commit.

Cuándo Usar

  • Adoptar commits convencionales en un repositorio con historial existente no conforme
  • El registro de commits está contaminado con mensajes WIP, fixup, fusión o sin sentido
  • Preparar un repositorio para release-please (que necesita commits convencionales para funcionar)
  • Limpiar antes de hacer público o transferir un repositorio

Cuándo NO Usar

  • El repositorio ya se ha publicado con commits convencionales (no hay nada que reescribir)
  • Ramas compartidas con colaboradores activos que no han sido notificados
  • Solo necesitas corregir los últimos commits (usa git rebase -i HEAD~N directamente)
  • El repositorio es un fork donde el upstream controla el formato de commits

Proceso

Paso 1: Evaluar el Repositorio

# Contar commits totales
git rev-list --count HEAD

# Mostrar resumen de mensajes de commit
git log --oneline --no-merges

# Verificar colaboradores (diversidad de autoría)
git shortlog -sne --no-merges

# Identificar densidad de commits de fusión
git log --merges --oneline | wc -l

Punto de decisión: Si el número de commits < 50, usa rebase interactivo. Si >= 50, usa git-filter-repo.

Paso 2: Clasificar Commits Existentes

Analiza cada commit usando dos señales: el texto del mensaje y los archivos modificados. Cuando el mensaje es ambiguo (por ejemplo, "actualizar cosas"), el diff es la fuente de verdad.

Señal 1: Contenido del Mensaje

Señal en el mensajeTipo Asignado
"add", "new", "implement", "create", "introduce", "support"feat
"fix", "bug", "patch", "resolve", "correct", "handle error"fix
"refactor", "restructure", "reorganize", "simplify", "extract", "move"refactor
"update dep", "bump", "upgrade", "version", "config", "setup"chore
"test", "spec", "coverage", "assert"test
"doc", "readme", "comment", "typo in doc"docs
"ci", "workflow", "pipeline", "action", "deploy"ci

Señal 2: Archivos Modificados (cuando el mensaje es ambiguo)

git show --stat --format="" <hash>
Patrón de archivoTipo Asignado
Solo *.md, docs/**docs
Solo *test*, *spec*, __tests__/**test
Solo .github/workflows/**, .gitlab-ci.yml, Jenkinsfileci
Solo package.json, *.lock, requirements.txt, Cargo.tomlchore
Solo archivos de configuración (.eslintrc, tsconfig.json, .prettierrc)chore

Candidatos a Squash

SeñalAcción
El mensaje es "WIP", "wip", "temp", "save", "checkpoint", una sola palabra o vacíohacer squash en el commit significativo más cercano
El mensaje es "fixup", "fix typo", "oops", "lint", "formato"hacer squash en el anterior
Commits de fusión (de fusiones no squash)candidato a squash — evaluar si el contenido ya está en main

Para commits ambiguos, revisa el diff completo (git show <hash>) para determinar el tipo a partir del contenido.

Paso 3: Escribir Mensajes de Calidad

Los mensajes reescritos deben seguir los estándares de mensajes de commit:

<tipo>[ámbito opcional]: <descripción imperativa corta>

[cuerpo opcional explicando POR QUÉ, no qué]

Buenas reescrituras:

"configuración inicial"           →  "chore: configuración inicial del proyecto"
"agregado inicio de sesión"       →  "feat: agregar inicio de sesión de usuario"
"corregir error"                 →  "fix: resolver bucle de redirección de inicio de sesión"
"Actualizar README.md"            →  "docs: actualizar README con instrucciones de configuración"
"dependencias actualizadas"       →  "chore(deps): actualizar dependencias"

Malas reescrituras (no hagas esto):

"cosas"                            →  "chore: cosas"              ← sigue sin significado
"cambios"                          →  "feat: cambios"            ← sin descripción
"arreglo"                          →  "fix: arreglo"             ← redundante

Cuando el mensaje original no tiene significado, lee el diff para escribir una descripción adecuada. Si el diff es demasiado grande o ambiguo, pregunta al usuario de qué trataba el commit.

Paso 4: Presentar el Plan de Reescritura

Antes de cualquier acción destructiva, muestra al usuario un plan completo:

PLAN DE REESCRITURA
====================
Commits totales: 47
Commits a reescribir: 32
Commits a squashear: 8
Commits ya conformes: 7
Autores preservados: 3 (Alice <alice@co>, Bob <bob@co>, CI Bot <bot@co>)

CAMBIOS PROPUESTOS:
  abc1234  "configuración inicial"           →  "chore: configuración inicial del proyecto"
  def5678  "agregado inicio de sesión"       →  "feat: agregar inicio de sesión de usuario"
  ghi9012  "wip"                             →  [SQUASH en def5678]
  jkl3456  "corregir error"                 →  "fix: resolver bucle de redirección de inicio de sesión"
  mno7890  "Actualizar README.md"            →  "docs: actualizar README con instrucciones de configuración"
  pqr1234  "feat: agregar búsqueda"          →  [MANTENER — ya conforme]
  ...

SEGURIDAD:
  ✓ Rama de respaldo: backup/pre-rewrite-<marca de tiempo>
  ⚠ Se requiere push forzado después de la reescritura
  ⚠ Todos los colaboradores deben reclonar o reiniciar después del push

SIN CAMBIOS:
  - Árbol de archivos (cero cambios de contenido)
  - Nombre, correo electrónico y fecha del autor en cada commit
  - Orden de commits y relaciones padre (excepto commits squasheados)

Espera la aprobación explícita del usuario antes de continuar. No continúes con "suena bien" — se requiere un "sí" o equivalente.

Paso 5: Crear Respaldo

# Crear una rama de respaldo en el HEAD actual
git branch "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

# Verificar que el respaldo existe
git branch -l 'backup/*'

# Opcionalmente, enviar el respaldo al remoto para mayor seguridad
git push origin "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

Paso 6: Ejecutar la Reescritura

Opción A: Rebase Interactivo (< 50 commits)

# Rebase desde la raíz
git rebase -i --root

En el editor:

  • Marca los candidatos a squash con squash o fixup
  • Marca las reescrituras de mensajes con reword
  • Preserva el orden de los commits

Preservación de autoría: El rebase interactivo preserva el nombre, correo electrónico y fecha del autor de forma predeterminada. NO uses --reset-author. NO uses --committer-date-is-author-date a menos que se solicite explícitamente.

Opción B: git-filter-repo (>= 50 commits)

Crea un script Python de callback (rewrite-messages.py) que asigna mensajes antiguos a nuevos:

import re

REESCRITURAS = {
    b'prefijo hash antiguo': b'mensaje nuevo',
    # ... generado a partir del plan
}

def reescribir(commit, metadata):
    # Buscar por mensaje original o hash
    if commit.original_id in REESCRITURAS:
        commit.message = REESCRITURAS[commit.original_id]

Aplica con:

git filter-repo --commit-callback "$(cat rewrite-messages.py)" --force

Preservación de autoría: git-filter-repo preserva la autoría de forma predeterminada. NO uses --mailmap ni banderas de reescritura de autor a menos que el usuario lo solicite explícitamente.

Squash de Commits

Para candidatos a squash, combínalos en el commit significativo más cercano. El commit resultante:

  • Usa el mensaje de commit convencional del commit significativo
  • Preserva el autor original del commit significativo (no el objetivo del squash)
  • Combina todos los cambios del rango squasheado

Paso 7: Verificar

Ejecuta todas las comprobaciones de verificación. Cada comprobación debe pasar antes de proceder al push.

# 1. Revisar el registro reescrito
git log --oneline --no-merges

# 2. Validar que TODOS los mensajes cumplan con commits convencionales
FALLOS=$(git log --format="%s" --no-merges | grep -cvE "^(feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+")
if [[ "$FALLOS" -gt 0 ]]; then
  echo "FALLO: $FALLOS commits no conformes encontrados:"
  git log --format="%h %s" --no-merges | grep -vE "^[a-f0-9]+ (feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+"
  exit 1
fi
echo "PASA: todos los commits son conformes"

# 3. Verificar CERO cambios de contenido (crítico — esto demuestra que solo cambiaron los mensajes)
DIFF=$(git diff "backup/pre-rewrite-"*..HEAD --stat)
if [[ -n "$DIFF" ]]; then
  echo "FALLO: se detectaron diferencias de contenido — la reescritura cambió código, no solo mensajes"
  echo "$DIFF"
  exit 1
fi
echo "PASA: árbol de archivos idéntico"

# 4. Verificar preservación de autoría
echo "=== Autores antes ==="
git -C . log backup/pre-rewrite-* --format="%an <%ae>" --no-merges | sort -u
echo "=== Autores después ==="
git log --format="%an <%ae>" --no-merges | sort -u
# Estas dos listas deben ser idénticas

Si alguna comprobación falla, NO hagas push. Restablece al respaldo e investiga:

git reset --hard backup/pre-rewrite-*

Paso 8: Push (con Confirmación)

Esto es destructivo para los colaboradores remotos. Confirma con el usuario:

⚠ ADVERTENCIA DE PUSH FORZADO
  Rama: main
  Remoto: origin
  Acción: git push --force-with-lease origin main

  Después de este push, todos los colaboradores deben ejecutar:
    git fetch origin
    git reset --hard origin/main

  Escribe "sí" para continuar:
git push --force-with-lease origin main

Usa --force-with-lease (no --force) para evitar sobrescribir cambios remotos concurrentes. Si --force-with-lease es rechazado, alguien ha hecho push a la rama desde la reescritura; investiga antes de reintentar.

Limpieza Posterior a la Reescritura

Después del push exitoso y la notificación a los colaboradores:

# Verificar que el remoto coincida con el local
git log --oneline -5
git log --oneline -5 origin/main
# Estos deben ser idénticos

# Mantén la rama de respaldo durante al menos 2 semanas, luego elimínala
# git branch -D backup/pre-rewrite-*
# git push origin --delete backup/pre-rewrite-*

Racionalizaciones

RacionalizaciónRealidad
"El historial antiguo no importa"Release-please analiza los mensajes de commit para determinar versiones. Un historial no conforme produce versiones incorrectas o ninguna. git bisect, git blame y git log --grep dependen de mensajes significativos.
"Empezaré de nuevo desde este commit"Pierdes autoría, atribución y contexto. Una rama --orphan destruye la narrativa del desarrollo. Reescribir preserva todo excepto los malos mensajes.
"Es demasiado arriesgado reescribir"La rama de respaldo hace que esto sea completamente reversible. Verifica que el diff muestre cero cambios de contenido. Si algo sale mal: git reset --hard backup/pre-rewrite-*.
"Reescribiré más tarde cuando sea importante"Las reescrituras de historial se vuelven más difíciles con el tiempo a medida que más personas clonan y crean ramas a partir de él. Hazlo ahora, antes de que existan más dependencias posteriores.
"El push forzado es peligroso"--force-with-lease protege contra sobrescribir el trabajo de otros. Combinado con la rama de respaldo, esto es seguro.
"Los mensajes son suficientemente cercanos""Suficientemente cercano" significa que release-please no los analizará. Los commits convencionales son un formato estricto: o un mensaje es conforme o no lo es.
"Puedo etiquetar versiones manualmente"Las etiquetas manuales se desvían, se olvidan y carecen de integración con changelog. El objetivo es la automatización, pero la automatización necesita entrada analizable.

Banderas Rojas

  • Omitir la creación de la rama de respaldo
  • Usar --force en lugar de --force-with-lease
  • Reescribir una rama en la que otros están trabajando activamente sin notificación
  • Diferencias de contenido que aparecen en el diff del Paso 7 (significa que la reescritura cambió código, no solo mensajes)
  • Recuentos de autores que cambian después de la reescritura (significa que la autoría no se preservó)
  • Proceder sin la aprobación del usuario del plan de reescritura
  • Mensajes reescritos que siguen sin significado (por ejemplo, "chore: cosas")
  • Squash de commits de diferentes autores sin discusión
  • No mantener la rama de respaldo durante al menos 2 semanas después de la reescritura

Individual skills in this repo

This repo contains 18 individual skills — each has its own dedicated page.

lguidolin/change-hygiene-and-code-craft

Use when writing or refactoring code, structuring a commit or PR, or deciding whether to abstract duplication. Symptoms — mixing reorg with logic changes, a PR doing several things at once, a file growing large, the second copy of similar code, or unsure whether to DRY something up.

lguidolin/cloud-delivery-aks

Use when deploying to Kubernetes or Azure Kubernetes Service (AKS), configuring cloud secrets, setting up progressive rollout/canary, per-PR ephemeral environments, or k8s health probes. Keywords — Kubernetes, AKS, Key Vault, Argo Rollouts, Flagger, canary, blue-green, liveness, readiness, PodDisruptionBudget, HPA, rollback, GHCR.

lguidolin/conventional-commits-and-releases

Use when committing, writing a commit message, opening a PR that will be squash-merged, or configuring automated versioning/changelogs. Keywords — conventional commits, release-please, semver, feat/fix/chore, breaking change, changelog.

lguidolin/defense-in-depth-security

Use when handling untrusted input, secrets, authentication/authorization, or dependencies — or threat-modeling a new surface. Keywords — STRIDE, threat model, least privilege, secrets management, supply chain, dependency scanning, input validation, audit log, defense in depth.

lguidolin/designing-before-building

Use when starting a feature, fixing a non-trivial bug, or about to write implementation code — before any code exists. Symptoms you need this: "this is simple, I'll just code it", reaching for the editor before a design is approved, or an idea that hasn't been turned into a spec and plan.

lguidolin/engineering-constitution

Use when starting work in a project that follows the engineering constitution, orienting to its rules, or deciding which engineering practice applies to a task — spec writing, commits, testing, security, deploys, database, or UI work.

lguidolin/graphql-contract-testing

Use when writing a GraphQL query/mutation that the UI and a test will share, or building route/schema contract or smoke tests. Symptoms — copying a query into a test, a test asserting on query text, schema change that didn't break the UI build, or RLS/permission drift. Keywords — graphql-codegen, typed document, contract test, route smoke test.

lguidolin/init-repo-CI

Use when setting up a new repository with conventional commits, release-please, and CI automation, or when retrofitting an existing repository that lacks automated versioning and PR validation workflows.

lguidolin/interface-craft-and-accessibility

Use when building or styling UI — components, layouts, forms, design tokens — or making accessibility decisions. Keywords — a11y, WCAG, keyboard navigation, focus state, contrast, design system, minimalist UI, component reuse, ARIA, semantic HTML.

lguidolin/merge-gates-and-automation

Use when setting up or changing CI, pre-push hooks, or a task runner, or deciding what must pass before merge. Symptoms — tempted to put authoritative checks only in a local hook, skip CI, bypass with --no-verify, or unsure what gates a merge vs. runs locally.

lguidolin/observability-and-slos

Use when adding logging, metrics, tracing, health checks, SLOs, or alerting — or when building a service surface that needs to be operable and debuggable. Keywords — structured logs, OpenTelemetry, correlation id, RED metrics, liveness, readiness, SLI, SLO, error budget, alerting.

lguidolin/performance-and-scale

Use when working on hot paths, list endpoints, pagination, data-access in loops, or public interfaces/schemas. Symptoms — unbounded queries, N+1 access, no latency budget, optimizing without measuring, or changing an interface many consumers depend on. Keywords — pagination, N+1, Hyrum's Law, performance budget, bundle size.

lguidolin/postgres-postgraphile-rls-and-sql

Use when writing PostgreSQL, PostGraphile config, Row-Level Security policies, SQL schema files, or working on the Browser→App→PostGraphile→Postgres data path. Keywords — RLS, SECURITY DEFINER, search_path, pgSettings, grants, roles, GraphQL depth limit, query cost, statement_timeout, SQL file organization.

lguidolin/recording-decisions

Use when a design or architecture decision has been made and needs to be captured — writing a decision record or ADR, updating a decision index, noting a deferred idea, or superseding a past decision. Keywords — ADR, decision record, rationale, rejected alternatives, dependency index.

lguidolin/resilience-and-deploy-safety

Use when planning a deploy, designing a rollback, or responding to an incident or writing a postmortem. Keywords — deploy safety, rollback, immutable artifact, progressive delivery, canary, blast radius, incident response, blameless postmortem, error budget.

lguidolin/ship-it

Use when the user wants to ship work — push, PR, archive decision records, merge, and clean up. Handles the full lifecycle from committing final changes through post-merge cleanup including converting specs/plans to compact decision records.

lguidolin/tests-as-a-control

Use when writing or modifying tests, when a test breaks during a refactor, or when testing permission/role rules. Symptoms — tempted to edit a test to make it pass, testing only the happy path, a deny-test that started passing, flaky tests, or unsure what to assert.

lguidolin/zero-downtime-migrations

Use when changing a database schema where data must survive the change — adding/removing/renaming columns, constraints, indexes, or backfilling. Symptoms — a destructive migration bundled with a code deploy, a NOT NULL column with a backfill, a table-locking UPDATE, or a rename. Keywords — expand/contract, parallel change, backfill, NOT VALID, CREATE INDEX CONCURRENTLY, graphile-migrate.

Skills relacionados