Communitygithub.com

lguidolin/commit-history-rewrite

Use quando um repositório existente possui um histórico de commits bagunçado que precisa estar em conformidade com commits convencionais antes de adotar o release-please, ou quando commits intermediários de WIP/fixup/merge precisam ser limpos.

O que é commit-history-rewrite?

Esta skill reescreve o histórico de commits de um repositório Git para conformidade com commits convencionais, essencial para ferramentas de automação como release-please. Ela analisa cada commit existente usando dois sinais: o conteúdo da mensagem e os arquivos alterados, classificando-os em tipos como feat, fix, docs, chore, refactor, test ou ci. Commits ambíguos ou sem sentido (WIP, fixup, merge) são identificados como candidatos a squash, e mensagens são reescritas seguindo o formato estrito de commits convencionais. O processo é totalmente seguro: antes de qualquer ação destrutiva, a skill exibe um plano completo para aprovação do usuário, cria um branch de backup, executa a reescrita (via rebase interativo para menos de 50 commits ou git-filter-repo para mais), e verifica rigorosamente que o conteúdo do código não foi alterado e que a autoria dos commits foi preservada. Por fim, realiza push com force-with-lease, protegendo contra alterações concorrentes remotas.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Reescrita de Histórico de Commits

Reescreve o histórico de commits de um repositório existente para estar em conformidade com commits convencionais. Analisa commits, classifica-os por tipo, identifica candidatos a squash e executa a reescrita com todas as proteções de segurança.

Visão Geral

Histórico é documentação. Cada mensagem de commit na main é um registro permanente que futuros engenheiros (e agentes) usarão para entender o que mudou e por quê. Mensagens como "fix", "update", "wip" destroem essa documentação. Release-please analisa mensagens de commit para determinar incrementos de versão — mensagens não conformes produzem versões erradas ou nenhuma versão.

Código é um passivo. A Lei de Hyrum também se aplica ao histórico do Git: com ferramentas suficientes dependendo de mensagens de commit (changelogs, automação de release, git bisect, git blame), o formato dessas mensagens se torna um contrato. Limpar o histórico antes de adotar automação evita que mensagens antigas poluam todas as ferramentas downstream.

Esta skill reescreve as mensagens, não o código. A árvore de arquivos deve ser idêntica antes e depois — apenas os metadados do commit mudam.

Quando Usar

  • Adotando commits convencionais em um repositório com histórico não conforme existente
  • O log de commits está poluído com mensagens WIP, fixup, merge ou sem sentido
  • Preparando um repositório para release-please (que precisa de commits convencionais para funcionar)
  • Limpando antes de tornar o código aberto ou transferir um repositório

Quando NÃO Usar

  • O repositório já foi publicado com commits convencionais (não há nada para reescrever)
  • Ramificações compartilhadas com colaboradores ativos que não foram notificados
  • Você só precisa corrigir os últimos commits (use git rebase -i HEAD~N diretamente)
  • O repositório é um fork onde o upstream controla o formato dos commits

Processo

Passo 1: Avaliar o Repositório

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

# Mostrar resumo das mensagens de commit
git log --oneline --no-merges

# Verificar colaboradores (diversidade de autoria)
git shortlog -sne --no-merges

# Identificar densidade de commits de merge
git log --merges --oneline | wc -l

Ponto de decisão: Se a contagem de commits < 50, use rebase interativo. Se >= 50, use git-filter-repo.

Passo 2: Classificar Commits Existentes

Analise cada commit usando dois sinais: o texto da mensagem e os arquivos alterados. Quando a mensagem é ambígua (ex.: "update stuff"), o diff é a fonte da verdade.

Sinal 1: Conteúdo da Mensagem

Sinal na mensagemTipo Atribuído
"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

Sinal 2: Arquivos Alterados (quando a mensagem é ambígua)

