diff --git a/CLAUDE.md b/CLAUDE.md index ab227d6..557f00b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,13 +43,33 @@ Universal development practices for all Svrnty/a-gent repositories. ## Commit Rules -All AI-authored commits MUST use: +### Allowed Authors +Only these emails are permitted for commits: +- `jp@svrnty.io` (Jean-Philippe Brule) +- `mathias@svrnty.io` (Mathias Beaulieu-Duncan) + +Configure with: `git config user.email jp@svrnty.io` + +### AI-Assisted Commits +All AI-authored commits MUST include: ``` -Co-Authored-By: Svrnty Inc. +Co-Authored-By: Svrnty Inc. ``` NEVER use third-party AI tool/company names in commits. +### Git Hooks Setup +Install lefthook to enforce commit standards: +```bash +brew install lefthook +lefthook install +``` + +The hooks automatically: +- Validate author email is in allowed list +- Append Co-Authored-By footer +- Warn on conventional commit format violations + ## PR Discipline - Clear, scoped commit messages @@ -73,7 +93,7 @@ Run before every commit: ## Cross-Repo Changes -When changes span multiple repos: -1. Commit to each repo separately -2. Reference related repo in commit message -3. Run tests in each affected repo before commit +- Each repo is standalone with its own build configuration +- Reference related repos in commit messages for cross-repo changes +- Coordinate multi-repo changes via matching branch names +- Test each repo independently before pushing diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..3f358b3 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,63 @@ +pre-commit: + parallel: true + commands: + check-author: + run: | + EMAIL=$(git config user.email) + ALLOWED="jp@svrnty.io mathias@svrnty.io" + for a in $ALLOWED; do + [ "$EMAIL" = "$a" ] && exit 0 + done + echo "BLOCKED: author email '$EMAIL' not in allowed list: $ALLOWED" + exit 1 + no-secrets: + run: | + BLOCKED=$(git diff --cached --name-only | grep -E '\.(env|pem|key)$|credentials\.json|id_rsa|id_ed25519' || true) + if [ -n "$BLOCKED" ]; then + echo "BLOCKED: refusing to commit sensitive files:" + echo "$BLOCKED" + exit 1 + fi + no-large-files: + run: | + LARGE=$(git diff --cached --name-only -z | xargs -0 -I{} sh -c 'if [ -f "{}" ]; then size=$(wc -c < "{}"); if [ "$size" -gt 5242880 ]; then echo "{} ($(( size / 1048576 ))MB)"; fi; fi' || true) + if [ -n "$LARGE" ]; then + echo "WARNING: large files staged (>5MB):" + echo "$LARGE" + fi + +commit-msg: + commands: + validate-message: + run: | + MSG=$(cat "{1}") + if echo "$MSG" | head -1 | grep -qE '^Merge '; then + exit 0 + fi + if ! echo "$MSG" | head -1 | grep -qE '^[a-z]+(\([a-zA-Z0-9_-]+\))?: .+'; then + echo "WARNING: commit message does not follow conventional format: type(scope): message" + fi + append-coauthor: + run: | + MSG=$(cat "{1}") + if ! echo "$MSG" | grep -qF 'Co-Authored-By: Svrnty Inc. '; then + printf '\n\nCo-Authored-By: Svrnty Inc. \n' >> "{1}" + fi + +pre-push: + commands: + protect-main: + run: | + BRANCH=$(git rev-parse --abbrev-ref HEAD) + if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then + echo "BLOCKED: direct push to $BRANCH is not allowed" + exit 1 + fi + check-behind-remote: + run: | + git fetch origin 2>/dev/null || true + BRANCH=$(git rev-parse --abbrev-ref HEAD) + BEHIND=$(git rev-list --count HEAD..origin/"$BRANCH" 2>/dev/null || echo 0) + if [ "$BEHIND" -gt 0 ]; then + echo "WARNING: local branch is $BEHIND commit(s) behind origin/$BRANCH" + fi