-
Notifications
You must be signed in to change notification settings - Fork 0
Skill to improve commit history and make PR reviews easier #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,163 @@ | ||||||||||||||||||||||
| --- | ||||||||||||||||||||||
| name: clean-commits | ||||||||||||||||||||||
| description: Reorganize messy git commit history on a feature branch into clean, logical, reviewer-friendly commits. Use this skill when the user types /clean-commits, or asks to "clean up commits", "reorganize commits", "rewrite history", "squash commits for review", "make commits easier to review", or anything about restructuring git history on their current branch. Also trigger when the user mentions their PR has too many commits, messy history, or WIP commits they want to clean up before review. | ||||||||||||||||||||||
| --- | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Clean Commits | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Rewrites the commit history on the current feature branch into clean, logical commits optimized for code review. This is a history rewrite only — the final tree state is identical to the original. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Workflow | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Phase 1: Analyze | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 1. **Detect the base.** Find where this branch diverges from its upstream: | ||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| # Check if the branch tracks a remote with a PR target | ||||||||||||||||||||||
| gh pr view --json baseRefName 2>/dev/null | ||||||||||||||||||||||
| # Fall back to merge-base with main/master | ||||||||||||||||||||||
| git merge-base HEAD main 2>/dev/null || git merge-base HEAD master | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
| Only care about commits between the base and HEAD. Ignore commits that are already on the base branch. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 2. **Read the history.** For each commit on this branch: | ||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| git log <base>..HEAD --reverse --format="%h %s" --stat | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
| Understand what each commit does, which files it touches, and how changes relate to each other. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 3. **Identify the commit message convention.** Look at existing commit messages for prefixes like `[DX-793]`, `feat:`, `fix:`, ticket numbers, etc. The rewritten commits must follow the same convention. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Phase 2: Plan | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Group changes into logical commits. The goal is to make each commit independently reviewable with a clear purpose. Common groupings (adapt to what's actually in the branch): | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| | Priority | Category | Why separate | | ||||||||||||||||||||||
| |----------|----------|-------------| | ||||||||||||||||||||||
| | 1 | Infrastructure / utilities | Small, foundational — reviewer needs to understand these first | | ||||||||||||||||||||||
| | 2 | Base class / framework changes | The API that other code depends on | | ||||||||||||||||||||||
| | 3 | Mechanical migrations | Same pattern across many files — reviewer can skim. Note file count in the plan | | ||||||||||||||||||||||
| | 4 | Behavioral / logic changes | The interesting stuff — small diffs that deserve careful review | | ||||||||||||||||||||||
| | 5 | Test infrastructure + test updates | Tests grouped together, after the code they test | | ||||||||||||||||||||||
| | 6 | E2e / integration test fixes | Different environment, different review | | ||||||||||||||||||||||
| | 7 | Documentation | Docs last so reviewer sees the spec after the code | | ||||||||||||||||||||||
|
Comment on lines
+35
to
+43
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| **Key principles for grouping:** | ||||||||||||||||||||||
| - Separate mechanical changes (same pattern in 50+ files) from behavioral changes (logic that matters). This is the highest-value split — reviewers can skim the mechanical commit and focus on the behavioral one. | ||||||||||||||||||||||
| - If a file appears in multiple logical groups (e.g., got a mechanical migration AND a behavioral fix), use intermediate states from the original commits so each new commit shows only its logical change. | ||||||||||||||||||||||
| - Keep test changes separate from source changes where practical. | ||||||||||||||||||||||
| - Keep docs separate. | ||||||||||||||||||||||
| - Aim for 5-10 commits. Fewer than 5 usually means a commit is doing too much. More than 10 usually means over-splitting. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| **Present the plan to the user as a table:** | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
| | # | Commit message | Files | Purpose | | ||||||||||||||||||||||
| |---|---------------|-------|---------| | ||||||||||||||||||||||
| | 1 | [PREFIX] Add X utility | 1 | Core utility function | | ||||||||||||||||||||||
| | 2 | [PREFIX] Add Y to base command | 3 | Framework methods | | ||||||||||||||||||||||
| | 3 | [PREFIX] Migrate commands to use X | ~80 | Mechanical — same pattern everywhere | | ||||||||||||||||||||||
| | ... | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
Comment on lines
+52
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add language specifier to the fenced code block. The code block showing the example table should specify a language (e.g., 📝 Proposed fix **Present the plan to the user as a table:**
-```
+```text
| # | Commit message | Files | Purpose |As per coding guidelines, static analysis tool markdownlint-cli2 reports: "Fenced code blocks should have a language specified (MD040)". 📝 Committable suggestion
Suggested change
🧰 Tools🪛 markdownlint-cli2 (0.21.0)[warning] 53-53: Fenced code blocks should have a language specified (MD040, fenced-code-language) 🤖 Prompt for AI Agents |
||||||||||||||||||||||
| **Wait for user approval before proceeding.** They may want to adjust groupings or messages. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Phase 3: Execute | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 1. **Create a backup branch:** | ||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| BRANCH=$(git branch --show-current) | ||||||||||||||||||||||
| ORIGINAL_HEAD=$(git rev-parse HEAD) | ||||||||||||||||||||||
| git branch "${BRANCH}-backup" "$ORIGINAL_HEAD" | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 2. **Create a temporary rewrite branch from the base:** | ||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| BASE_COMMIT=<detected base> | ||||||||||||||||||||||
| git checkout -b "${BRANCH}-rewrite" "$BASE_COMMIT" | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 3. **Build each commit.** For each planned commit, checkout the right files and commit: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| For files that only appear in one logical commit, checkout from the final HEAD: | ||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| git checkout "$ORIGINAL_HEAD" -- path/to/file.ts | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| For files that appear in multiple logical commits (e.g., mechanical migration in commit 3, behavioral fix in commit 5), use intermediate states from the original history. Find the right source commit: | ||||||||||||||||||||||
| - The "mechanical only" state: checkout from the original commit that did the migration | ||||||||||||||||||||||
| - The "with behavioral fix" state: checkout from the final HEAD | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| **Use a shell script file for complex checkouts** to avoid quoting issues with inline bash. Write the file list to a script, then execute it: | ||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| # Write checkout script to /tmp to avoid shell quoting issues | ||||||||||||||||||||||
| cat > /tmp/checkout-files.sh << 'SCRIPT' | ||||||||||||||||||||||
| #!/bin/bash | ||||||||||||||||||||||
| set -e | ||||||||||||||||||||||
| # Files needing intermediate state | ||||||||||||||||||||||
| INTERMEDIATE_FILES=(path/to/file1.ts path/to/file2.ts) | ||||||||||||||||||||||
| for f in "${INTERMEDIATE_FILES[@]}"; do | ||||||||||||||||||||||
| git checkout <intermediate-commit> -- "$f" | ||||||||||||||||||||||
| done | ||||||||||||||||||||||
| # Files needing final state | ||||||||||||||||||||||
| git diff --name-only <base> <final> -- src/some/path/ | \ | ||||||||||||||||||||||
| grep -v 'special-file' | \ | ||||||||||||||||||||||
| while IFS= read -r f; do git checkout <final> -- "$f"; done | ||||||||||||||||||||||
| SCRIPT | ||||||||||||||||||||||
|
Comment on lines
+102
to
+105
|
||||||||||||||||||||||
| bash /tmp/checkout-files.sh | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Stage and commit (NO Co-Authored-By): | ||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| git add -A | ||||||||||||||||||||||
| git commit -m "[PREFIX] Commit message here" | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 4. **Swap the branch pointer:** | ||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| git branch -f "$BRANCH" "${BRANCH}-rewrite" | ||||||||||||||||||||||
| git checkout "$BRANCH" | ||||||||||||||||||||||
| git branch -d "${BRANCH}-rewrite" | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Phase 4: Verify | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 1. **Diff check — this is non-negotiable.** The final tree MUST match the original: | ||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| git diff "$BRANCH" "${BRANCH}-backup" -- <relevant paths> | ||||||||||||||||||||||
|
||||||||||||||||||||||
| git diff "$BRANCH" "${BRANCH}-backup" -- <relevant paths> | |
| git diff "$BRANCH" "${BRANCH}-backup" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add language specifier to the fenced code block.
The code block showing the backup branch cleanup command should specify bash as the language for proper rendering and to satisfy markdown linting rules.
📝 Proposed fix
Remind the user the backup branch is available locally:
-```
+```bash
git branch -d ${BRANCH}-backup # when you're happy with the result</details>
As per coding guidelines, static analysis tool markdownlint-cli2 reports: "Fenced code blocks should have a language specified (MD040)".
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.21.0)</summary>
[warning] 150-150: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @.claude/skills/clean-commits/SKILL.md around lines 142 - 152, Update the
fenced code block that shows the backup branch cleanup in "Phase 5: Push" so it
includes the language specifier "bash"; locate the block containing the literal
line "git branch -d ${BRANCH}-backup # when you're happy with the result" and
change the opening fence to specify bash (e.g., ```bash) to satisfy MD040 and
markdownlint-cli2.
</details>
<!-- fingerprinting:phantom:triton:puma -->
<!-- This is an auto-generated comment by CodeRabbit -->
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The “Detect the base” section runs
gh pr view --json baseRefNamebut doesn’t show how to extract the branch name (or compute the merge-base from it). As written it just prints JSON and isn’t actionable for determining<base>. Consider using--jqto extract.baseRefNameand then runninggit merge-base HEAD <baseRefName>(or document the exact next step).Only care about commits between the base and HEAD. Ignore commits that are already on the base branch.