mirror of
https://github.com/C4illin/ConvertX.git
synced 2026-11-11 16:50:57 +01:00
Add AI indicator pre-commit guard
This commit is contained in:
1 parent
3cc5c5371e
commit
6f7cb19373
5 files changed
+479
No files matched your search
@@ -0,0 +1,114 @@
|
||||
# AI-Blocking Git Hooks
|
||||
|
||||
Git hooks that prevent AI indicators from being committed to your repository.
|
||||
|
||||
## What These Hooks Block
|
||||
|
||||
The hooks scan for AI references including:
|
||||
|
||||
- AI tool names (Claude, Cursor, Copilot, ChatGPT, Gemini, etc.)
|
||||
- AI-related phrases ("ai-generated", "ai-assisted", "generated with", etc.)
|
||||
- Model names (GPT-4, Claude Sonnet, Claude Code, etc.)
|
||||
- Company names (OpenAI, Anthropic, etc.)
|
||||
- Co-authorship markers (Co-Authored-By: Claude, noreply@anthropic.com, etc.)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install [pre-commit](https://pre-commit.com/):
|
||||
|
||||
```bash
|
||||
pip install pre-commit
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Single Repository
|
||||
|
||||
1. Navigate to your repository:
|
||||
|
||||
```bash
|
||||
cd /path/to/your/repo
|
||||
```
|
||||
|
||||
2. Create or update `.pre-commit-config.yaml`:
|
||||
|
||||
```yaml
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: ai-pre-commit
|
||||
name: Block AI indicators in staged files
|
||||
entry: python3 .config/ai-hooks/ai_guard.py pre-commit
|
||||
language: system
|
||||
pass_filenames: false
|
||||
stages: [pre-commit]
|
||||
- id: ai-commit-msg
|
||||
name: Block AI indicators in commit messages
|
||||
entry: python3 .config/ai-hooks/ai_guard.py commit-msg
|
||||
language: system
|
||||
stages: [commit-msg]
|
||||
```
|
||||
|
||||
3. Install the hooks:
|
||||
|
||||
```bash
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### commit-msg Hook
|
||||
|
||||
- Runs when you create a commit
|
||||
- Scans the commit message for AI indicators
|
||||
- Blocks the commit if any indicators are found
|
||||
|
||||
### pre-commit Hook
|
||||
|
||||
- Runs before the commit is created
|
||||
- Scans all staged files for AI indicators
|
||||
- Blocks the commit if any indicators are found in the code
|
||||
|
||||
## Uninstallation
|
||||
|
||||
To remove the hooks from a repository:
|
||||
|
||||
```bash
|
||||
cd /path/to/your/repo
|
||||
rm .git/hooks/commit-msg .git/hooks/pre-commit .git/hooks/ai_guard.py
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
To verify the hooks are working:
|
||||
|
||||
1. Create a test file with AI indicators:
|
||||
|
||||
```bash
|
||||
echo "Generated with Claude Code" > test.txt
|
||||
git add test.txt
|
||||
git commit -m "test"
|
||||
```
|
||||
|
||||
2. You should see an error message blocking the commit
|
||||
|
||||
3. Clean up:
|
||||
```bash
|
||||
git reset HEAD test.txt
|
||||
rm test.txt
|
||||
```
|
||||
|
||||
## Bypassing Hooks When Needed
|
||||
|
||||
In rare cases where you need to commit content with AI indicators, you can bypass the hooks using the `--no-verify` flag:
|
||||
|
||||
```bash
|
||||
git commit --no-verify -m "your commit message"
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- These hooks are **repository-specific** and must be installed in each repo where you want them active
|
||||
- Git hooks are not tracked by git itself (they live in `.git/hooks/`)
|
||||
- If you have existing hooks, the installer will create `.backup` copies
|
||||
- All pattern matching is case-insensitive
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Python wrapper for AI indicator guard hooks.
|
||||
Calls the appropriate bash script based on the hook type.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: ai_guard.py <pre-commit|commit-msg>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
hook_type = sys.argv[1]
|
||||
|
||||
# Get the directory where this script is located
|
||||
script_dir = Path(__file__).parent
|
||||
|
||||
# Determine which hook script to call
|
||||
if hook_type == "pre-commit":
|
||||
hook_script = script_dir / "pre-commit"
|
||||
elif hook_type == "commit-msg":
|
||||
hook_script = script_dir / "commit-msg"
|
||||
# For commit-msg, we need to pass the commit message file
|
||||
if len(sys.argv) < 3:
|
||||
print("Error: commit-msg hook requires message file argument", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
commit_msg_file = sys.argv[2]
|
||||
else:
|
||||
print(f"Unknown hook type: {hook_type}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not hook_script.exists():
|
||||
print(f"Hook script not found: {hook_script}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Call the hook script
|
||||
try:
|
||||
if hook_type == "commit-msg":
|
||||
result = subprocess.run(
|
||||
[str(hook_script), commit_msg_file],
|
||||
check=False
|
||||
)
|
||||
else:
|
||||
result = subprocess.run(
|
||||
[str(hook_script)],
|
||||
check=False
|
||||
)
|
||||
sys.exit(result.returncode)
|
||||
except Exception as e:
|
||||
print(f"Error running hook: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Guard commit messages against explicit AI usage indicators.
|
||||
# The script rejects messages containing phrases that suggest the text
|
||||
# was generated by an AI assistant or tool (as defined in onboarding docs).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MSG_FILE=${1:?}
|
||||
|
||||
# Patterns to block; all matches are case-insensitive (grep -i).
|
||||
declare -a PATTERNS=(
|
||||
"generated by cursor"
|
||||
"cursor ai"
|
||||
"cursor wrote this"
|
||||
"cursor suggestion"
|
||||
"generated by claude"
|
||||
"claude wrote this"
|
||||
"claude ai"
|
||||
"claude opus"
|
||||
"claude sonnet"
|
||||
"claude haiku"
|
||||
"claude code"
|
||||
"generated with claude code"
|
||||
"generated with \\[claude code\\]"
|
||||
"co-authored-by: claude"
|
||||
"noreply@anthropic\\.com"
|
||||
"generated by gemini"
|
||||
"gemini wrote this"
|
||||
"google gemini"
|
||||
"gemini ultra"
|
||||
"gemini pro"
|
||||
"gemini nano"
|
||||
"generated by bard"
|
||||
"bard wrote this"
|
||||
"google bard"
|
||||
"as an ai assistant"
|
||||
"as an ai"
|
||||
"ai assistant"
|
||||
"ai suggestion"
|
||||
"ai suggestions"
|
||||
"ai-generated"
|
||||
"ai generated"
|
||||
"ai-assisted"
|
||||
"ai assisted"
|
||||
"ai-powered"
|
||||
"ai powered"
|
||||
"powered by ai"
|
||||
"with ai assistance"
|
||||
"with the help of ai"
|
||||
"assisted by ai"
|
||||
"written with ai"
|
||||
"written by ai"
|
||||
"generated with ai"
|
||||
"generated via ai"
|
||||
"created with ai"
|
||||
"created by ai"
|
||||
"authored by ai"
|
||||
"co-authored by ai"
|
||||
"ai help"
|
||||
"ai helped"
|
||||
"ai copilot"
|
||||
"ai co-pilot"
|
||||
"chatgpt"
|
||||
"gpt-4"
|
||||
"gpt4"
|
||||
"gpt-3"
|
||||
"gpt3"
|
||||
"openai"
|
||||
"anthropic"
|
||||
"google ai"
|
||||
"google deepmind"
|
||||
"deepmind"
|
||||
"perplexity"
|
||||
"perplexity ai"
|
||||
"tabnine"
|
||||
"tabnine ai"
|
||||
"replit ghostwriter"
|
||||
"ghostwriter ai"
|
||||
"aws codewhisperer"
|
||||
"amazon codewhisperer"
|
||||
"code whisperer"
|
||||
"codewhisperer"
|
||||
"github copilot"
|
||||
"copilot"
|
||||
"amazon q"
|
||||
"amazon q developer"
|
||||
"meta ai"
|
||||
"deepseek"
|
||||
"phind"
|
||||
"blackbox ai"
|
||||
"magic ai"
|
||||
"ai model"
|
||||
"llm"
|
||||
"large language model"
|
||||
"language model"
|
||||
"\\[ai\\]"
|
||||
"\\[ai-generated\\]"
|
||||
"\\[cursor\\]"
|
||||
"\\[claude\\]"
|
||||
"\\[gemini\\]"
|
||||
"\\[copilot\\]"
|
||||
"\\[chatgpt\\]"
|
||||
"\\[llm\\]"
|
||||
)
|
||||
|
||||
for pattern in "${PATTERNS[@]}"; do
|
||||
if LC_ALL=C grep -qiE "$pattern" "$MSG_FILE"; then
|
||||
cat >&2 <<EOF
|
||||
✖ Commit message blocked: found AI indicator "$pattern".
|
||||
|
||||
Please rewrite the message without explicitly referencing AI tools or prompts.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
Executable
+171
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Block commits when staged files contain explicit AI usage indicators.
|
||||
# Mirrors the onboarding guidance so reviewers remain blinded to AI tooling.
|
||||
# Markdown (.md) files are explicitly scanned to prevent AI flag text in docs.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
declare -a PATTERNS=(
|
||||
"generated by cursor"
|
||||
"cursor ai"
|
||||
"cursor wrote this"
|
||||
"cursor suggestion"
|
||||
"generated by claude"
|
||||
"claude wrote this"
|
||||
"claude ai"
|
||||
"claude opus"
|
||||
"claude sonnet"
|
||||
"claude haiku"
|
||||
"claude code"
|
||||
"generated with claude code"
|
||||
"generated with \\[claude code\\]"
|
||||
"co-authored-by: claude"
|
||||
"noreply@anthropic\\.com"
|
||||
"generated by gemini"
|
||||
"gemini wrote this"
|
||||
"google gemini"
|
||||
"gemini ultra"
|
||||
"gemini pro"
|
||||
"gemini nano"
|
||||
"generated by bard"
|
||||
"bard wrote this"
|
||||
"google bard"
|
||||
"as an ai assistant"
|
||||
"as an ai"
|
||||
"ai assistant"
|
||||
"ai suggestion"
|
||||
"ai suggestions"
|
||||
"ai-generated"
|
||||
"ai generated"
|
||||
"ai-assisted"
|
||||
"ai assisted"
|
||||
"ai-powered"
|
||||
"ai powered"
|
||||
"powered by ai"
|
||||
"with ai assistance"
|
||||
"with the help of ai"
|
||||
"assisted by ai"
|
||||
"written with ai"
|
||||
"written by ai"
|
||||
"generated with ai"
|
||||
"generated via ai"
|
||||
"created with ai"
|
||||
"created by ai"
|
||||
"authored by ai"
|
||||
"co-authored by ai"
|
||||
"ai help"
|
||||
"ai helped"
|
||||
"ai copilot"
|
||||
"ai co-pilot"
|
||||
"chatgpt"
|
||||
"gpt-4"
|
||||
"gpt4"
|
||||
"gpt-3"
|
||||
"gpt3"
|
||||
"openai"
|
||||
"anthropic"
|
||||
"google ai"
|
||||
"google deepmind"
|
||||
"deepmind"
|
||||
"perplexity"
|
||||
"perplexity ai"
|
||||
"tabnine"
|
||||
"tabnine ai"
|
||||
"replit ghostwriter"
|
||||
"ghostwriter ai"
|
||||
"aws codewhisperer"
|
||||
"amazon codewhisperer"
|
||||
"code whisperer"
|
||||
"codewhisperer"
|
||||
"github copilot"
|
||||
"copilot"
|
||||
"amazon q"
|
||||
"amazon q developer"
|
||||
"meta ai"
|
||||
"deepseek"
|
||||
"phind"
|
||||
"blackbox ai"
|
||||
"magic ai"
|
||||
"ai model"
|
||||
"llm"
|
||||
"large language model"
|
||||
"language model"
|
||||
"\\[ai\\]"
|
||||
"\\[ai-generated\\]"
|
||||
"\\[cursor\\]"
|
||||
"\\[claude\\]"
|
||||
"\\[gemini\\]"
|
||||
"\\[copilot\\]"
|
||||
"\\[chatgpt\\]"
|
||||
"\\[llm\\]"
|
||||
)
|
||||
|
||||
PATTERN_REGEX=$(printf '%s|' "${PATTERNS[@]}")
|
||||
PATTERN_REGEX=${PATTERN_REGEX%|}
|
||||
|
||||
# Build a newline-separated list of binary file paths (bash 3.2 compatible)
|
||||
BINARY_PATHS=""
|
||||
while IFS=$'\t' read -r added deleted path; do
|
||||
[[ -z "${path:-}" ]] && continue
|
||||
if [[ "$path" == *" -> "* ]]; then
|
||||
path=${path##* -> }
|
||||
fi
|
||||
if [[ "$added" == "-" || "$deleted" == "-" ]]; then
|
||||
BINARY_PATHS="${BINARY_PATHS}${path}"$'\n'
|
||||
fi
|
||||
done < <(git diff --cached --numstat --diff-filter=ACMRT || true)
|
||||
|
||||
report=""
|
||||
current_file=""
|
||||
last_report_file=""
|
||||
violations=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
"diff --git "*)
|
||||
current_file=""
|
||||
;;
|
||||
"+++ "*)
|
||||
current_file=${line#+++ }
|
||||
if [[ "$current_file" == b/* ]]; then
|
||||
current_file=${current_file#b/}
|
||||
elif [[ "$current_file" == a/* ]]; then
|
||||
current_file=${current_file#a/}
|
||||
fi
|
||||
if [[ "$current_file" == "/dev/null" ]]; then
|
||||
current_file=""
|
||||
fi
|
||||
;;
|
||||
"Binary files "*)
|
||||
current_file=""
|
||||
;;
|
||||
*)
|
||||
if [[ "$line" == +* && "$line" != "+++"* ]]; then
|
||||
[[ -z "$current_file" ]] && continue
|
||||
if [[ -n "$BINARY_PATHS" ]] && echo "$BINARY_PATHS" | grep -qxF "$current_file"; then
|
||||
continue
|
||||
fi
|
||||
content=${line:1}
|
||||
if [[ -n "$content" ]] && LC_ALL=C grep -qiE "$PATTERN_REGEX" <<<"$content"; then
|
||||
if [[ "$current_file" != "$last_report_file" ]]; then
|
||||
report+=$'\n'"• $current_file"
|
||||
last_report_file="$current_file"
|
||||
fi
|
||||
report+=$'\n'" ${line}"
|
||||
violations=1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done < <(git diff --cached --diff-filter=ACMRT --unified=0 --no-color)
|
||||
|
||||
if [[ $violations -eq 1 ]]; then
|
||||
cat >&2 <<EOF
|
||||
✖ Commit blocked: staged changes contain AI usage indicators in the current diff.
|
||||
Please remove or rephrase the highlighted additions before committing:$report
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,17 @@
|
||||
# Configure pre-commit to reuse the AI indicator guard scripts.
|
||||
# Both hooks run locally from .config/ai-hooks.
|
||||
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: ai-pre-commit
|
||||
name: Block AI indicators in staged files
|
||||
entry: python3 .config/git-hooks/ai_guard.py pre-commit
|
||||
language: system
|
||||
pass_filenames: false
|
||||
stages: [pre-commit]
|
||||
- id: ai-commit-msg
|
||||
name: Block AI indicators in commit messages
|
||||
entry: python3 .config/git-hooks/ai_guard.py commit-msg
|
||||
language: system
|
||||
stages: [commit-msg]
|
||||
Reference in new issue
Block a user