diff --git a/AGENTS.md b/AGENTS.md index d5f399ac..6a4ec545 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -118,10 +118,16 @@ fix(member): 마이페이지 API의 null 인증 주체 처리를 보완한다 refactor(content): 랭킹 조회 로직을 전용 리포지토리로 분리한다 ``` +### 커밋 메시지 검증 절차 +- `git commit` 실행 직전에 `work/scripts/check-commit-message-rules.sh`를 실행해 규칙 준수 여부를 확인한다. +- `git commit` 실행 직후에도 `work/scripts/check-commit-message-rules.sh`를 다시 실행해 최종 메시지를 재검증한다. +- 스크립트 결과가 `[FAIL]`이면 커밋 메시지를 규칙에 맞게 수정한 뒤 다시 검증한다. + ## 작업 절차 체크리스트 - 변경 전: 유사 기능 코드를 먼저 찾아 네이밍/예외/응답 패턴을 맞춘다. - 변경 중: 공개 API 스키마를 임의 변경하지 말고, 작은 단위로 안전하게 수정한다. - 변경 후: 최소 단일 테스트 또는 `./gradlew test`를 실행하고, 필요 시 `./gradlew ktlintCheck`를 수행한다. +- 커밋 전/후: `git commit` 직전과 직후에 `work/scripts/check-commit-message-rules.sh`를 실행해 커밋 메시지 규칙 준수 여부를 확인한다. ## 작업 계획 문서 규칙 (docs) - 모든 작업 시작 전에 `docs` 폴더 아래에 계획 문서를 먼저 생성하고, 해당 문서를 기준으로 구현을 진행한다. diff --git a/docs/20260220_커밋메시지검증규칙추가.md b/docs/20260220_커밋메시지검증규칙추가.md new file mode 100644 index 00000000..a22606d2 --- /dev/null +++ b/docs/20260220_커밋메시지검증규칙추가.md @@ -0,0 +1,15 @@ +# 20260220 커밋 메시지 검증 규칙 추가 + +## 구현 계획 +- [x] AGENTS.md의 커밋 메시지 규칙 섹션에 커밋 전/후 검증 절차를 추가한다. +- [x] AGENTS.md의 작업 절차 체크리스트에 커밋 전/후 스크립트 실행 규칙을 추가한다. +- [x] 문서 변경 검증을 위해 `./gradlew tasks --all`을 실행한다. +- [x] AGENTS.md 커밋 메시지 규칙과 불일치하는 `work/scripts/check-commit-message-rules.sh` 검증 로직을 정합성 있게 수정한다. +- [x] 수정한 스크립트에 대해 문법 및 실행 검증을 수행한다. + +## 검증 기록 +- [x] 검증 결과를 작업 완료 후 기록한다. + +- 무엇을: `AGENTS.md`에 커밋 전/후 검증 절차를 추가했고, `work/scripts/check-commit-message-rules.sh`를 AGENTS.md 기준(Conventional Commit 형식, 소문자 type, 한글 description, `Refs:` footer 형식)으로 정합성 있게 수정했다. +- 왜: 문서 규칙과 실제 검증 로직이 어긋나면 커밋 메시지 정책이 일관되게 강제되지 않기 때문이다. +- 어떻게 검증했는지: `bash -n ./work/scripts/check-commit-message-rules.sh`, 유효/무효 메시지 실행 검증(`--message`), `Refs` footer 유효/무효 케이스 검증을 수행했다. 추가로 `./gradlew tasks --all`과 `./gradlew build`를 실행해 저장소 명령 유효성과 전체 빌드 성공(`BUILD SUCCESSFUL`)을 확인했다. diff --git a/work/scripts/check-commit-message-rules.sh b/work/scripts/check-commit-message-rules.sh index db3a9a14..f95abbe3 100755 --- a/work/scripts/check-commit-message-rules.sh +++ b/work/scripts/check-commit-message-rules.sh @@ -1,71 +1,121 @@ -#!/bin/bash +#!/usr/bin/env bash -# Check if a commit message follows project rules -# Rules: 50/72 formatting, no advertisements/branding -# Usage: ./check-commit-message-rules.sh [commit-hash] -# If no commit-hash is provided, checks the latest commit +print_usage() { + echo "Usage:" + echo " $0" + echo " $0 " + echo " $0 --message \"\"" + echo " $0 --message-file " +} -# Determine which commit to check -if [ $# -eq 0 ]; then - commit_ref="HEAD" - echo "Checking latest commit..." -else - commit_ref="$1" - echo "Checking commit: $commit_ref" -fi +load_commit_message() { + if [ $# -eq 0 ]; then + local commit_ref="HEAD" + echo "Checking latest commit..." >&2 + git log -1 --pretty=format:"%s%n%b" "$commit_ref" + return + fi -# Get the commit message -commit_message=$(git log -1 --pretty=format:"%s%n%b" "$commit_ref") + case "$1" in + -h|--help) + print_usage + exit 0 + ;; + --message) + shift + if [ $# -eq 0 ]; then + echo "[FAIL] --message option requires a commit message" + print_usage + exit 1 + fi + echo "Checking provided commit message..." >&2 + printf '%s' "$*" + ;; + --message-file) + shift + if [ $# -ne 1 ]; then + echo "[FAIL] --message-file option requires a file path" + print_usage + exit 1 + fi + if [ ! -f "$1" ]; then + echo "[FAIL] Commit message file not found: $1" + exit 1 + fi + echo "Checking commit message file: $1" >&2 + cat "$1" + ;; + *) + if [ $# -ne 1 ]; then + echo "[FAIL] Invalid arguments" + print_usage + exit 1 + fi -# Split into subject and body -subject=$(echo "$commit_message" | head -n1) -body=$(echo "$commit_message" | tail -n +2 | sed '/^$/d') + local commit_ref="$1" + if ! git rev-parse --verify "$commit_ref^{commit}" >/dev/null 2>&1; then + echo "[FAIL] Invalid commit reference: $commit_ref" + exit 1 + fi + + echo "Checking commit: $commit_ref" >&2 + git log -1 --pretty=format:"%s%n%b" "$commit_ref" + ;; + esac +} + +commit_message=$(load_commit_message "$@") +subject=$(printf '%s\n' "$commit_message" | head -n1) +body=$(printf '%s\n' "$commit_message" | tail -n +2) echo "Checking commit message format..." echo "Subject: $subject" -# Check subject line length -subject_length=${#subject} -if [ $subject_length -gt 50 ]; then - echo "[FAIL] Subject line too long: $subject_length characters (max 50)" - exit_code=1 -else - echo "[PASS] Subject line length OK: $subject_length characters" - exit_code=0 +exit_code=0 + +if [ -z "$subject" ]; then + echo "[FAIL] Subject must not be empty" + exit 1 fi -# Check body line lengths if body exists -if [ -n "$body" ]; then - echo "Checking body line lengths..." - while IFS= read -r line; do - line_length=${#line} - if [ $line_length -gt 72 ]; then - echo "[FAIL] Body line too long: $line_length characters (max 72)" - echo "Line: $line" - exit_code=1 - fi - done <<< "$body" +subject_pattern='^([a-z]+)(\([a-z0-9._/-]+\))?(!)?: (.+)$' +if [[ "$subject" =~ $subject_pattern ]]; then + type="${BASH_REMATCH[1]}" + description="${BASH_REMATCH[4]}" - if [ $exit_code -eq 0 ]; then - echo "[PASS] All body lines within 72 characters" + echo "[PASS] Subject follows Conventional Commit format" + echo "[PASS] Type is lowercase: $type" + + if printf '%s\n' "$description" | grep -Eq '[가-힣]'; then + echo "[PASS] Description contains Korean text" + else + echo "[FAIL] Description must contain Korean text" + exit_code=1 fi else - echo "[INFO] No body content to check" + echo "[FAIL] Subject must match: (scope): " + echo " scope is optional, example: feat: 기능을 추가한다" + exit_code=1 fi -# Check for advertisements, branding, or promotional content -echo "Checking for advertisements and branding..." -if echo "$commit_message" | grep -qi "generated with\|claude code\|anthropic\|co-authored-by.*claude\|🤖"; then - echo "[FAIL] Commit message contains advertisements, branding, or promotional content" - exit_code=1 -else - echo "[PASS] No advertisements or branding detected" +if [ -n "$body" ] && printf '%s\n' "$body" | grep -Eq '^Refs:'; then + while IFS= read -r refs_line; do + if ! printf '%s\n' "$refs_line" | grep -Eq '^Refs: #[0-9]+(, #[0-9]+)*$'; then + echo "[FAIL] Refs footer format is invalid: $refs_line" + echo " expected format: Refs: #123 or Refs: #123, #456" + exit_code=1 + fi + done < <(printf '%s\n' "$body" | grep -E '^Refs:') + + if [ $exit_code -eq 0 ]; then + echo "[PASS] Refs footer format is valid" + fi fi if [ $exit_code -eq 0 ]; then - echo "[PASS] Commit message follows all rules" + echo "[PASS] Commit message follows AGENTS.md rules" else - echo "[FAIL] Commit message violates project rules" + echo "[FAIL] Commit message violates AGENTS.md rules" fi exit $exit_code