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
7 changes: 3 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <noreply@anthropic.com>"
Nightshift-Task: <task-id>
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"
Expand Down
7 changes: 3 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <noreply@anthropic.com>"
Nightshift-Task: <task-id>
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"
Expand Down
12 changes: 8 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)" \
Expand Down Expand Up @@ -52,6 +52,10 @@ release: tag
git push origin "$(VERSION)"

install-hooks:
@echo "Installing git pre-commit hook..."
@ln -sf ../../scripts/pre-commit.sh .git/hooks/pre-commit
@echo "Done. Hook installed at .git/hooks/pre-commit"
@echo "Installing git hooks..."
@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"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
4 changes: 2 additions & 2 deletions docs/guides/releasing-new-version.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
84 changes: 84 additions & 0 deletions scripts/commit-msg-test.sh
Original file line number Diff line number Diff line change
@@ -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"
57 changes: 57 additions & 0 deletions scripts/commit-msg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# commit-msg hook for td
# Install all git hooks: 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

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: <task-id>" >&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
7 changes: 5 additions & 2 deletions scripts/loop-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,10 @@ Batch review loops:

```bash
git add <specific files>
git commit -m "feat: <summary> (td-<id>)"
git commit -m "feat: <summary> (td-<id>)

Nightshift-Task: <task-id>
Nightshift-Ref: https://github.com/marcus/nightshift"
td review <id>
```

Expand All @@ -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 <id> "Blocked: <reason>"` then `td block <id>`.
- **Commit messages reference td.** Format: `feat|fix|chore: <summary> (td-<id>)`
- **Commit messages use Conventional Commit subjects.** Format: `type: <summary>`; an optional `(td-<id>)` reference may appear in the subject or body. Nightshift-managed work includes `Nightshift-Task` and `Nightshift-Ref` trailers.
2 changes: 1 addition & 1 deletion scripts/pre-commit.sh
Original file line number Diff line number Diff line change
@@ -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
Expand Down