diff options
| author | Craig Jennings <c@cjennings.net> | 2026-04-19 17:14:54 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-04-19 17:14:54 -0500 |
| commit | 208a079f4230edd520f5aa92288ae48247340910 (patch) | |
| tree | 236db8e21cd7369c7d0b741c673524464ba9af36 /hooks/gh-pr-create-confirm.py | |
| parent | 4957c60c9ee985628ad59344e593d20a18ca8fdb (diff) | |
| download | rulesets-208a079f4230edd520f5aa92288ae48247340910.tar.gz rulesets-208a079f4230edd520f5aa92288ae48247340910.zip | |
feat(hooks): shared _common.py helpers + systemMessage AI-attribution warning
Consolidates stdin-parse and response-emit across the two confirm hooks
into `hooks/_common.py` (stdlib-only, sibling symlinked alongside the
hooks it serves). Net ~28 lines less duplication.
Adds a `systemMessage` banner alongside the confirmation modal when the
commit message or PR title/body contains AI-attribution patterns:
- Co-Authored-By: Claude|Anthropic|GPT|AI trailers
- 🤖 robot emoji
- "Generated with Claude Code" / similar footers
- "Created with …" / "Assisted by …" variants
Scanning targets structural leak patterns only — bare mentions of
"Claude" or "Anthropic" in diff context don't fire, so discussing the
tools themselves in a commit message doesn't false-positive.
Clean-room synthesis from GowayLee/cchooks (MIT) — specifically, the
systemMessage-alongside-reason pattern and event-aware stdin helpers.
Diffstat (limited to 'hooks/gh-pr-create-confirm.py')
| -rwxr-xr-x | hooks/gh-pr-create-confirm.py | 27 |
1 files changed, 13 insertions, 14 deletions
diff --git a/hooks/gh-pr-create-confirm.py b/hooks/gh-pr-create-confirm.py index b983352..e3c2f13 100755 --- a/hooks/gh-pr-create-confirm.py +++ b/hooks/gh-pr-create-confirm.py @@ -24,20 +24,17 @@ Wire in ~/.claude/settings.json alongside git-commit-confirm.py: } """ -import json import re import sys +from _common import read_payload, respond_ask, scan_attribution + MAX_BODY_LINES = 20 def main() -> int: - try: - payload = json.loads(sys.stdin.read()) - except (json.JSONDecodeError, ValueError): - return 0 - + payload = read_payload() if payload.get("tool_name") != "Bash": return 0 @@ -48,14 +45,16 @@ def main() -> int: fields = parse_pr_create(cmd) reason = format_pr_confirmation(fields) - output = { - "hookSpecificOutput": { - "hookEventName": "PreToolUse", - "permissionDecision": "ask", - "permissionDecisionReason": reason, - } - } - print(json.dumps(output)) + # Scan both title and body — PRs leak attribution in either slot. + scan_text = "\n".join(filter(None, [fields.get("title"), fields.get("body")])) + hits = scan_attribution(scan_text) + system_message = ( + f"WARNING — PR title/body contains AI-attribution patterns: " + f"{'; '.join(hits)}. Policy forbids AI credit in PRs." + if hits else None + ) + + respond_ask(reason, system_message=system_message) return 0 |
