diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 42df6bc..2efe478 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,6 +55,19 @@ repos: entry: bash -lc 'go vet ./...' language: system pass_filenames: false + - id: require-issue-reference + name: issue reference check (warning) + description: Warn if commit message lacks a GitHub issue reference like #123 + entry: bash -lc 'scripts/require_issue_ref.sh "$1"' + language: system + stages: [commit-msg] + pass_filenames: true + - id: line-count-limit + name: line count limit (warn >500, block >750) + description: Warn on files 501–750 lines; fail if any >750 lines + entry: bash -lc 'scripts/line_count_check.sh' + language: system + pass_filenames: true # GolangCI-Lint (static analysis) - repo: https://github.com/golangci/golangci-lint diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fb4745d..1fc582b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,6 +67,10 @@ Our pre-commit `no-commit-to-branch` hook blocks commits on non-conforming branc ``` or commit normally and the `commit-msg` hook will validate. +- Issue reference: include a GitHub issue reference (`#`) somewhere in the commit + message (body or footer). Examples: `Refs #29`, `Closes #102`. + The commit-msg hook only warns if missing; commits still proceed. + ## Development Workflow - Create or pick up an issue. For new features, propose design via an issue first. @@ -86,6 +90,13 @@ Our pre-commit `no-commit-to-branch` hook blocks commits on non-conforming branc - `go fmt ./...` and `go vet ./...` run via pre-commit. - `golangci-lint` runs as part of pre-commit; CI integration may run it on PRs as well. +## File Size Guidelines + +- Keep source files concise. The pre-commit hook warns when a changed file exceeds 500 lines and blocks commits for files over 750 lines. + - Warning: 501–750 lines (message only; commit proceeds) + - Error: >750 lines (commit blocked) + - Consider splitting large files into smaller units for readability and maintainability. + ## Security & Secrets - `detect-secrets` scans for potential credentials. diff --git a/scripts/line_count_check.sh b/scripts/line_count_check.sh new file mode 100644 index 0000000..a868114 --- /dev/null +++ b/scripts/line_count_check.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Pre-commit hook: Warn for files >500 lines (up to 750), +# block commit if any file exceeds 750 lines. + +warn_threshold=500 +block_threshold=750 + +warned=0 +blocked=0 +declare -a blocked_files +declare -a warned_files + +is_regular_file() { + [[ -f "$1" ]] +} + +line_count() { + # Use wc -l; handle files with no trailing newline + wc -l < "$1" | awk '{print $1}' +} + +for f in "$@"; do + # Skip if not a regular file (deleted, dir, symlink, etc.) + if ! is_regular_file "$f"; then + continue + fi + + # Skip binary files: if grep treating binary as non-text, skip + if ! LC_ALL=C grep -Iq . "$f"; then + continue + fi + + cnt=$(line_count "$f" || echo 0) + + if (( cnt > block_threshold )); then + blocked=1 + blocked_files+=("$f ($cnt lines)") + elif (( cnt > warn_threshold )); then + warned=1 + warned_files+=("$f ($cnt lines)") + fi +done + +if (( warned == 1 )); then + echo "[line-count] Warning: some files exceed ${warn_threshold} lines:" >&2 + for w in "${warned_files[@]}"; do + echo " - $w" >&2 + done +fi + +if (( blocked == 1 )); then + echo "[line-count] Error: files exceed ${block_threshold} lines; reduce size or split files:" >&2 + for b in "${blocked_files[@]}"; do + echo " - $b" >&2 + done + exit 1 +fi + +exit 0 diff --git a/scripts/require_issue_ref.sh b/scripts/require_issue_ref.sh new file mode 100644 index 0000000..0259b6c --- /dev/null +++ b/scripts/require_issue_ref.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +# pre-commit commit-msg hook: check commit message references a GitHub issue +# Usage: scripts/require_issue_ref.sh + +msg_file="${1:-}" +if [[ -z "${msg_file}" || ! -f "${msg_file}" ]]; then + echo "commit-msg hook error: commit message file not found" >&2 + exit 1 +fi + +# Read first line (subject) +subject="$(head -n1 "$msg_file" | tr -d '\r')" +lower_subject="$(printf '%s' "$subject" | tr '[:upper:]' '[:lower:]')" + +# Allow merges and reverts to pass without extra checks +if [[ "$lower_subject" =~ ^merge\ || "$lower_subject" =~ ^revert\ ]]; then + exit 0 +fi + +# Allow automated release/version bump commits +if [[ "$lower_subject" =~ ^chore\(release\):\ || "$lower_subject" =~ ^bump: ]]; then + exit 0 +fi + +# Require a # anywhere in the message (subject or body) +if rg -N --pcre2 -q "#[0-9]{1,7}\b" "$msg_file" 2>/dev/null; then + exit 0 +fi + +# Fallback to grep if ripgrep is unavailable +if command -v grep >/dev/null 2>&1 && grep -Eq "#[0-9]{1,7}(\b|$)" "$msg_file"; then + exit 0 +fi + +echo "[issue-ref] Warning: no GitHub issue reference found (e.g., 'refs #123' or 'closes #123')." >&2 +echo "Include '#' in the message body or footer when possible." >&2 +exit 0