2020from copycat_policy import (
2121 COPYCAT_BLOCK , COPYCAT_WARN , COPYCAT_CONTAINMENT , MAX_WARNINGS ,
2222 MIN_ADDED_LINES , LITERAL_BLOCK , FUNC_BLOCK_WARN ,
23+ FUNC_MIN_BODY_TOKENS , FUNC_MIN_PR_LEVEL , FUNC_MAIN_SKIP ,
2324 STRUCTURAL_ENABLED , LLM_ENABLED , skip_copycat_scoring ,
2425 COPYCAT_REFERENCE_STATE ,
2526)
@@ -70,16 +71,65 @@ def gh(args):
7071
7172
7273def is_boilerplate_block (sig , body ):
73- """Skip per-function scoring on shared launch/dispatch boilerplate ."""
74+ """Skip per-function scoring on shared launch/dispatch / tiny device helpers ."""
7475 text = f"{ sig } { body } " .strip ()
7576 if _BOILERPLATE_RE .search (text ) and len (body .splitlines ()) <= 3 :
7677 return True
7778 s = sig .strip ()
7879 if s .startswith (("if " , "if(" , "} else" , "else if" )):
7980 return True
81+ nlines = sum (1 for l in body .splitlines () if l .strip ())
82+ # Tiny __device__ helpers (dequant/scale) converge across MoE PRs and often
83+ # already exist on main — not evidence of copying another open PR.
84+ if "__device__" in s and nlines <= 20 :
85+ return True
86+ if nlines <= 8 :
87+ return True
88+ tokens = [t for l in body .splitlines () for t in l .split () if len (t ) > 1 ]
89+ if len (tokens ) < FUNC_MIN_BODY_TOKENS :
90+ return True
8091 return False
8192
8293
94+ def _main_file_text (repo , path , cache ):
95+ """Raw file contents at origin/main (cached). Empty if missing."""
96+ if not path or path in cache :
97+ return cache .get (path , "" )
98+ r = gh (["api" , f"repos/{ repo } /contents/{ path } ?ref=main" ,
99+ "-H" , "Accept: application/vnd.github.raw" ])
100+ text = r .stdout or "" if r .returncode == 0 else ""
101+ cache [path ] = text
102+ return text
103+
104+
105+ def block_already_on_main (repo , path , body , cache = None ):
106+ """True when ≥FUNC_MAIN_SKIP of the block's non-empty lines already exist on main."""
107+ if cache is None :
108+ cache = {}
109+ main = _main_file_text (repo , path , cache )
110+ if not main :
111+ return False
112+ lines = [l .strip () for l in body .splitlines () if l .strip ()]
113+ if not lines :
114+ return False
115+ main_lines = {l .strip () for l in main .splitlines () if l .strip ()}
116+ hit = sum (1 for l in lines if l in main_lines )
117+ return (hit / len (lines )) >= FUNC_MAIN_SKIP
118+
119+
120+ def func_layer_should_warn (pr_level_c , func_c , copy_sig , copy_body , path = "" , repo = "" , cache = None ):
121+ """Gate L4 warns — skip tiny/main-shared helpers and negligible PR-level overlap."""
122+ if func_c < FUNC_BLOCK_WARN :
123+ return False
124+ if pr_level_c < FUNC_MIN_PR_LEVEL :
125+ return False
126+ if is_boilerplate_block (copy_sig , copy_body ):
127+ return False
128+ if repo and path and block_already_on_main (repo , path , copy_body , cache ):
129+ return False
130+ return True
131+
132+
83133def pr_has_label (repo , num , label ):
84134 info = json .loads (gh (["pr" , "view" , str (num ), "-R" , repo , "--json" , "labels" ]).stdout or "{}" )
85135 return any (l .get ("name" ) == label for l in info .get ("labels" , []))
@@ -166,12 +216,19 @@ def structural_similarity(repo, copy_num, orig_num, containment_pct):
166216
167217def split_into_blocks (repo , num ):
168218 """Split a PR's added lines into logical CUDA code blocks (kernel functions, device
169- functions, and other named scopes). Filters out trivial if/else conditionals that
170- happen to match across PRs but aren't actual functions (minimum 10 tokens)."""
219+ functions, and other named scopes). Returns list of (sig, body, path). Filters out
220+ trivial if/else conditionals that happen to match across PRs but aren't actual
221+ functions (minimum 10 tokens)."""
171222 diff = gh (["pr" , "diff" , str (num ), "-R" , repo ]).stdout or ""
172223 blocks = []
173- current_sig = None ; current_body = []
224+ current_sig = None ; current_body = []; current_file = ""
174225 for line in diff .splitlines ():
226+ if line .startswith ("+++ " ):
227+ p = line [4 :].strip ()
228+ if p .startswith ("b/" ):
229+ p = p [2 :]
230+ current_file = "" if (not p or p == "/dev/null" ) else p
231+ continue
175232 if not line .startswith ("+" ) or line .startswith ("+++" ):
176233 continue
177234 s = line [1 :].strip ()
@@ -190,34 +247,37 @@ def split_into_blocks(repo, num):
190247 if current_sig and current_body :
191248 tokens = [t for l in current_body for t in l .split () if len (t )> 1 ]
192249 if len (tokens ) >= 10 :
193- blocks .append ((current_sig , "\n " .join (current_body )))
250+ blocks .append ((current_sig , "\n " .join (current_body ), current_file ))
194251 current_sig = s
195252 current_body = []
196253 elif current_sig is not None :
197254 current_body .append (line [1 :])
198255 if current_sig and current_body :
199256 tokens = [t for l in current_body for t in l .split () if len (t )> 1 ]
200257 if len (tokens ) >= 10 :
201- blocks .append ((current_sig , "\n " .join (current_body )))
258+ blocks .append ((current_sig , "\n " .join (current_body ), current_file ))
202259 return blocks
203260
204261
205- def per_function_containment (repo , copy_num , orig_num ):
206- """Return the HIGHEST per-function containment across all shared blocks. If the original
207- PR has one function (focused change) and the copy PR adds it inside a larger PR, this
208- will catch it even when PR-level containment is low."""
262+ def per_function_containment (repo , copy_num , orig_num , main_cache = None ):
263+ """Return (best_c, copy_sig, orig_sig, copy_body, path) for the strongest non-boilerplate
264+ shared block. Skips tiny device helpers and blocks already present on main."""
265+ if main_cache is None :
266+ main_cache = {}
209267 copy_blocks = split_into_blocks (repo , copy_num )
210268 orig_blocks = split_into_blocks (repo , orig_num )
211269 if not copy_blocks or not orig_blocks :
212- return 0.0 , "" , ""
213- best = 0.0 ; best_copy_sig = "" ; best_orig_sig = ""
214- for csig , cb in copy_blocks :
270+ return 0.0 , "" , "" , "" , ""
271+ best = 0.0 ; best_copy_sig = "" ; best_orig_sig = "" ; best_body = "" ; best_path = ""
272+ for csig , cb , cpath in copy_blocks :
215273 if is_boilerplate_block (csig , cb ):
216274 continue
275+ if block_already_on_main (repo , cpath , cb , main_cache ):
276+ continue
217277 ctokens = set (cb .split ())
218278 if len (ctokens ) < 5 :
219279 continue
220- for osig , ob in orig_blocks :
280+ for osig , ob , _opath in orig_blocks :
221281 otokens = set (ob .split ())
222282 if len (otokens ) < 5 :
223283 continue
@@ -226,7 +286,8 @@ def per_function_containment(repo, copy_num, orig_num):
226286 c = len (ctokens & otokens ) / len (ctokens )
227287 if c > best :
228288 best = c ; best_copy_sig = csig ; best_orig_sig = osig
229- return best , best_copy_sig , best_orig_sig
289+ best_body = cb ; best_path = cpath
290+ return best , best_copy_sig , best_orig_sig , best_body , best_path
230291
231292
232293def _llm_provider ():
@@ -420,10 +481,10 @@ def warn_copycat(repo, num, original, author, strike_count, containment_pct, str
420481 f"is substantially contained in #{ original } by a different author — the PR-level "
421482 f"containment is low because the copied function is embedded inside a larger diff." )
422483 elif structural :
423- head = (f"**{ containment_pct :.0f } % containment** + structural similarity "
484+ head = (f"**{ containment_pct :.0% } containment** + structural similarity "
424485 "(Levenshtein + bigram cosine both above threshold vs this PR's code shape)" )
425486 else :
426- head = f"**{ containment_pct :.0f } % containment** in the earlier #{ original } "
487+ head = f"**{ containment_pct :.0% } containment** in the earlier #{ original } "
427488 if will_block :
428489 tail = (f"\n \n This is the **{ MAX_WARNINGS } rd** copycat-like submission — the account is now "
429490 "**blocked** and the PR closed." )
@@ -495,6 +556,7 @@ def main():
495556 original = None ; orig_author = None ; best_containment = 0.0
496557 pr_level_containment = 0.0
497558 best_lev = 0.0 ; best_cos = 0.0 ; structural_fired = False
559+ main_cache = {}
498560
499561 for e_num in earlier_nums :
500562 e_author = pr_author .get (e_num , "" )
@@ -520,16 +582,17 @@ def main():
520582
521583 # Layer 4: per-function containment (near-verbatim kernel inside larger PR).
522584 if c < COPYCAT_WARN and not structural_fired :
523- func_c , func_csig , func_osig = per_function_containment (REPO , pr_num , e_num )
524- if func_c >= FUNC_BLOCK_WARN :
585+ func_c , func_csig , func_osig , func_body , func_path = per_function_containment (
586+ REPO , pr_num , e_num , main_cache )
587+ if func_layer_should_warn (c , func_c , func_csig , func_body , func_path , REPO , main_cache ):
525588 structural_fired = True ; best_lev = func_c ; best_cos = - 1.0
526589 best_containment = max (best_containment , COPYCAT_WARN )
527590 original = e_num ; orig_author = e_author
528591 print (f" per-function bump: { func_csig [:60 ]} ... is { func_c :.0%} contained in #{ e_num } -> WARN" )
529- elif _llm_enabled () and func_c >= LLM_FUNC_MIN :
592+ elif _llm_enabled () and func_c >= LLM_FUNC_MIN and not is_boilerplate_block ( func_csig , func_body ) :
530593 print (f" layer 4 LLM: per-function containment={ func_c :.1%} (vs #{ e_num } )" )
531- cb = next (( b for s , b in split_into_blocks ( REPO , pr_num ) if s == func_csig ), "" )
532- ob = next ((b for s , b in split_into_blocks (REPO , e_num ) if s == func_osig ), "" )
594+ cb = func_body
595+ ob = next ((b for s , b , _p in split_into_blocks (REPO , e_num ) if s == func_osig ), "" )
533596 is_copy , llm_conf , reason = llm_judge_copycat (cb , ob , func_csig , func_osig )
534597 print (f" LLM: copycat={ is_copy } confidence={ llm_conf :.2f} reason={ reason [:120 ]} " )
535598 if is_copy and llm_conf >= LLM_CONFIDENCE_MIN :
0 commit comments