git show --stat --format="" <hash>
Padrão de arquivoTipo Atribuído
Apenas *.md, docs/**docs
Apenas *test*, *spec*, __tests__/**test
Apenas .github/workflows/**, .gitlab-ci.yml, Jenkinsfileci
Apenas package.json, *.lock, requirements.txt, Cargo.tomlchore
Apenas arquivos de configuração (.eslintrc, tsconfig.json, .prettierrc)chore

Candidatos a Squash

SinalAção
Mensagem é "WIP", "wip", "temp", "save", "checkpoint", palavra única ou vaziasquash no commit significativo mais próximo
Mensagem é "fixup", "fix typo", "oops", "lint", "formatting"squash no anterior
Commits de merge (de merges não-squash)candidato a squash — avalie se o conteúdo já está na main

Para commits ambíguos, verifique o diff completo (git show <hash>) para determinar o tipo a partir do conteúdo.

Passo 3: Escrever Mensagens de Qualidade

As mensagens reescritas devem seguir os padrões de mensagens de commit:

<tipo>[escopo opcional]: <descrição imperativa curta>

[corpo opcional explicando POR QUE, não o que]

Boas reescritas:

"initial setup"           →  "chore: initial project setup"
"added login"             →  "feat: add user login"
"fix bug"                 →  "fix: resolve login redirect loop"
"Update README.md"        →  "docs: update README with setup instructions"
"updated deps"            →  "chore(deps): update dependencies"

Reescritas ruins (não faça estas):

"stuff"                   →  "chore: stuff"          ← ainda sem sentido
"changes"                 →  "feat: changes"         ← sem descrição
"fix"                     →  "fix: fix"              ← redundante

Quando a mensagem original não tem significado, leia o diff para escrever uma descrição adequada. Se o diff for muito grande ou ambíguo, pergunte ao usuário sobre o que era o commit.

Passo 4: Apresentar o Plano de Reescrita

Antes de qualquer ação destrutiva, mostre ao usuário um plano completo:

PLANO DE REESCRITA
===================
Total de commits: 47
Commits a reescrever: 32
Commits a fazer squash: 8
Commits já em conformidade: 7
Autores preservados: 3 (Alice <alice@co>, Bob <bob@co>, CI Bot <bot@co>)

MUDANÇAS PROPOSTAS:
  abc1234  "initial setup"           →  "chore: initial project setup"
  def5678  "added login"             →  "feat: add user login"
  ghi9012  "wip"                     →  [SQUASH em def5678]
  jkl3456  "fix bug"                 →  "fix: resolve login redirect loop"
  mno7890  "Update README.md"        →  "docs: update README with setup instructions"
  pqr1234  "feat: add search"        →  [MANTER — já em conformidade]
  ...

SEGURANÇA:
  ✓ Branch de backup: backup/pre-rewrite-<timestamp>
  ⚠ Push forçado necessário após a reescrita
  ⚠ Todos os colaboradores devem clonar novamente ou resetar após o push

INALTERADO:
  - Árvore de arquivos (zero mudanças de conteúdo)
  - Nome do autor, email e data em cada commit
  - Ordem dos commits e relações de pai (exceto commits com squash)

Aguarde aprovação explícita do usuário antes de prosseguir. Não prossiga com "parece bom" — exija "sim" ou equivalente.

Passo 5: Criar Backup

# Crie um branch de backup no HEAD atual
git branch "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

# Verifique se o backup existe
git branch -l 'backup/*'

# Opcionalmente, envie o backup para o remoto para segurança extra
git push origin "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

Passo 6: Executar a Reescrita

Opção A: Rebase Interativo (< 50 commits)

# Rebase a partir da raiz
git rebase -i --root

No editor:

  • Marque candidatos a squash com squash ou fixup
  • Marque reescritas de mensagens com reword
  • Preserve a ordem dos commits

Preservação de autoria: O rebase interativo preserva nome, email e data do autor por padrão. NÃO use --reset-author. NÃO use --committer-date-is-author-date a menos que seja especificamente solicitado.

Opção B: git-filter-repo (>= 50 commits)

Crie um script de callback Python (rewrite-messages.py) que mapeia mensagens antigas para novas:

import re

REWRITES = {
    b'prefixo do hash antigo': b'nova mensagem',
    # ... gerado a partir do plano
}

def rewrite(commit, metadata):
    # Pesquisar pela mensagem original ou hash
    if commit.original_id in REWRITES:
        commit.message = REWRITES[commit.original_id]

Aplique com:

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

Preservação de autoria: git-filter-repo preserva a autoria por padrão. NÃO use --mailmap ou flags de reescrita de autor a menos que o usuário solicite explicitamente.

Squashing Commits

Para candidatos a squash, combine no commit significativo mais próximo. O commit resultante:

  • Usa a mensagem de commit convencional do commit significativo
  • Preserva o autor original do commit significativo (não do alvo do squash)
  • Combina todas as alterações do intervalo do squash

Passo 7: Verificar

Execute todas as verificações de validação. Cada verificação deve passar antes de prosseguir para o push.

# 1. Revise o log reescrito
git log --oneline --no-merges

# 2. Valide TODAS as mensagens correspondem a commits convencionais
FAILURES=$(git log --format="%s" --no-merges | grep -cvE "^(feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+")
if [[ "$FAILURES" -gt 0 ]]; then
  echo "FALHA: $FAILURES commits não 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 "PASSOU: todos os commits estão em conformidade"

# 3. Verifique ZERO mudanças de conteúdo (crítico — prova que apenas mensagens mudaram)
DIFF=$(git diff "backup/pre-rewrite-"*..HEAD --stat)
if [[ -n "$DIFF" ]]; then
  echo "FALHA: diferenças de conteúdo detectadas — a reescrita alterou código, não apenas mensagens"
  echo "$DIFF"
  exit 1
fi
echo "PASSOU: árvore de arquivos idêntica"

# 4. Verifique preservação de autoria
echo "=== Autores antes ==="
git -C . log backup/pre-rewrite-* --format="%an <%ae>" --no-merges | sort -u
echo "=== Autores depois ==="
git log --format="%an <%ae>" --no-merges | sort -u
# Estas duas listas devem ser idênticas

Se alguma verificação falhar, NÃO faça push. Restaure a partir do backup e investigue:

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

Passo 8: Push (com Confirmação)

Isto é destrutivo para colaboradores remotos. Confirme com o usuário:

⚠ AVISO DE FORCE PUSH
  Branch: main
  Remoto: origin
  Ação: git push --force-with-lease origin main

  Após este push, todos os colaboradores devem executar:
    git fetch origin
    git reset --hard origin/main

  Digite "sim" para prosseguir:
git push --force-with-lease origin main

Use --force-with-lease (não --force) para evitar sobrescrever alterações remotas concorrentes. Se --force-with-lease for rejeitado, alguém enviou para o branch desde a reescrita — investigue antes de tentar novamente.

Limpeza Pós-Reescrita

Após push bem-sucedido e notificação aos colaboradores:

# Verifique se o remoto corresponde ao local
git log --oneline -5
git log --oneline -5 origin/main
# Estes devem ser idênticos

# Mantenha o branch de backup por pelo menos 2 semanas, depois delete
# git branch -D backup/pre-rewrite-*
# git push origin --delete backup/pre-rewrite-*

Racionalizações

RacionalizaçãoRealidade
"O histórico antigo não importa"Release-please analisa mensagens de commit para determinar versões. Histórico não conforme produz versões erradas ou nenhuma versão. git bisect, git blame e git log --grep dependem de mensagens significativas.
"Vou simplesmente começar do zero a partir deste commit"Você perde blame, autoria e contexto. Um branch --orphan destrói a narrativa do desenvolvimento. Reescrever preserva tudo, exceto mensagens ruins.
"É muito arriscado reescrever"O branch de backup torna isso totalmente reversível. Verifique se o diff mostra zero mudanças de conteúdo. Se algo der errado: git reset --hard backup/pre-rewrite-*.
"Vou reescrever depois, quando for importante"As reescritas de histórico ficam mais difíceis com o tempo, à medida que mais pessoas clonam e criam branches a partir dele. Faça agora, antes que existam mais dependências downstream.
"Force push é perigoso"--force-with-lease protege contra sobrescrever o trabalho de outros. Combinado com o branch de backup, isso é seguro.
"As mensagens são boas o suficiente""Boas o suficiente" significa que release-please não vai analisá-las. Commits convencionais são um formato estrito — ou uma mensagem está em conformidade ou não está.
"Posso simplesmente marcar releases manualmente"Marcas manuais divergem, são esquecidas e carecem de integração com changelog. O objetivo é automação — mas automação precisa de entrada analisável.

Bandeiras Vermelhas

  • Pular a criação do branch de backup
  • Usar --force em vez de --force-with-lease
  • Reescrever um branch no qual outros estão trabalhando ativamente sem notificação
  • Diferenças de conteúdo aparecendo no diff do Passo 7 (significa que a reescrita alterou código, não apenas mensagens)
  • Contagens de autores mudando após a reescrita (significa que a autoria não foi preservada)
  • Prosseguir sem aprovação do usuário do plano de reescrita
  • Mensagens reescritas que ainda são sem sentido (ex.: "chore: stuff")
  • Fazer squash de commits de diferentes autores sem discussão
  • Não manter o branch de backup por pelo menos 2 semanas após a reescrita

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.

Habilidades Relacionadas