From 1a3edeecdc90da7745c382910103716ddcc6229f Mon Sep 17 00:00:00 2001 From: tester Date: Sun, 10 May 2026 02:36:37 -0700 Subject: [PATCH 1/2] chore: normalize commit message format Narrow Nightshift trailer enforcement to explicit hook signals and add regression coverage for normal commits about Nightshift-related code. Nightshift-Task: commit-normalize Nightshift-Ref: https://github.com/marcus/nightshift --- AGENTS.md | 7 +-- CLAUDE.md | 7 +-- Makefile | 7 ++- README.md | 2 +- docs/guides/releasing-new-version.md | 4 +- scripts/commit-msg-test.sh | 84 ++++++++++++++++++++++++++++ scripts/commit-msg.sh | 57 +++++++++++++++++++ scripts/loop-prompt.md | 7 ++- scripts/pre-commit.sh | 2 +- 9 files changed, 160 insertions(+), 17 deletions(-) create mode 100755 scripts/commit-msg-test.sh create mode 100755 scripts/commit-msg.sh diff --git a/AGENTS.md b/AGENTS.md index cbabd8be..b9ea07f4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,13 +73,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 the change Details here -🤖 Generated with Codex - -Co-Authored-By: Codex 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/CLAUDE.md b/CLAUDE.md index 571cd506..c130e515 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,13 +73,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 the change 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..d24d7c4b 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 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,7 @@ release: tag git push origin "$(VERSION)" install-hooks: - @echo "Installing git pre-commit hook..." + @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..d5c4f99f 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,7 @@ 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 checks and commit message format) make install-hooks ``` diff --git a/docs/guides/releasing-new-version.md b/docs/guides/releasing-new-version.md index ca98e527..a771ab2c 100644 --- a/docs/guides/releasing-new-version.md +++ b/docs/guides/releasing-new-version.md @@ -53,7 +53,7 @@ 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" ``` ### 3. Verify Tests Pass @@ -137,7 +137,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-test.sh b/scripts/commit-msg-test.sh new file mode 100755 index 00000000..f17d9d62 --- /dev/null +++ b/scripts/commit-msg-test.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +HOOK="$ROOT/scripts/commit-msg.sh" +TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/td-commit-msg-test-XXXXXX") +trap 'rm -rf "$TMPDIR"' EXIT + +pass=0 +fail=0 + +run_case() { + local name=$1 + local expected=$2 + local env_mode=$3 + local file="$TMPDIR/$name.msg" + shift 3 + + printf "%s" "$*" > "$file" + + set +e + if [[ "$env_mode" == "nightshift" ]]; then + NIGHTSHIFT_TASK=commit-normalize "$HOOK" "$file" >"$TMPDIR/$name.out" 2>&1 + else + "$HOOK" "$file" >"$TMPDIR/$name.out" 2>&1 + fi + local status=$? + set -e + + if [[ "$expected" == "pass" && $status -eq 0 ]] || [[ "$expected" == "fail" && $status -ne 0 ]]; then + echo "ok - $name" + pass=$((pass + 1)) + else + echo "not ok - $name" + sed 's/^/ /' "$TMPDIR/$name.out" + fail=$((fail + 1)) + fi +} + +run_case "valid-conventional" pass none "feat: normalize commit messages + +Add the hook. +" + +run_case "valid-with-td-reference" pass none "fix: tighten review flow (td-a1b2) +" + +run_case "valid-nightshift-topic-without-trailers" pass none "fix: handle nightshift scheduler crash +" + +run_case "valid-nightshift-trailers" pass nightshift "chore: normalize commit examples + +Nightshift-Task: commit-normalize +Nightshift-Ref: https://github.com/marcus/nightshift +" + +run_case "invalid-subject" fail none "normalize commit examples +" + +run_case "invalid-overlong-subject" fail none "feat: this subject is intentionally far too long for the repository commit message hook +" + +run_case "missing-nightshift-trailers" fail nightshift "chore: normalize commit examples +" + +run_case "exempt-merge" pass none "Merge branch 'main' into feature +" + +run_case "exempt-revert" pass none "Revert \"feat: normalize commit messages\" +" + +run_case "exempt-fixup" pass none "fixup! feat: normalize commit messages +" + +run_case "exempt-squash" pass none "squash! feat: normalize commit messages +" + +echo "" +if [[ $fail -gt 0 ]]; then + echo "$fail commit-msg test(s) failed" + exit 1 +fi + +echo "$pass commit-msg test(s) passed" diff --git a/scripts/commit-msg.sh b/scripts/commit-msg.sh new file mode 100755 index 00000000..9934f352 --- /dev/null +++ b/scripts/commit-msg.sh @@ -0,0 +1,57 @@ +#!/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 + +MSG_FILE=${1:-} + +if [[ -z "$MSG_FILE" || ! -f "$MSG_FILE" ]]; then + echo "commit-msg: missing commit message file" >&2 + exit 1 +fi + +subject=$( + sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d; q' "$MSG_FILE" | + sed 's/^[[:space:]]*//; s/[[:space:]]*$//' +) + +if [[ -z "$subject" ]]; then + echo "commit-msg: subject must not be empty" >&2 + exit 1 +fi + +case "$subject" in + Merge\ *|Revert\ *|fixup!\ *|squash!\ *|amend!\ *) + exit 0 + ;; +esac + +if (( ${#subject} > 72 )); then + echo "commit-msg: subject must be 72 characters or less" >&2 + exit 1 +fi + +type_pattern='(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)' +if ! [[ "$subject" =~ ^${type_pattern}(\([a-z0-9._-]+\))?!?:[[:space:]][^[:space:]].* ]]; then + echo "commit-msg: subject must use Conventional Commit format: type: summary" >&2 + echo "commit-msg: allowed types: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test" >&2 + exit 1 +fi + +nightshift_signal=0 +if [[ -n "${NIGHTSHIFT_TASK:-}" || -n "${NIGHTSHIFT_REF:-}" || -n "${NIGHTSHIFT:-}" ]]; then + nightshift_signal=1 +elif grep -Eq '^Nightshift-(Task|Ref):' "$MSG_FILE"; then + nightshift_signal=1 +fi + +if (( nightshift_signal )); then + if ! grep -Eq '^Nightshift-Task: [^[:space:]].*$' "$MSG_FILE"; then + echo "commit-msg: Nightshift work requires trailer: Nightshift-Task: " >&2 + exit 1 + fi + if ! grep -Eq '^Nightshift-Ref: https://github\.com/marcus/nightshift$' "$MSG_FILE"; then + echo "commit-msg: Nightshift work requires trailer: Nightshift-Ref: https://github.com/marcus/nightshift" >&2 + exit 1 + fi +fi diff --git a/scripts/loop-prompt.md b/scripts/loop-prompt.md index 7f84db2e..ad009e5a 100644 --- a/scripts/loop-prompt.md +++ b/scripts/loop-prompt.md @@ -158,7 +158,10 @@ Batch review loops: ```bash git add -git commit -m "feat: (td-)" +git commit -m "feat: (td-) + +Nightshift-Task: +Nightshift-Ref: https://github.com/marcus/nightshift" td review ``` @@ -174,4 +177,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 use Conventional Commit subjects.** Format: `type: `; an optional `(td-)` reference may appear in the subject or body. Nightshift-managed work includes `Nightshift-Task` and `Nightshift-Ref` trailers. diff --git a/scripts/pre-commit.sh b/scripts/pre-commit.sh index 2b563f26..be4cf552 100755 --- a/scripts/pre-commit.sh +++ b/scripts/pre-commit.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # pre-commit hook for td -# Install: make install-hooks (or: ln -sf ../../scripts/pre-commit.sh .git/hooks/pre-commit) +# Install all git hooks: make install-hooks set -euo pipefail PASS=0 From 1c9ad4cf255f9adf6c47b4fc9284e1f66bcd7457 Mon Sep 17 00:00:00 2001 From: tester Date: Sun, 10 May 2026 02:51:36 -0700 Subject: [PATCH 2/2] fix: support hook install from linked worktrees Resolve the hook directory through Git so make install-hooks works when .git is a file in a linked worktree. Nightshift-Task: commit-normalize Nightshift-Ref: https://github.com/marcus/nightshift --- Makefile | 9 ++++++--- scripts/commit-msg.sh | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index d24d7c4b..d94d8578 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,9 @@ release: tag install-hooks: @echo "Installing git hooks..." - @ln -sf ../../scripts/pre-commit.sh .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" + @hooks_dir=$$(git rev-parse --git-path hooks); \ + repo_root=$$(git rev-parse --show-toplevel); \ + mkdir -p "$$hooks_dir"; \ + ln -sf "$$repo_root/scripts/pre-commit.sh" "$$hooks_dir/pre-commit"; \ + ln -sf "$$repo_root/scripts/commit-msg.sh" "$$hooks_dir/commit-msg"; \ + echo "Done. Hooks installed at $$hooks_dir/pre-commit and $$hooks_dir/commit-msg" diff --git a/scripts/commit-msg.sh b/scripts/commit-msg.sh index 9934f352..98390e0c 100755 --- a/scripts/commit-msg.sh +++ b/scripts/commit-msg.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # commit-msg hook for td -# Install: make install-hooks (or: ln -sf ../../scripts/commit-msg.sh .git/hooks/commit-msg) +# Install all git hooks: make install-hooks set -euo pipefail MSG_FILE=${1:-}