Communitygithub.com

lguidolin/commit-history-rewrite

既存のリポジトリのコミット履歴が乱雑で、release-pleaseを導入する前に conventional commits に準拠させる必要がある場合や、WIP/fixup/マージコミットを整理したい場合に使用します。

commit-history-rewrite とは?

このスキルは、既存のGitリポジトリのコミット履歴を conventional commits 形式に書き換えるためのものです。release-please のようなツールはコミットメッセージをパースしてバージョンやリリースノートを自動生成するため、メッセージが不適切だと自動化が機能しません。 このスキルでは、まずリポジトリのコミット数を評価し、50コミット未満ならインタラクティブリベース、50以上なら git-filter-repo を使って書き換えます。各コミットのメッセージ内容と変更ファイルの2つのシグナルからコミットタイプ(feat, fix, refactor, chore, test, docs, ci)を分類し、WIPやfixupなどのコミットは適切なコミットにスカッシュします。 書き換え前に完全な計画をユーザーに提示し、明示的な承認を得ます。バックアップブランチを作成し、書き換え後はファイルツリーに変更がないこと、全コミットが conventional commits に準拠していること、著者が保持されていることを検証してから、--force-with-lease でプッシュします。安全装置が多数組み込まれており、ユーザーの承認なしに破壊的処理を実行しません。

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

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Commit History Rewrite (コミット履歴書き換え)

既存のリポジトリのコミット履歴を conventional commits に準拠するように書き換えます。コミットを分析し、タイプ別に分類し、スカッシュ候補を特定し、完全な安全装置付きで書き換えを実行します。

概要

履歴はドキュメントです。 メインブランチのすべてのコミットメッセージは、将来のエンジニア(およびエージェント)が 何が 変わったのか、なぜ 変わったのかを理解するための永続的な記録です。「fix」「update」「wip」のようなメッセージはこのドキュメントを破壊します。release-please はコミットメッセージをパースしてバージョンを決定します。準拠していないメッセージは誤ったリリース、またはリリースなしという結果を生みます。

コードは負債です。 Hyrumの法則はGit履歴にも当てはまります。ツールがコミットメッセージに依存する(チェンジログ、リリース自動化、git bisect、git blame)につれて、それらのメッセージの形式は契約になります。自動化を導入する前に履歴をクリーンアップすることで、古いメッセージがすべての下流ツールを汚染するのを防ぎます。

このスキルは メッセージ を書き換え、コード は書き換えません。ファイルツリーは書き換え前後で同一でなければなりません。変更されるのはコミットメタデータのみです。

使用すべきとき

  • 既存の conventional commits に準拠していないリポジトリに conventional commits を採用する場合
  • コミットログが WIP、fixup、マージ、または意味のないメッセージで汚染されている場合
  • release-please のためにリポジトリを準備する場合(release-please が機能するには conventional commits が必要)
  • リポジトリをオープンソース化する前、または移管する前にクリーンアップする場合

使用すべきでないとき

  • リポジトリがすでに conventional commits で公開されている場合(書き換えるものがない)
  • 通知されていないアクティブな共同作業者がいる共有ブランチの場合
  • 直近の 数個の コミットだけを修正する必要がある場合(直接 git rebase -i HEAD~N を使用)
  • フォークであり、上流がコミット形式を制御している場合

プロセス

ステップ1: リポジトリを評価

# 総コミット数をカウント
git rev-list --count HEAD

# コミットメッセージのサマリーを表示
git log --oneline --no-merges

# 共同作業者を確認(著者の多様性)
git shortlog -sne --no-merges

# マージコミットの密度を特定
git log --merges --oneline | wc -l

判断ポイント: コミット数が50未満の場合はインタラクティブリベースを使用。50以上の場合は git-filter-repo を使用。

ステップ2: 既存のコミットを分類

各コミットを 2つのシグナル(メッセージテキストと変更ファイル)を使用して分析します。メッセージがあいまいな場合(例:「update stuff」)、差分が真実の源です。

シグナル1: メッセージ内容

メッセージ内のシグナル割り当てられるタイプ
「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

シグナル2: 変更ファイル(メッセージがあいまいな場合)

