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 doc-hygiene: run: | STAGED=$(git diff --cached --name-only) # Check if code files are staged (not just docs) CODE_CHANGED=$(echo "$STAGED" | grep -vE '\.(md|txt|yml|yaml|json|toml|lock)$|^LICENSE$|^\.gitignore$' || true) if [ -z "$CODE_CHANGED" ]; then exit 0 fi # Warn if CHANGELOG.md is not being updated with code changes if ! echo "$STAGED" | grep -q '^CHANGELOG.md$'; then echo "WARNING: code changes staged without CHANGELOG.md update" echo " → Update CHANGELOG.md under [Unreleased] before committing" echo " → See root CLAUDE.md § Documentation Standards for format" fi # Warn if README.md is missing if [ ! -f "README.md" ]; then echo "WARNING: README.md is missing — every repo must have one" echo " → See root CLAUDE.md § README Requirements for structure" 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" echo " → Types: feat, fix, refactor, docs, test, chore, ci, perf" 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 post-commit: commands: register-repo: run: | REPO_NAME=$(basename "$(git rev-parse --show-toplevel)") ROOT_CLAUDE="$(git rev-parse --show-toplevel)/../CLAUDE.md" [ -f "$ROOT_CLAUDE" ] || exit 0 if grep -qF "| \`$REPO_NAME\`" "$ROOT_CLAUDE"; then exit 0 fi COMMIT=$(git rev-parse --short HEAD) DATE=$(date +%Y-%m-%d) TOTAL_LINE=$(grep -n '^\*\*Total:' "$ROOT_CLAUDE" | head -1 | cut -d: -f1) if [ -z "$TOTAL_LINE" ]; then exit 0 fi OLD_COUNT=$(sed -n "${TOTAL_LINE}p" "$ROOT_CLAUDE" | grep -oP '\d+') NEW_COUNT=$((OLD_COUNT + 1)) sed -i "${TOTAL_LINE}i| \`${REPO_NAME}\` | — | NEW REPO — registered ${DATE} (${COMMIT}). Update stack and purpose. |" "$ROOT_CLAUDE" NEW_TOTAL_LINE=$((TOTAL_LINE + 1)) sed -i "${NEW_TOTAL_LINE}s/Total: ${OLD_COUNT}/Total: ${NEW_COUNT}/" "$ROOT_CLAUDE" echo "REGISTRY: added '$REPO_NAME' to root CLAUDE.md (${DATE}, ${COMMIT})" bootstrap-siblings: run: | REPO_ROOT=$(git rev-parse --show-toplevel) HOOKS_DIR="$REPO_ROOT/../.svrnty-hooks" [ -d "$HOOKS_DIR" ] || exit 0 [ -f "$HOOKS_DIR/lefthook.yml" ] || exit 0 for sibling in "$REPO_ROOT"/../*/; do [ -d "$sibling/.git" ] || continue [ -f "$sibling/lefthook.yml" ] && continue SNAME=$(basename "$sibling") # Deploy lefthook cp "$HOOKS_DIR/lefthook.yml" "$sibling/lefthook.yml" # Deploy CLAUDE.md [ -f "$HOOKS_DIR/CLAUDE.md.template" ] && cp "$HOOKS_DIR/CLAUDE.md.template" "$sibling/CLAUDE.md" # Deploy governance docs [ -f "$HOOKS_DIR/LICENSE" ] && [ ! -f "$sibling/LICENSE" ] && cp "$HOOKS_DIR/LICENSE" "$sibling/LICENSE" [ -f "$HOOKS_DIR/CONTRIBUTING.md" ] && [ ! -f "$sibling/CONTRIBUTING.md" ] && cp "$HOOKS_DIR/CONTRIBUTING.md" "$sibling/CONTRIBUTING.md" [ -f "$HOOKS_DIR/SECURITY.md" ] && [ ! -f "$sibling/SECURITY.md" ] && cp "$HOOKS_DIR/SECURITY.md" "$sibling/SECURITY.md" [ -f "$HOOKS_DIR/CHANGELOG.md.template" ] && [ ! -f "$sibling/CHANGELOG.md" ] && cp "$HOOKS_DIR/CHANGELOG.md.template" "$sibling/CHANGELOG.md" # Install lefthook (cd "$sibling" && lefthook install 2>/dev/null) echo "BOOTSTRAP: installed lefthook + governance docs in '$SNAME'" done 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