chore(hooks): enforce conventional commits and the branch policy

commitlint has been sitting in the repo wired to nothing. Hook it up: commit-msg lints the
message, pre-commit formats staged files and blocks direct commits to main,
pre-push blocks pushing it.

The test suite deliberately does not run in pre-commit. It runs in checks.yml on
every PR and locally via `make preflight`, and a full `turbo run test` on every
commit would tax exactly the fast checkpoint commits this repo lives on while
catching nothing the PR gate would not.

A hook is only ever a courtesy — it can be skipped with --no-verify — so CI runs
the same rules over a PR's commits.
This commit is contained in:
Nayan
2026-07-25 11:58:00 +05:30
parent a884e72f66
commit c7da399cf3
4 changed files with 67 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
# Conventional Commits, enforced. commitlint.config.ts has been in this repo
# since the beginning wired to nothing, which is why the log is a wall of
# "checkpoint". checks.yml runs the same rule over a PR's commits, because a
# hook is only ever a courtesy — it can be skipped with --no-verify.
pnpm commitlint --edit "$1"
+41
View File
@@ -0,0 +1,41 @@
#!/bin/sh
# Block direct commits to main — branch first, then open a PR. This and the
# pre-push guard are the only main protection there is until the repo has a
# remote with branch protection turned on (see .github/README.md).
branch="$(git rev-parse --abbrev-ref HEAD)"
if [ "$branch" = "main" ]; then
echo "✖ Direct commits to 'main' are blocked — branch first: git switch -c <name>"
exit 1
fi
# The test suite deliberately does NOT run here. It runs in checks.yml on every
# PR, and locally via `make preflight` before you open one. A full `turbo run
# test` on every commit taxes exactly the fast checkpoint commits this repo
# lives on, and catches nothing that the PR gate would not.
# ultracite
# Check if there are any staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)
if [ -z "$STAGED_FILES" ]; then
echo "No staged files to format"
exit 0
fi
# Run formatter, capturing the exit code so we can still re-stage and report
FORMAT_EXIT_CODE=0
pnpm dlx ultracite fix || FORMAT_EXIT_CODE=$?
# Re-stage files that were already staged
echo "$STAGED_FILES" | while IFS= read -r file; do
if [ -f "$file" ]; then
git add -- "$file"
fi
done
if [ $FORMAT_EXIT_CODE -ne 0 ]; then
echo "Ultracite found issues that could not be auto-fixed."
exit $FORMAT_EXIT_CODE
fi
echo "✨ Files formatted by Ultracite"
# ultracite end
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
# Block pushing to main — open a PR from a branch instead.
#
# git feeds pre-push the refs being pushed on stdin, one per line:
# <local_ref> <local_sha> <remote_ref> <remote_sha>
while read -r _local_ref _local_sha remote_ref _remote_sha; do
case "$remote_ref" in
refs/heads/main)
echo "✖ Pushing to 'main' is blocked — open a PR from your branch."
exit 1
;;
esac
done
+7
View File
@@ -0,0 +1,7 @@
import type { UserConfig } from "@commitlint/types";
const config: UserConfig = {
extends: ["@commitlint/config-conventional"],
};
export default config;