|
| 1 | +#!/bin/sh |
| 2 | +# |
| 3 | +# Commit message validation for productive-tools. |
| 4 | +# |
| 5 | +# Enforces: |
| 6 | +# 1. First line starts with a capitalized English verb (from allowlist) |
| 7 | +# 2. Rejects conventional commit prefixes (feat:, fix:, chore:, etc.) |
| 8 | +# 3. Requires Co-authored-by: Claude <claude@anthropic.com> trailer |
| 9 | +# |
| 10 | +# Skips: merge commits, Renovate bot commits, initial commits. |
| 11 | + |
| 12 | +COMMIT_MSG_FILE="$1" |
| 13 | +COMMIT_MSG=$(cat "$COMMIT_MSG_FILE") |
| 14 | +FIRST_LINE=$(head -1 "$COMMIT_MSG_FILE") |
| 15 | +FIRST_WORD=$(echo "$FIRST_LINE" | awk '{print $1}') |
| 16 | + |
| 17 | +# --- Skip rules --- |
| 18 | + |
| 19 | +# Merge commits |
| 20 | +if echo "$FIRST_LINE" | grep -qE "^Merge (branch|pull request|tag|remote)"; then |
| 21 | + exit 0 |
| 22 | +fi |
| 23 | + |
| 24 | +# Renovate bot / dependabot |
| 25 | +if echo "$COMMIT_MSG" | grep -qE "^(chore\(deps\)|fix\(deps\)|Update dependency|Lock file maintenance)"; then |
| 26 | + exit 0 |
| 27 | +fi |
| 28 | + |
| 29 | +# Revert commits (auto-generated by git) |
| 30 | +if echo "$FIRST_LINE" | grep -qE '^Revert "'; then |
| 31 | + exit 0 |
| 32 | +fi |
| 33 | + |
| 34 | +# Initial commit |
| 35 | +if echo "$FIRST_LINE" | grep -qiE "^Initial commit$"; then |
| 36 | + exit 0 |
| 37 | +fi |
| 38 | + |
| 39 | +# --- Rule 1: Reject conventional commit prefixes --- |
| 40 | + |
| 41 | +if echo "$FIRST_LINE" | grep -qE "^(feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(\(.+\))?(!)?:"; then |
| 42 | + echo "❌ Commit message rejected: conventional commit prefixes are not used in this project." |
| 43 | + echo "" |
| 44 | + echo " Got: $FIRST_LINE" |
| 45 | + echo " Expected: Start with a capitalized verb (e.g., \"Add ...\", \"Fix ...\", \"Update ...\")" |
| 46 | + echo "" |
| 47 | + exit 1 |
| 48 | +fi |
| 49 | + |
| 50 | +# --- Rule 2: First word must be a capitalized English verb --- |
| 51 | + |
| 52 | +ALLOWED_VERBS="Add|Fix|Update|Remove|Refactor|Improve|Bump|Prepare|Replace|Exclude|Support|Move|Rename|Clean|Create|Extract|Implement|Wire|Enable|Disable|Delete|Migrate|Rewrite|Simplify|Split|Merge|Revert|Document|Release|Publish|Configure|Normalize|Inline|Deduplicate|Reorganize|Consolidate|Integrate|Validate|Handle|Extend|Expose|Deprecate|Drop|Skip|Set|Use|Make|Run|Allow|Prevent|Ensure|Restore|Reset|Convert|Wrap|Unwrap|Format|Lint|Test|Mock|Stub|Pin|Unpin|Upgrade|Downgrade|Sync|Export|Import|Load|Unload|Register|Unregister|Init|Bootstrap|Scaffold|Generate|Emit|Suppress|Guard|Narrow|Widen|Relax|Restrict|Limit|Expand|Collapse|Flatten|Nest|Group|Ungroup|Sort|Filter|Map|Reduce|Batch|Queue|Retry|Cache|Flush|Purge|Prune|Trim|Pad|Truncate|Swap|Toggle|Flip|Invert|Negate|Clarify|Annotate|Comment|Log|Debug|Profile|Benchmark|Optimize|Parallelize|Serialize|Deserialize|Encode|Decode|Encrypt|Decrypt|Hash|Sign|Verify|Authenticate|Authorize|Revoke|Throttle|Debounce|Defer|Delay|Schedule|Cancel|Abort|Interrupt|Resume|Pause|Start|Stop|Restart|Shutdown|Connect|Disconnect|Bind|Unbind|Attach|Detach|Mount|Unmount|Inject|Eject|Patch|Hotfix|Backport|Cherry|Squash|Amend|Stash|Pop|Apply|Resolve|Reject|Throw|Catch|Recover|Fallback|Default|Override|Overload|Overwrite|Preserve|Retain|Persist|Save|Read|Write|Open|Close|Lock|Unlock|Acquire|Release|Yield|Await|Fetch|Pull|Push|Send|Receive|Stream|Pipe|Route|Redirect|Forward|Proxy|Relay|Broadcast|Notify|Alert|Warn|Inform|Report|Summarize|Aggregate|Compute|Calculate|Count|Measure|Track|Monitor|Watch|Observe|Listen|Subscribe|Unsubscribe|Publish|Unpublish|Activate|Deactivate|Show|Hide|Reveal|Conceal|Display|Render|Paint|Draw|Animate|Transition|Transform|Translate|Scale|Rotate|Skew|Clip|Crop|Resize|Zoom|Pan|Scroll|Focus|Blur|Select|Deselect|Highlight|Dim|Elevate|Lower|Raise|Lift|Embed|Unembed|Introduce|Finalize|Complete|Finish|Conclude|Terminate|End|Begin|Commence|Launch|Deploy|Provision|Tear|Destroy|Demolish|Rebuild|Reconstruct|Refurbish|Renovate|Overhaul|Revamp|Rework|Redo|Undo|Rebase|Reland|Re-export|Re-add" |
| 53 | + |
| 54 | +if ! echo "$FIRST_WORD" | grep -qE "^($ALLOWED_VERBS)$"; then |
| 55 | + echo "❌ Commit message rejected: first word must be a capitalized English verb." |
| 56 | + echo "" |
| 57 | + echo " Got: \"$FIRST_WORD\" in \"$FIRST_LINE\"" |
| 58 | + echo " Expected: Start with a verb like Add, Fix, Update, Remove, Refactor, Improve, Bump..." |
| 59 | + echo "" |
| 60 | + echo " Common verbs: Add, Fix, Update, Remove, Refactor, Improve, Bump, Prepare," |
| 61 | + echo " Replace, Create, Implement, Simplify, Migrate, Rewrite" |
| 62 | + echo "" |
| 63 | + exit 1 |
| 64 | +fi |
| 65 | + |
| 66 | +# --- Rule 3: Require Co-authored-by trailer --- |
| 67 | + |
| 68 | +if ! echo "$COMMIT_MSG" | grep -qF "Co-authored-by: Claude <claude@anthropic.com>"; then |
| 69 | + echo "❌ Commit message rejected: missing Co-authored-by trailer." |
| 70 | + echo "" |
| 71 | + echo " Add this line at the end of your commit message (after a blank line):" |
| 72 | + echo "" |
| 73 | + echo " Co-authored-by: Claude <claude@anthropic.com>" |
| 74 | + echo "" |
| 75 | + exit 1 |
| 76 | +fi |
| 77 | + |
| 78 | +exit 0 |
0 commit comments