SLOPSHOPPER

jr-no-ai-trailer

Stops commits and pull requests that carry an AI co-author line or a 'Generated with' footer, so your history stays yours.

newguard
A shopper browsing a rack in a slop shop
README

jr-no-ai-trailer

Keeps AI attribution out of your git history. A git commit whose message carries a Co-Authored-By: line naming an AI (Claude, an Anthropic address, GPT, Copilot, Gemini and so on) is stopped before it runs, and Claude is told to remove the line. A gh pr create or gh pr edit with a "Generated with Claude Code" footer is stopped the same way.

Human co-authors pass untouched.

Settings (/config)

OptionDefault
Allow AI co-author linesoffLet Co-Authored-By lines that name an AI through
Block 'Generated with' footersonAlso stop commits and pull requests whose message says it was generated with an AI tool

Install

/plugin install jr-no-ai-trailer --marketplace jrlprost/claude-code-mods

Access

Reads the command of each Bash call. Writes nothing, runs nothing, sends nothing over the network.

Source 2 files
hooks/register.ts 14 lines
1import type { Register } from 'claude-code'
2
3import { judge } from './trailer'
4
5export const register: Register = (on, options) => {
6  const allowTrailer = options.allowTrailer === true
7  const blockFooter = options.blockFooter !== false
8
9  on('tool.call', { tool: 'Bash' }, ($, e, next) => {
10    const verdict = judge(e.command, { allowTrailer, blockFooter })
11    return verdict ?? next(e)
12  }).catch(($, e, next) => (next.called ? next(e) : { deny: 'jr-no-ai-trailer: the check failed, so the commit was held back.' }))
13}
14
hooks/trailer.ts 43 lines
1// What counts as an AI attribution in a commit or a pull request.
2
3const AI_NAMES = /(claude|anthropic|openai|chatgpt|gpt-?\d|copilot|gemini|codex|cursor|devin|\bai\b|noreply@anthropic\.com)/i
4
5/** A `Co-Authored-By:` line naming an AI or an AI vendor's address. */
6export const findAiTrailer = (text: string) => {
7  for (const line of text.split(/\\n|\n/)) {
8    const m = line.match(/co-authored-by:\s*(.+)/i)
9    if (m && AI_NAMES.test(m[1] ?? '')) return m[0].trim().replace(/["']+$/, '')
10  }
11  return null
12}
13
14/** The "Generated with Claude Code" footer some tools add to commits and pull requests. */
15export const findGeneratedFooter = (text: string) => {
16  const m = text.match(/(🤖\s*)?generated with \[?(claude code|claude|codex|copilot)[^\n"']*/i)
17  return m ? m[0].trim() : null
18}
19
20export const isCommit = (command: string) => /\bgit\b[^\n;&|]*\bcommit\b/.test(command)
21export const isPullRequest = (command: string) => /\bgh\s+pr\s+(create|edit)\b/.test(command)
22
23export type Verdict = { deny: string } | null
24
25export const judge = (command: string, options: { allowTrailer: boolean; blockFooter: boolean }): Verdict => {
26  const commit = isCommit(command)
27  const pr = isPullRequest(command)
28  if (!commit && !pr) return null
29  if (!options.allowTrailer) {
30    const trailer = findAiTrailer(command)
31    if (trailer) {
32      return { deny: `jr-no-ai-trailer: this ${commit ? 'commit' : 'pull request'} carries an AI co-author line ("${trailer}"). Remove that line and run it again.` }
33    }
34  }
35  if (options.blockFooter) {
36    const footer = findGeneratedFooter(command)
37    if (footer) {
38      return { deny: `jr-no-ai-trailer: this ${commit ? 'commit' : 'pull request'} carries an AI footer ("${footer}"). Remove it and run it again.` }
39    }
40  }
41  return null
42}
43