git show --stat --format="" <hash>
ファイルパターン割り当てられるタイプ
*.md、docs/** のみdocs
*test*、*spec*、__tests__/** のみtest
.github/workflows/**、.gitlab-ci.yml、Jenkinsfile のみci
package.json、*.lock、requirements.txt、Cargo.toml のみchore
設定ファイル(.eslintrc、tsconfig.json、.prettierrc)のみchore

スカッシュ候補

シグナルアクション
メッセージが「WIP」「wip」「temp」「save」「checkpoint」、単一単語、または空直近の意味のあるコミットにスカッシュ
メッセージが「fixup」「fix typo」「oops」「lint」「formatting」前のコミットにスカッシュ
マージコミット(スカッシュマージ以外)スカッシュ候補 — 内容がすでにメインブランチにあるか評価

あいまいなコミットについては、完全な差分(git show <hash>)を確認して、内容からタイプを判断します。

ステップ3: 品質の高いメッセージを書く

書き換えられたメッセージはコミットメッセージの標準に従わなければなりません。

<type>[optional scope]: <short imperative description>

[optional body explaining WHY, not what]

良い書き換え例:

"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"

悪い書き換え例(やってはいけない):

"stuff"                   →  "chore: stuff"          ← 依然として無意味
"changes"                 →  "feat: changes"         ← 説明なし
"fix"                     →  "fix: fix"              ← 冗長

元のメッセージが無意味な場合は、差分を読んで適切な説明を書きます。差分が大きすぎるかあいまいな場合は、ユーザーにコミットの内容を尋ねます。

ステップ4: 書き換え計画を提示

破壊的処理の前に、ユーザーに完全な計画を示します。

REWRITE PLAN (書き換え計画)
============
Total commits: 47
Commits to rewrite: 32
Commits to squash: 8
Commits already conforming: 7
Authors preserved: 3 (Alice <alice@co>, Bob <bob@co>, CI Bot <bot@co>)

PROPOSED CHANGES (提案された変更):
  abc1234  "initial setup"           →  "chore: initial project setup"
  def5678  "added login"             →  "feat: add user login"
  ghi9012  "wip"                     →  [SQUASH into def5678]
  jkl3456  "fix bug"                 →  "fix: resolve login redirect loop"
  mno7890  "Update README.md"        →  "docs: update README with setup instructions"
  pqr1234  "feat: add search"        →  [KEEP — already conforming]
  ...

SAFETY (安全性):
  ✓ Backup branch: backup/pre-rewrite-<timestamp>
  ⚠ Force push required after rewrite (書き換え後に強制プッシュが必要)
  ⚠ All collaborators must re-clone or reset after push (すべての共同作業者はプッシュ後に再クローンまたはリセットが必要)

UNCHANGED (変更されないもの):
  - File tree (zero content changes) (ファイルツリー: 内容の変更はゼロ)
  - Author name, email, and date on every commit (各コミットの著者名、メール、日付)
  - Commit order and parent relationships (except squashed commits) (コミット順序と親の関係: スカッシュされたコミットを除く)

先に進む前に、明示的なユーザーの承認を待ちます。 「いいね」や「了解」では進めず、「yes」または同等の明確な応答を要求します。

ステップ5: バックアップを作成

# 現在のHEADにバックアップブランチを作成
git branch "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

# バックアップが存在することを確認
git branch -l 'backup/*'

# オプション: バックアップをリモートにプッシュして安全性を高める
git push origin "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

ステップ6: 書き換えを実行

オプションA: インタラクティブリベース(50コミット未満)

# ルートからリベース
git rebase -i --root

エディターで:

  • スカッシュ候補は squash または fixup とマーク
  • メッセージ書き換えは reword とマーク
  • コミット順序を保持

著者の保存: インタラクティブリベースはデフォルトで著者名、メール、日付を保存します。--reset-author を使用しないでください。--committer-date-is-author-date は明示的に要求された場合以外は使用しないでください。

オプションB: git-filter-repo(50コミット以上)

古いメッセージを新しいメッセージにマッピングするPythonコールバックスクリプト(rewrite-messages.py)を作成します。

import re

REWRITES = {
    b'old hash prefix': b'new message',
    # ... 計画から生成
}

def rewrite(commit, metadata):
    # 元のメッセージまたはハッシュで検索
    if commit.original_id in REWRITES:
        commit.message = REWRITES[commit.original_id]

以下で適用:

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

著者の保存: git-filter-repo はデフォルトで著者を保存します。ユーザーが明示的に要求しない限り、--mailmap や著者書き換えフラグを使用しないでください。

コミットのスカッシュ

スカッシュ候補については、直近の意味のあるコミットに統合します。結果のコミットは:

  • 意味のあるコミットの conventional commit メッセージを使用
  • 意味のあるコミットの 元の著者 を保持(スカッシュ先ではない)
  • スカッシュ範囲のすべての変更を結合

ステップ7: 検証

すべての検証チェックを実行します。プッシュに進む前に、すべてのチェックが合格しなければなりません。

# 1. 書き換えられたログを確認
git log --oneline --no-merges

# 2. すべてのメッセージが conventional commits に準拠していることを検証
FAILURES=$(git log --format="%s" --no-merges | grep -cvE "^(feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+")
if [[ "$FAILURES" -gt 0 ]]; then
  echo "FAIL: $FAILURES non-conforming commits found:"
  git log --format="%h %s" --no-merges | grep -vE "^[a-f0-9]+ (feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+"
  exit 1
fi
echo "PASS: all commits conform"

# 3. コンテンツの変更がゼロであることを検証(重要 — メッセージのみが変更されたことを証明)
DIFF=$(git diff "backup/pre-rewrite-"*..HEAD --stat)
if [[ -n "$DIFF" ]]; then
  echo "FAIL: content differences detected — rewrite changed code, not just messages"
  echo "$DIFF"
  exit 1
fi
echo "PASS: file tree identical"

# 4. 著者が保持されていることを検証
echo "=== Authors before ==="
git -C . log backup/pre-rewrite-* --format="%an <%ae>" --no-merges | sort -u
echo "=== Authors after ==="
git log --format="%an <%ae>" --no-merges | sort -u
# これらの2つのリストは同一でなければならない

いずれかのチェックが失敗した場合、プッシュしないでください。 バックアップにリセットして調査します:

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

ステップ8: プッシュ(確認付き)

これはリモートの共同作業者にとって破壊的です。 ユーザーに確認します:

⚠ FORCE PUSH WARNING (強制プッシュ警告)
  Branch: main
  Remote: origin
  Action: git push --force-with-lease origin main

  After this push, all collaborators must run:
    git fetch origin
    git reset --hard origin/main

  Type "yes" to proceed: (続行するには「yes」と入力)
git push --force-with-lease origin main

--force ではなく --force-with-lease を使用して、同時のリモート変更を上書きしないようにします。--force-with-lease が拒否された場合、書き換え以降に誰かがブランチにプッシュしています。再試行する前に調査してください。

書き換え後のクリーンアップ

プッシュと共同作業者への通知が成功した後:

# リモートがローカルと一致することを確認
git log --oneline -5
git log --oneline -5 origin/main
# これらは同一である必要があります

# バックアップブランチは少なくとも2週間保持し、その後削除
# git branch -D backup/pre-rewrite-*
# git push origin --delete backup/pre-rewrite-*

合理化

合理化現実
「古い履歴は重要じゃない」release-please はコミットメッセージをパースしてバージョンを決定します。準拠していない履歴は誤ったリリースやリリースなしを引き起こします。git bisect、git blame、git log --grep はすべて意味のあるメッセージに依存しています。
「このコミットから新しく始めればいい」blame、著者、コンテキストが失われます。--orphan ブランチは開発の物語を破壊します。書き換えは悪いメッセージ以外のすべてを保持します。
「書き換えるのはリスクが高すぎる」バックアップブランチにより完全に元に戻せます。差分がコンテンツの変更ゼロであることを検証します。何か問題が発生した場合: git reset --hard backup/pre-rewrite-*。
「後でやるよ、重要になってから」より多くの人がクローンしてブランチを作成するにつれて、履歴の書き換えは時間とともに難しくなります。下流の依存関係が増える前に、今すぐ行いましょう。
「強制プッシュは危険だ」--force-with-lease は他の人の作業を上書きしないように保護します。バックアップブランチと組み合わせることで安全です。
「メッセージはだいたい合ってる」「だいたい合ってる」では release-please はパースしません。Conventional commits は厳格な形式です。メッセージは準拠するか、しないかのどちらかです。
「手動でリリースにタグを付けられる」手動タグはずれたり、忘れられたり、チェンジログとの統合が欠けます。ポイントは自動化です。しかし自動化にはパース可能な入力が必要です。

レッドフラグ

  • バックアップブランチの作成をスキップする
  • --force-with-lease の代わりに --force を使用する
  • 通知なしに他の人がアクティブに作業しているブランチを書き換える
  • ステップ7の差分にコンテンツの違いが現れる(書き換えがメッセージだけでなくコードを変更したことを意味する)
  • 書き換え後に著者数が変わる(著者が保持されていないことを意味する)
  • 書き換え計画のユーザー承認なしに進める
  • 書き換えられたメッセージが依然として無意味(例:「chore: stuff」)
  • 異なる著者間のコミットを相談なしにスカッシュする
  • 書き換え後にバックアップブランチを少なくとも2週間保持しない

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.

関連スキル