diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 456b816e..be5c8a42 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,5 +74,5 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" git add Formula/td.rb git diff --cached --quiet && echo "No changes" && exit 0 - git commit -m "td: bump to ${{ steps.version.outputs.version }}" + git commit -m "chore(homebrew): bump td to ${{ steps.version.outputs.version }}" git push diff --git a/CLAUDE.md b/CLAUDE.md index cd0ab661..a3b35dca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,13 +34,12 @@ go test ./... # Test all ```bash # Commit changes with proper message git add . -git commit -m "feat: description of changes +git commit -m "feat: describe changes (td-) Details here -🤖 Generated with Claude Code - -Co-Authored-By: Claude Haiku 4.5 " +Nightshift-Task: +Nightshift-Ref: https://github.com/marcus/nightshift" # Create version tag (bump from current version, e.g., v0.2.0 → v0.3.0) git tag -a v0.3.0 -m "Release v0.3.0: description" diff --git a/Makefile b/Makefile index 18da511f..ba869ff3 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ help: @printf "%s\n" \ "Targets:" \ " make fmt # gofmt -w ." \ - " make install-hooks # install git pre-commit hook" \ + " make install-hooks # install git pre-commit and commit-msg hooks" \ " make test # go test ./..." \ " make install # build and install with version from git" \ " make tag VERSION=vX.Y.Z # create annotated git tag (requires clean tree)" \ @@ -52,6 +52,8 @@ release: tag git push origin "$(VERSION)" install-hooks: - @echo "Installing git pre-commit hook..." + @mkdir -p .git/hooks + @echo "Installing git hooks..." @ln -sf ../../scripts/pre-commit.sh .git/hooks/pre-commit - @echo "Done. Hook installed at .git/hooks/pre-commit" + @ln -sf ../../scripts/commit-msg.sh .git/hooks/commit-msg + @echo "Done. Hooks installed at .git/hooks/pre-commit and .git/hooks/commit-msg" diff --git a/README.md b/README.md index 684416ad..93da7d25 100644 --- a/README.md +++ b/README.md @@ -189,10 +189,14 @@ make install-dev # Format code make fmt -# Install git pre-commit hook (gofmt, go vet, go build on staged files) +# Install git hooks: +# pre-commit -> gofmt, go vet, go build +# commit-msg -> normalize commit subjects make install-hooks ``` +Commit subjects are normalized to `type: summary` or `type(scope): summary`, with an optional trailing ` (td-)`. The `commit-msg` hook rewrites obvious cases like `Docs - Update changelog` to `docs: update changelog`, preserves commit bodies and trailers, leaves Git-generated merge/fixup/squash/revert subjects alone, and stops the commit with guidance when the subject cannot be safely interpreted. + ## Tests & Quality Checks ```bash diff --git a/docs/guides/releasing-new-version.md b/docs/guides/releasing-new-version.md index ca98e527..e0f0c5b9 100644 --- a/docs/guides/releasing-new-version.md +++ b/docs/guides/releasing-new-version.md @@ -53,9 +53,11 @@ Add entry at the top of `CHANGELOG.md`: Commit the changelog: ```bash git add CHANGELOG.md -git commit -m "docs: Update changelog for vX.Y.Z" +git commit -m "docs: update changelog for vX.Y.Z" ``` +If you have run `make install-hooks`, the local `commit-msg` hook will normalize obvious subject formatting issues, preserve the commit body and trailers, and reject subjects it cannot safely interpret. Use `type: summary` or `type(scope): summary`, plus an optional trailing ` (td-)` when the release prep is tied to a task. + ### 3. Verify Tests Pass ```bash @@ -137,7 +139,7 @@ go test ./... # Update changelog # (Edit CHANGELOG.md, add entry at top) git add CHANGELOG.md -git commit -m "docs: Update changelog for vX.Y.Z" +git commit -m "docs: update changelog for vX.Y.Z" # Push commits, then tag (tag push triggers automated release) git push origin main diff --git a/scripts/commit-msg.sh b/scripts/commit-msg.sh new file mode 100755 index 00000000..7a7a0946 --- /dev/null +++ b/scripts/commit-msg.sh @@ -0,0 +1,208 @@ +#!/usr/bin/env bash +# commit-msg hook for td +# Install: make install-hooks (or: ln -sf ../../scripts/commit-msg.sh .git/hooks/commit-msg) +set -euo pipefail + +message_file=${1:-} + +trim() { + local value=${1-} + value=${value#"${value%%[![:space:]]*}"} + value=${value%"${value##*[![:space:]]}"} + printf '%s' "$value" +} + +normalize_type() { + local raw normalized + + raw=$(trim "${1-}") + raw=$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]') + raw=${raw//_/-} + + case "$raw" in + feat|feature|features) + normalized="feat" + ;; + fix|bug|bugfix|bug-fix|hotfix) + normalized="fix" + ;; + docs|doc|documentation) + normalized="docs" + ;; + chore|chores) + normalized="chore" + ;; + build|ci|perf|refactor|revert|style) + normalized="$raw" + ;; + test|tests|testing) + normalized="test" + ;; + *) + return 1 + ;; + esac + + printf '%s' "$normalized" +} + +normalize_scope() { + local raw normalized + + raw=$(trim "${1-}") + raw=${raw#[} + raw=${raw%]} + raw=$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]') + normalized=$(printf '%s' "$raw" | sed -E 's/[[:space:]_]+/-/g; s/^-+//; s/-+$//; s/-+/-/g') + + if [[ -z "$normalized" || ! "$normalized" =~ ^[a-z0-9][a-z0-9._/-]*$ ]]; then + return 1 + fi + + printf '%s' "$normalized" +} + +normalize_summary() { + local summary first_char + + summary=$(trim "${1-}") + summary=$(printf '%s' "$summary" | sed -E 's/[[:space:]]+/ /g') + + if [[ "$summary" == *"." && "$summary" != *"..." ]]; then + summary=${summary%.} + fi + + if [[ "$summary" =~ ^([A-Z])([a-z].*)$ ]]; then + first_char=$(printf '%s' "${BASH_REMATCH[1]}" | tr '[:upper:]' '[:lower:]') + summary="${first_char}${BASH_REMATCH[2]}" + fi + + printf '%s' "$summary" +} + +print_error() { + local subject=${1-} + + cat >&2 <&2 + exit 1 +fi + +lines=() +while IFS= read -r line || [[ -n "$line" ]]; do + lines+=("$line") +done < "$message_file" +subject_index=-1 + +for i in "${!lines[@]}"; do + line=${lines[$i]} + if [[ -z $(trim "$line") || "$line" =~ ^# ]]; then + continue + fi + subject_index=$i + break +done + +if (( subject_index < 0 )); then + exit 0 +fi + +original_subject=${lines[$subject_index]} +normalized_subject=$(normalize_subject "$original_subject") + +if [[ "$normalized_subject" != "$(trim "$original_subject")" ]]; then + lines[$subject_index]=$normalized_subject + printf '%s\n' "${lines[@]}" > "$message_file" + echo "commit-msg: normalized subject to '$normalized_subject'" >&2 +fi diff --git a/scripts/loop-prompt.md b/scripts/loop-prompt.md index 7f84db2e..f9883b09 100644 --- a/scripts/loop-prompt.md +++ b/scripts/loop-prompt.md @@ -174,4 +174,4 @@ Use `td review`, not `td close` — self-closing is blocked. - **Don't break sync.** Deterministic IDs, proper event logging, no hard deletes. - **Session isolation is sacred.** Don't bypass review guards. - **If stuck, log and skip.** `td log "Blocked: "` then `td block `. -- **Commit messages reference td.** Format: `feat|fix|chore: (td-)` +- **Commit messages follow the repo hook.** Format: `type: ` or `type(scope): `, with optional ` (td-)`.