| name | validate-git-hygiene |
| description | Validate git commit messages, branch naming conventions, and check for sensitive files. Returns structured output with commit format validation, branch name compliance, and sensitive file detection (.env, credentials, .pem, .key). Used for git workflow validation and security checks. |
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