- CLAUDE.md: repo-specific tech stack, commands, deps (points to root) - LICENSE: MIT 2026 svrnty (standardized) - CONTRIBUTING.md: unified workflow, correct co-author email - SECURITY.md: unified vulnerability reporting policy - CHANGELOG.md: Keep a Changelog template (if new) - lefthook.yml: added doc-hygiene hook, improved bootstrap Co-Authored-By: Svrnty Inc. <jp@svrnty.io>
130 lines
5.5 KiB
YAML
130 lines
5.5 KiB
YAML
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. <jp@svrnty.io>'; then
|
|
printf '\n\nCo-Authored-By: Svrnty Inc. <jp@svrnty.io>\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
|