#!/usr/bin/env bash
# enkinex commit-msg hook — GENERATED from enkinex-aiops githooks/commit-msg.
# Do not edit here; change the source and run `just sync-opencode`.
#
# Enforces the locked commit grammar for every author — agent or human, and
# every harness. The same rules are stated in AGENTS.md for the model to
# follow; this hook is what makes them true when it does not.
set -euo pipefail

TYPES='feat|fix|refactor|docs|chore|test|infra|proj'

fail() {
    echo "enkinex commit-msg: $*" >&2
    exit 1
}

msg_file="$1"
# Drop comment lines and the `commit --verbose` diff section.
msg="$(sed -e '/^#/d' -e '/^diff --git /,$d' "$msg_file")"
subject="$(printf '%s\n' "$msg" | sed -e '/^[[:space:]]*$/d' | head -n1)"

# Machine-generated subjects that git itself owns are exempt.
case "$subject" in
    "Merge "*|"Revert "*|"fixup! "*|"squash! "*|"amend! "*) exit 0 ;;
esac

[ -n "$subject" ] || fail "empty commit message."

if ! printf '%s' "$subject" | grep -Eq "^(${TYPES})(\([a-z0-9][a-z0-9-]*\))?: .+"; then
    fail "subject must be '<type>: <imperative>'.
  type must be one of: ${TYPES//|/, }
  got: $subject"
fi

# A scope is optional and names a module inside this repo. The repo name is
# not a scope: these are separate repositories, so the repo is already implied
# by the history the commit lands in. Package-name scopes are a monorepo
# device and do not transfer.
scope="$(printf '%s' "$subject" | sed -nE 's/^[a-z]+\(([^)]*)\).*/\1/p')"
if [ -n "$scope" ]; then
    repo="$(basename "$(git rev-parse --show-toplevel)")"
    if [ "$scope" = "$repo" ] || [ "$scope" = "${repo#enkinex-}" ]; then
        fail "scope '($scope)' repeats the repository name.
  Drop it, or name the module this touches instead: '${subject%%(*}(<module>): …'
  got: $subject"
    fi
fi

if [ "${#subject}" -gt 72 ]; then
    fail "subject is ${#subject} characters; the limit is 72.
  got: $subject"
fi

case "$subject" in
    *.) fail "subject must not end with a period.
  got: $subject" ;;
esac

if printf '%s\n' "$msg" | grep -Eqi '^(Closes|Fixes|Resolves):'; then
    fail "Closes:/Fixes:/Resolves: footers are not used.
  An issue is closed by hand after the squash merge, not as a side effect of a
  commit message (ADR-0006)."
fi

# Plan reference footer. `Refs:` carries a task ID — a project prefix plus the
# task's file number in the private enkinex-pm planning repo (AIOPS-10, MGR-16,
# PM-04) — and `No-Plan-Ref:` is the documented footer for a commit that
# advances no task, not a bypass.
#
# The ID is validated and the old `Refs: plan/<file>.md#<anchor>` path grammar
# is not accepted. That grammar named a directory the repos no longer have, so
# an unchecked footer pointed nowhere silently and permanently — the defect
# AIOPS-10 exists to close. The pattern is deliberately generic rather than an
# enumerated list of prefixes, so a project acquiring its first plan folder
# needs no change here.
#
# Only ONE line has to be a valid reference rather than every matching line: a
# commit body may legitimately discuss `Refs:` footers at the start of a line —
# this history has seven such commits — and rejecting those would make the hook
# refuse commits about the convention it enforces.
#
# History is untouched. This validates the message being written, never what is
# already committed, so pre-changeover footers stay readable and are not errors.
# `git commit --amend` on one is the single case where this refuses what it once
# accepted, and this org does not rewrite history.
TASK_ID='[A-Z][A-Z0-9]*-[0-9]+'
if printf '%s\n' "$msg" | grep -Eq "^Refs: *${TASK_ID}([,[:space:]]+${TASK_ID})*[[:space:]]*$"; then
    :
elif printf '%s\n' "$msg" | grep -Eq '^No-Plan-Ref: *[^[:space:]]'; then
    :
else
    found="$(printf '%s\n' "$msg" | grep -E '^(Refs|No-Plan-Ref):' | head -n1)"
    [ -n "$found" ] && fail "plan reference footer is not a task ID.
  got: $found
  Use 'Refs: <TASK-ID>' — a project prefix plus the task's file number in
  enkinex-pm, e.g. 'Refs: AIOPS-10'. Separate several with commas.
  The 'Refs: plan/<file>.md#<anchor>' path grammar was retired when planning
  moved to enkinex-pm: a path footer now resolves for nobody.
  Use 'No-Plan-Ref: <reason>' when the commit advances no task."
    fail "missing plan reference footer.
  Add 'Refs: <TASK-ID>' naming the task delivered, e.g. 'Refs: AIOPS-10',
  or 'No-Plan-Ref: <reason>' when the commit advances no task."
fi

exit 0
