Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,12 @@ help:
@echo " check - Run tests and lint"
@echo " install - Build and install to Go bin directory"
@echo " calibrate-providers - Compare local Claude/Codex session usage for calibration"
@echo " install-hooks - Install git pre-commit hook"
@echo " install-hooks - Install git pre-commit and commit-msg hooks"
@echo " help - Show this help"

# Install git pre-commit hook
# Install git hooks
install-hooks:
@ln -sf ../../scripts/pre-commit.sh .git/hooks/pre-commit
@ln -sf ../../scripts/commit-msg.sh .git/hooks/commit-msg
@echo "✓ pre-commit hook installed (.git/hooks/pre-commit → scripts/pre-commit.sh)"
@echo "✓ commit-msg hook installed (.git/hooks/commit-msg → scripts/commit-msg.sh)"
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,19 +258,37 @@ Each task has a default cooldown interval to prevent the same task from running

## Development

### Pre-commit hooks
### Git hooks

Install the git pre-commit hook to catch formatting and vet issues before pushing:
Install the local git hooks before pushing:

```bash
make install-hooks
```

This symlinks `scripts/pre-commit.sh` into `.git/hooks/pre-commit`. The hook runs:
This symlinks:
- `scripts/pre-commit.sh` into `.git/hooks/pre-commit`
- `scripts/commit-msg.sh` into `.git/hooks/commit-msg`

The pre-commit hook runs:
- **gofmt** — flags any staged `.go` files that need formatting
- **go vet** — catches common correctness issues
- **go build** — ensures the project compiles

The commit-message hook expects a normalized subject line:
- `type: summary`
- `type(scope): summary`

Examples:
- `fix: restore commit message hook`
- `feat(tasks): add branch metadata`
- `docs: describe local git hooks`

Allowed exceptions:
- Git-generated subjects such as `Merge ...` and `Revert ...`
- Autosquash subjects such as `fixup! ...` and `squash! ...`
- Nightshift trailers such as `Nightshift-Task: ...` and `Nightshift-Ref: ...`

To bypass in a pinch: `git commit --no-verify`

## Uninstalling
Expand Down
56 changes: 56 additions & 0 deletions scripts/commit-msg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# commit-msg hook for nightshift
# Install: make install-hooks
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

normalize_line() {
local line=$1
line=${line%$'\r'}
printf '%s' "$line"
}

subject=
while IFS= read -r raw_line || [[ -n "$raw_line" ]]; do
line=$(normalize_line "$raw_line")
[[ -z "$line" ]] && continue
[[ $line == \#* ]] && continue
subject=$line
break
done <"$msg_file"

if [[ -z "$subject" ]]; then
cat >&2 <<'EOF'
commit-msg: empty commit message
Use: type: summary
EOF
exit 1
fi

case "$subject" in
Merge\ *|Revert\ *|fixup!\ *|squash!\ *)
exit 0
;;
esac

pattern='^(build|chore|ci|docs|feat|fix|perf|refactor|style|test)(\([a-z0-9#][a-z0-9._/#-]*\))?(!)?: [^[:space:]](.*[^[:space:]])?$'

if [[ $subject =~ $pattern ]]; then
exit 0
fi

cat >&2 <<EOF
commit-msg: invalid subject
Expected: type: summary
Example: fix: normalize commit message hook install
Allowed: optional scope like feat(tasks): ..., plus Merge/Revert/fixup!/squash! subjects
Bypass: git commit --no-verify
Found: $subject
EOF
exit 1
Loading