Validate Git Hygiene
Purpose
Validate git repository hygiene including commit message format, branch naming conventions, and sensitive file detection.
When to Use
- Conductor Phase 2 (Implementation) - Branch validation
- Conductor Phase 4 (PR Creation) - Commit validation
- Pre-commit hooks validation
- Security audits (sensitive file detection)
- Git workflow compliance checks
Validation Checks
- Commit Messages: Conventional Commits format
- Branch Naming: Standard patterns (feat/, fix/, etc.)
- Sensitive Files: Detection of credentials, keys, etc.
Instructions
Step 1: Validate Branch Name
echo "→ Validating branch name..."
# Get current branch
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
# Check against standard patterns
VALID_PATTERNS=(
"^feat/.*"
"^fix/.*"
"^docs/.*"
"^chore/.*"
"^refactor/.*"
"^test/.*"
"^hotfix/.*"
"^release/.*"
"^main$"
"^master$"
"^develop$"
"^development$"
"^claude/.*"
)
BRANCH_VALID="false"
for pattern in "${VALID_PATTERNS[@]}"; do
if echo "$CURRENT_BRANCH" | grep -qE "$pattern"; then
BRANCH_VALID="true"
break
fi
done
if [ "$BRANCH_VALID" = "true" ]; then
echo "✅ Branch name valid: $CURRENT_BRANCH"
BRANCH_STATUS="valid"
else
echo "❌ Branch name invalid: $CURRENT_BRANCH"
echo " Expected: feat/*, fix/*, docs/*, chore/*, etc."
BRANCH_STATUS="invalid"
fi
Step 2: Validate Recent Commits
echo ""
echo "→ Validating recent commit messages..."
# Get last 10 commits (or since main/develop)
COMMITS=$(git log --oneline --no-merges -10 --format="%H %s")
# Conventional commit pattern
COMMIT_PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?!?: .+"
INVALID_COMMITS=()
VALID_COMMITS=0
while IFS= read -r commit; do
HASH=$(echo "$commit" | cut -d' ' -f1)
MSG=$(echo "$commit" | cut -d' ' -f2-)
if echo "$MSG" | grep -qE "$COMMIT_PATTERN"; then
((VALID_COMMITS++))
else
INVALID_COMMITS+=("$HASH:$MSG")
fi
done <<< "$COMMITS"
TOTAL_COMMITS=$((VALID_COMMITS + ${#INVALID_COMMITS[@]}))
if [ ${#INVALID_COMMITS[@]} -eq 0 ]; then
echo "✅ All $VALID_COMMITS commit messages valid"
COMMITS_STATUS="valid"
else
echo "❌ ${#INVALID_COMMITS[@]} invalid commit messages found"
COMMITS_STATUS="invalid"
# Show first 3 invalid commits
for i in "${INVALID_COMMITS[@]:0:3}"; do
HASH=$(echo "$i" | cut -d: -f1)
MSG=$(echo "$i" | cut -d: -f2-)
echo " $HASH: $MSG"
done
fi
Step 3: Check for Sensitive Files
echo ""
echo "→ Checking for sensitive files..."
# Patterns for sensitive files
SENSITIVE_PATTERNS=(
"\.env$"
"\.env\..+"
"credentials\.json$"
"\.pem$"
"\.key$"
"\.p12$"
"id_rsa$"
"\.ssh/"
"secret"
"private.*\.key$"
"\.pfx$"
)
SENSITIVE_FILES=()
# Check tracked files
for pattern in "${SENSITIVE_PATTERNS[@]}"; do
while IFS= read -r file; do
if [ -n "$file" ]; then
SENSITIVE_FILES+=("$file")
fi
done < <(git ls-files | grep -E "$pattern")
done
# Check unstaged/untracked
for pattern in "${SENSITIVE_PATTERNS[@]}"; do
while IFS= read -r file; do
# Only files not in .gitignore
if [ -n "$file" ] && ! git check-ignore -q "$file" 2>/dev/null; then
SENSITIVE_FILES+=("$file (untracked)")
fi
done < <(git status --porcelain | awk '{print $2}' | grep -E "$pattern")
done
if [ ${#SENSITIVE_FILES[@]} -eq 0 ]; then
echo "✅ No sensitive files detected"
SENSITIVE_STATUS="clean"
else
echo "⚠️ ${#SENSITIVE_FILES[@]} potential sensitive files found:"
SENSITIVE_STATUS="found"
for file in "${SENSITIVE_FILES[@]:0:5}"; do
echo " - $file"
done
fi
Step 4: Determine Overall Status
# Aggregate results
if [ "$BRANCH_STATUS" = "valid" ] && [ "$COMMITS_STATUS" = "valid" ] && [ "$SENSITIVE_STATUS" = "clean" ]; then
OVERALL_STATUS="passing"
CAN_PROCEED="true"
STATUS="success"
elif [ "$SENSITIVE_STATUS" = "found" ]; then
OVERALL_STATUS="sensitive-files"
CAN_PROCEED="false"
STATUS="error"
DETAILS="Sensitive files detected - must be removed or added to .gitignore"
elif [ "$BRANCH_STATUS" = "invalid" ] || [ "$COMMITS_STATUS" = "invalid" ]; then
OVERALL_STATUS="format-issues"
CAN_PROCEED="true"
STATUS="warning"
DETAILS="Git hygiene issues detected - should be fixed but not blocking"
else
OVERALL_STATUS="passing"
CAN_PROCEED="true"
STATUS="success"
fi
Step 5: Return Structured Output
{
"status": "$STATUS",
"gitHygiene": {
"status": "$OVERALL_STATUS",
"branch": {
"name": "$CURRENT_BRANCH",
"valid": $([ "$BRANCH_STATUS" = "valid" ] && echo 'true' || echo 'false')
},
"commits": {
"total": $TOTAL_COMMITS,
"valid": $VALID_COMMITS,
"invalid": ${#INVALID_COMMITS[@]},
"invalidExamples": $(printf '%s\n' "${INVALID_COMMITS[@]:0:3}" | jq -R -s -c 'split("\n") | map(select(length > 0))')
},
"sensitiveFiles": {
"count": ${#SENSITIVE_FILES[@]},
"files": $(printf '%s\n' "${SENSITIVE_FILES[@]}" | jq -R -s -c 'split("\n") | map(select(length > 0))')
}
},
"canProceed": $CAN_PROCEED,
"details": "$DETAILS"
}
Output Format
All Checks Pass
{
"status": "success",
"gitHygiene": {
"status": "passing",
"branch": {
"name": "feat/add-user-profile",
"valid": true
},
"commits": {
"total": 10,
"valid": 10,
"invalid": 0,
"invalidExamples": []
},
"sensitiveFiles": {
"count": 0,
"files": []
}
},
"canProceed": true
}
Issues Found
{
"status": "warning",
"gitHygiene": {
"status": "format-issues",
"branch": {
"name": "update-settings",
"valid": false
},
"commits": {
"total": 10,
"valid": 7,
"invalid": 3,
"invalidExamples": [
"a1b2c3d:updated some stuff",
"e4f5g6h:WIP",
"i7j8k9l:fixed bug"
]
},
"sensitiveFiles": {
"count": 0,
"files": []
}
},
"canProceed": true,
"details": "Git hygiene issues detected - should be fixed but not blocking"
}
Sensitive Files Detected
{
"status": "error",
"gitHygiene": {
"status": "sensitive-files",
"branch": {
"name": "feat/api-integration",
"valid": true
},
"commits": {
"total": 5,
"valid": 5,
"invalid": 0,
"invalidExamples": []
},
"sensitiveFiles": {
"count": 2,
"files": [
".env.production",
"src/config/credentials.json"
]
}
},
"canProceed": false,
"details": "Sensitive files detected - must be removed or added to .gitignore"
}
Conventional Commit Format
Valid commit message format:
<type>(<scope>): <description>
Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert
Scope: optional (component, feature, etc.)
Description: imperative, lowercase, no period
Examples:
✅ feat(auth): add user login endpoint
✅ fix: resolve null pointer in settings
✅ docs(readme): update installation instructions
❌ Updated some files
❌ WIP
❌ Fixed bug
Branch Naming Conventions
Valid patterns:
feat/* - New features
fix/* - Bug fixes
docs/* - Documentation
chore/* - Maintenance
refactor/* - Code refactoring
test/* - Test additions
hotfix/* - Critical fixes
release/* - Release preparation
claude/* - AI-generated branches
Examples:
✅ feat/user-authentication
✅ fix/settings-crash
✅ claude/implement-validation-011ABC
❌ update-settings
❌ myfeature
Sensitive File Patterns
Detected patterns:
.env,.env.local,.env.productioncredentials.json,secrets.json*.pem,*.key,*.p12,*.pfxid_rsa,.ssh/files- Files containing "secret", "private"
Integration with Conductor
Phase 2: Branch Validation
Use `validate-git-hygiene` to check branch name:
- Expected: feat/*, fix/*, etc.
- If invalid: Warning but continue
Phase 4: Pre-PR Validation
Use `validate-git-hygiene` to check:
- Commit messages format
- No sensitive files
- If sensitive files: BLOCK - cannot proceed
Fixing Issues
Branch Name
# Rename current branch
git branch -m feat/descriptive-name
Commit Messages
# Amend last commit message
git commit --amend -m "feat: proper commit message"
# Interactive rebase to fix multiple commits
git rebase -i HEAD~5
Sensitive Files
# Remove from git (keep local)
git rm --cached .env
# Add to .gitignore
echo ".env*" >> .gitignore
git add .gitignore
git commit -m "chore: add .env to .gitignore"
Related Skills
create-feature-branch- Creates properly named branchescommit-with-validation- Commits with validationsecurity-pentest- Uses for sensitive file detection
Best Practices
- Use conventional commits - Enables auto-changelog, semantic versioning
- Meaningful branch names - Self-documenting workflow
- Never commit secrets - Use environment variables
- Check before push - Validate locally first
- Use .gitignore - Prevent accidental commits
Notes
- Sensitive files BLOCK (canProceed: false)
- Branch/commit format issues are warnings (canProceed: true)
- Checks last 10 commits or since main/develop
- Respects .gitignore for file detection