diff --git a/salesflow-saas/.claude/hooks/pre-commit.sh b/salesflow-saas/.claude/hooks/pre-commit.sh index e5d800cd..4af79b36 100755 --- a/salesflow-saas/.claude/hooks/pre-commit.sh +++ b/salesflow-saas/.claude/hooks/pre-commit.sh @@ -1,23 +1,133 @@ -#!/bin/bash +#!/usr/bin/env bash # Dealix Pre-Commit Hook -set -e -echo "Dealix Pre-Commit Checks..." +# Runs linting, secret detection, Arabic consistency, and affected tests. +set -euo pipefail -# Check for hardcoded secrets -if grep -rn "API_KEY\s*=\s*['\"][^'\"]*['\"]" backend/app/ --include="*.py" 2>/dev/null | grep -v config.py | grep -v example; then - echo "ERROR: Hardcoded API key found!" - exit 1 +ROOT_DIR="$(git rev-parse --show-toplevel)" +BACKEND_DIR="${ROOT_DIR}/backend" +STAGED_PY_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$' || true) + +echo "============================================" +echo " Dealix Pre-Commit Checks" +echo "============================================" + +# ── 1. Ruff Linter ───────────────���───────────── +echo "" +echo "[1/4] Running ruff linter..." + +if [ -n "${STAGED_PY_FILES}" ]; then + if command -v ruff &>/dev/null; then + LINT_FAILED=0 + echo "${STAGED_PY_FILES}" | xargs ruff check --select E,W,F,I --no-fix || LINT_FAILED=1 + if [ ${LINT_FAILED} -eq 1 ]; then + echo "FAIL: ruff found issues. Fix them before committing." + echo " Run: ruff check --fix backend/" + exit 1 + fi + echo "PASS: ruff checks passed." + else + echo "WARN: ruff not installed. Skipping lint check." + echo " Install: pip install ruff" + fi +else + echo "SKIP: No Python files staged." fi -# Check .env not staged -if git diff --cached --name-only | grep -q "\.env$"; then - echo "ERROR: .env file staged!" - exit 1 +# ── 2. Hardcoded Secrets Detection ───────────── +echo "" +echo "[2/4] Checking for hardcoded secrets..." + +SECRETS_FOUND=0 + +if [ -n "${STAGED_PY_FILES}" ]; then + MATCHES=$(echo "${STAGED_PY_FILES}" | xargs grep -n -E "(API_KEY|SECRET_KEY|PASSWORD|PRIVATE_KEY|ACCESS_TOKEN)\s*=\s*['"][^'"]{8,}" 2>/dev/null | grep -v "os\.environ\|get_settings\|config\.\|settings\.\|# example\|# test\|# noqa" || true) + + if [ -n "${MATCHES}" ]; then + echo "CRITICAL: Possible hardcoded secrets found:" + echo "${MATCHES}" + SECRETS_FOUND=1 + fi + + BEARER_MATCHES=$(echo "${STAGED_PY_FILES}" | xargs grep -n -E "Bearer\s+[A-Za-z0-9_\-]{20,}" 2>/dev/null | grep -v "settings\.\|config\.\|Authorization.*Bearer.*{" || true) + + if [ -n "${BEARER_MATCHES}" ]; then + echo "CRITICAL: Possible hardcoded Bearer tokens:" + echo "${BEARER_MATCHES}" + SECRETS_FOUND=1 + fi + + if [ ${SECRETS_FOUND} -eq 1 ]; then + echo "" + echo "FAIL: Remove hardcoded secrets. Use environment variables via get_settings()." + exit 1 + fi + + echo "PASS: No hardcoded secrets detected." +else + echo "SKIP: No Python files staged." fi -# Run linter -if command -v ruff &> /dev/null; then - ruff check backend/app/ --fix --quiet 2>/dev/null || true +# ── 3. Arabic String Consistency ──────────────── +echo "" +echo "[3/4] Checking Arabic string consistency..." + +STAGED_FRONTEND_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(tsx?|jsx?)$' || true) + +if [ -n "${STAGED_FRONTEND_FILES}" ]; then + UNTRANSLATED=$(echo "${STAGED_FRONTEND_FILES}" | xargs grep -n -E "TODO.*translat|FIXME.*arabic|FIXME.*rtl" 2>/dev/null || true) + + if [ -n "${UNTRANSLATED}" ]; then + echo "WARN: Found untranslated string markers:" + echo "${UNTRANSLATED}" + echo " These should be resolved before release." + else + echo "PASS: No untranslated string markers found." + fi +else + echo "SKIP: No frontend files staged." fi -echo "Pre-commit checks passed." +if [ -n "${STAGED_PY_FILES}" ]; then + MISSING_ARABIC=$(echo "${STAGED_PY_FILES}" | xargs grep -n -E "detail=\"[A-Z][a-z]|message=\"[A-Z][a-z]" 2>/dev/null | grep -v "# en-only\|# internal\|HTTPException\|logger\." || true) + + if [ -n "${MISSING_ARABIC}" ]; then + echo "WARN: Possible English-only user-facing strings in Python:" + echo "${MISSING_ARABIC}" + echo " Consider adding Arabic translations." + fi +fi + +# ── 4. Run Affected Tests ────────────────────── +echo "" +echo "[4/4] Running affected tests..." + +if [ -n "${STAGED_PY_FILES}" ]; then + TEST_FILES="" + for PY_FILE in ${STAGED_PY_FILES}; do + BASENAME=$(basename "${PY_FILE}" .py) + FOUND_TESTS=$(find "${BACKEND_DIR}/tests" -name "test_${BASENAME}.py" 2>/dev/null || true) + if [ -n "${FOUND_TESTS}" ]; then + TEST_FILES="${TEST_FILES} ${FOUND_TESTS}" + fi + done + + if [ -n "${TEST_FILES}" ]; then + echo "Running tests:${TEST_FILES}" + cd "${BACKEND_DIR}" + if ! pytest ${TEST_FILES} -x -q --tb=short 2>/dev/null; then + echo "" + echo "FAIL: Some tests failed. Fix them before committing." + exit 1 + fi + echo "PASS: All affected tests passed." + else + echo "SKIP: No matching test files found for staged changes." + fi +else + echo "SKIP: No Python files staged." +fi + +echo "" +echo "============================================" +echo " All pre-commit checks passed" +echo "============================================" diff --git a/salesflow-saas/.claude/hooks/pre-push.sh b/salesflow-saas/.claude/hooks/pre-push.sh index a13acb7a..76dd6e04 100755 --- a/salesflow-saas/.claude/hooks/pre-push.sh +++ b/salesflow-saas/.claude/hooks/pre-push.sh @@ -1,13 +1,91 @@ -#!/bin/bash +#!/usr/bin/env bash # Dealix Pre-Push Hook -set -e -echo "Dealix Pre-Push Checks..." +# Runs full test suite, checks migrations, and verifies no .env files are staged. +set -euo pipefail -# Run tests -cd backend && python -m pytest -x -q --tb=short 2>/dev/null || { - echo "ERROR: Tests failed!" +ROOT_DIR="$(git rev-parse --show-toplevel)" +BACKEND_DIR="${ROOT_DIR}/backend" + +echo "============================================" +echo " Dealix Pre-Push Checks" +echo "============================================" + +# ── 1. Full Test Suite ────────────────────────── +echo "" +echo "[1/3] Running full test suite..." + +if [ -d "${BACKEND_DIR}/tests" ]; then + cd "${BACKEND_DIR}" + if ! pytest -x -q --tb=short 2>&1; then + echo "" + echo "FAIL: Test suite failed. Fix all tests before pushing." + exit 1 + fi + echo "PASS: Full test suite passed." + cd "${ROOT_DIR}" +else + echo "WARN: No tests directory found at ${BACKEND_DIR}/tests" +fi + +# ── 2. Uncommitted Migrations ────────────────── +echo "" +echo "[2/3] Checking for uncommitted migrations..." + +UNTRACKED_MIGRATIONS=$(git ls-files --others --exclude-standard "${BACKEND_DIR}/alembic/versions/" 2>/dev/null | grep '\.py$' || true) +MODIFIED_MIGRATIONS=$(git diff --name-only "${BACKEND_DIR}/alembic/versions/" 2>/dev/null | grep '\.py$' || true) + +if [ -n "${UNTRACKED_MIGRATIONS}" ]; then + echo "FAIL: Found untracked migration files:" + echo "${UNTRACKED_MIGRATIONS}" + echo " Commit these migrations before pushing." exit 1 -} -cd .. +fi -echo "Pre-push checks passed." +if [ -n "${MODIFIED_MIGRATIONS}" ]; then + echo "WARN: Found modified but uncommitted migration files:" + echo "${MODIFIED_MIGRATIONS}" + echo " Consider committing these changes." +fi + +MODEL_CHANGES=$(git diff HEAD --name-only "${BACKEND_DIR}/app/models/" 2>/dev/null | grep '\.py$' || true) +if [ -n "${MODEL_CHANGES}" ]; then + MIGRATION_CHANGES=$(git diff HEAD --name-only "${BACKEND_DIR}/alembic/versions/" 2>/dev/null | grep '\.py$' || true) + if [ -z "${MIGRATION_CHANGES}" ]; then + echo "WARN: Model files changed but no new migrations detected:" + echo "${MODEL_CHANGES}" + echo " Run: cd backend && alembic revision --autogenerate -m 'description'" + fi +fi + +echo "PASS: Migration check complete." + +# ── 3. No .env Files ─────────────────────────── +echo "" +echo "[3/3] Verifying no .env files are being pushed..." + +TRACKED_ENV=$(git ls-files | grep -E '\.env$|\.env\.local$|\.env\.production$' | grep -v '\.env\.example$' || true) +if [ -n "${TRACKED_ENV}" ]; then + echo "CRITICAL: .env files are tracked in the repository:" + echo "${TRACKED_ENV}" + echo "" + echo "FAIL: Never push .env files to the repository." + echo " Remove them: git rm --cached " + echo " Add to .gitignore if missing" + exit 1 +fi + +STAGED_ENV=$(git diff --cached --name-only | grep -E '\.env$|\.env\.' | grep -v '\.env\.example$' || true) +if [ -n "${STAGED_ENV}" ]; then + echo "CRITICAL: .env files staged for commit:" + echo "${STAGED_ENV}" + echo "" + echo "FAIL: Unstage .env files: git reset HEAD " + exit 1 +fi + +echo "PASS: No .env files in push." + +echo "" +echo "============================================" +echo " All pre-push checks passed" +echo "============================================"