
    i?3              
         d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlmZmZ ddlmZ  eej                  j!                  d e ee      j'                         j(                  j(                                    Zedz  dz  Zedz  d	z  d
z  Zg dZg dZdZdZddZdZddZdddZddZ d dZ!dd!dZ"d"d#dZ#d$dZ$d%dZ%d&dZ&e'dk(  r e	jP                   e&              yy)'u  gemini_review_gate.py — Gemini Review를 GitHub status check로 강제하는 게이트.

CI에서 호출:
    1) status=pending 으로 check run 생성/갱신 (리뷰 진행 중)
    2) Gemini API 호출 → blocking 패턴 detect
    3) status=success/failure 로 check run 갱신
    4) 결과를 stdout에 JSON으로 출력 + memory/logs/gemini-calls.jsonl append

블로킹 패턴 (대소문자 무시 + 부분 매칭):
    en: blocking, critical issue, must fix, request changes
    ko: 차단, 필수 수정, 심각, blocker

차단 결정:
    - Gemini 응답에 위 패턴 1개 이상 → conclusion="failure"
    - 0건 → conclusion="success"
    - Gemini API 키 없거나 호출 실패 → conclusion="neutral" (CI 차단 X)

Rate limit / debounce:
    - 같은 SHA 중복 호출 차단 (cache 파일)
    - PR별 debounce 120초

CLI:
    python3 scripts/gemini_review_gate.py         --pr-number 123 --commit-sha abc... [--status pending|success|failure]
        [--repo OWNER/REPO] [--diff-file path/to/diff] [--mode call|publish-only]
    )annotationsN)datetimetimezone)Path	WORKSPACEmemorycachelogszgemini-calls.jsonl)blockingzcritical issuezmust fixzrequest changesblocker)u   차단u   필수 수정u   심각r   zgemini-review-gatex   c                 d    t        j                  t        j                        j	                         S N)r   nowr   utc	isoformat     K/home/jay/workspace/.worktrees/task-2464-dev6/scripts/gemini_review_gate.py_now_isor   6   s    <<%//11r   )	zno blockingz
no blockerznot blockingzno criticalzno must fixzno request changesu   차단 없음u   필수 수정 없음u   심각한 문제 없음c                   | sg S | j                         }|}t        D ]"  }|j                  |j                         d      }$ g }t        D ]O  }t	        j
                  dt	        j                  |j                                d|      s?|j                  |       Q t        D ]&  }|j                         |v s|j                  |       ( |S )u{  Return matched blocking patterns (case-insensitive, word-aware).

    - Strips out common negation phrases ("no blocking issues", "차단 없음" 등)
      so that `LGTM — no blocking issues` 가 false positive를 일으키지 않는다.
    - English patterns use word-boundary regex.
    - Korean patterns use plain substring (한글 단어 경계는 \b가 안 통함).
     z\b)	lower_NEGATION_PHRASESreplaceBLOCKING_PATTERNS_ENresearchescapeappendBLOCKING_PATTERNS_KO)textloweredcleanednegmatchespats         r   detect_blockingr(   G   s     	jjlGG  4//#))+s34G#  99299SYY[12"5w?NN3  $  99;'!NN3  Nr   c                    ddg| z   }t        j                  |dd|d      }|j                  |j                  |j                  fS )NghapiT   )capture_outputr"   inputtimeout)
subprocessrun
returncodestdoutstderr)args
input_datacmdprocs       r   gh_api_jsonr9   _   s@    -$
C>>#dZY[\D??DKK44r   c                   t         j                  dd       t         dt        j                  |j	                               j                          dz  }|j                         rdd| fS t         d|  dz  }|j                         rV	 t        |j                         j                         xs d	      }t        j                         |z
  t        k  rddt         dfS y# t        $ r d
}Y 8w xY w)z7Rate-limit + dedup gate. Returns (should_call, reason).Tparentsexist_okgemini-.jsonFzduplicate SHA cached at 
gemini-pr-	.lasttime0g        z
debounce (zs))Tok)	CACHE_DIRmkdirhashlibsha1encode	hexdigestexistsfloat	read_textstrip
ValueErrortimeDEBOUNCE_SECONDS)	pr_number
commit_sha	sha_cachedebounce_pathlasts        r   should_call_geminirV   e   s    OOD4O0ggll:3D3D3F&G&Q&Q&S%TTYZZI0<<<*YKy AAM	00288:AcBD 99; 00J'7&8;;;	  	D	s   	+C C-,C-c                    t         j                  j                  dd       t         j                  dd      5 }|j	                  t        j                  | d      dz          d d d        y # 1 sw Y   y xY w)	NTr;   autf-8encodingFensure_ascii
)LOG_PATHparentrE   openwritejsondumps)recordfs     r   write_call_logrg   v   s\    OO$6	sW	- ?	

66=>? ? ?s   *A,,A5c                   t         j                  j                  d      }|r	 t        j                  |      }d|j                  dd      |j                  dt        |       dz        |j                  dt        |j                  dd            dz        |j                  dd	      d
S t         j                  j                  d      }|s	ddd	d	d	ddS 	 d	dl}d	dl}|xs  t         j                  j                  dd      }d| d| }d| dd  }	t        j                  ddd|	igigi      j                  d      }
|j                  j                  ||
ddi      }t        j                         }	 |j                  j                  |d      5 }t        j                  |j!                         j#                  d            }ddd       t%        t        j                         |z
  d z        }d}	 d!   d	   d"   d   d	   d   }|j                  d#i       xs i }d|t%        |j                  d$d	            t%        |j                  d%d	            |d
S # t        $ r}ddd	d	d	d| dcY d}~S d}~ww xY w# t        $ r}ddd	d	d	d| dcY d}~S d}~ww xY w# 1 sw Y   xY w# t&        t(        f$ r t        j                        }Y w xY w# |j*                  j,                  $ rN}ddd	d	t%        t        j                         |z
  d z        d&|j.                   d'|j0                   dcY d}~S d}~w|j*                  j2                  $ rA}ddd	d	t%        t        j                         |z
  d z        d(|j0                   dcY d}~S d}~wt        $ r7}ddd	d	t%        t        j                         |z
  d z        d)| dcY d}~S d}~ww xY w)*zLightweight Gemini API call. Stub-friendly; mock by setting GEMINI_REVIEW_MOCK=<json>.

    Returns:
        {"ok": bool, "text": str, "tokens_in": int, "tokens_out": int, "latency_ms": int, "error": str?}
    GEMINI_REVIEW_MOCKTr"    	tokens_in   
tokens_out
latency_msr   )rC   r"   rk   rm   rn   Fzmock parse: )rC   r"   rk   rm   rn   errorNGEMINI_API_KEYzGEMINI_API_KEY missingzurllib import: GEMINI_MODELzgemini-2.5-proz8https://generativelanguage.googleapis.com/v1beta/models/z:generateContent?key=u  You are a strict code reviewer. Given the following diff, list any BLOCKING issues using the literal string 'blocking' or 'must fix' (English) or '차단'/'필수 수정' (Korean) in your response. If the diff is acceptable, respond with 'no blocking issues'.

DIFF:
i }  contentspartsrY   zContent-Typezapplication/json)dataheaders<   )r/   i  
candidatescontentusageMetadatapromptTokenCountcandidatesTokenCountzhttp z: zurl: zcall: )osenvirongetrc   loadslen	Exceptionurllib.requesturllib.errorImportErrorrd   rH   requestRequestrO   urlopenreaddecodeintKeyError
IndexErrorro   	HTTPErrorcodereasonURLError)	diff_textmodel	mock_jsonpayloadeapi_keyurllib	use_modelurlpromptbodyreqt0resprt   rn   r"   usages                     r   call_geminir   |   sQ    

34I
	|jj+GFB/$[[c)n6IJ%kk,GKKPR<S8TXY8YZ%kk,:  jjnn-.GRaqXYd|}}{ I8HIIDYKOdeldm
nC	 FU#$		&  ::zW/?.@$A#BCDKKGTD
..
 
 4.J\9]
 
^C	BO^^##C#4 	;::diik009:D	;$))+*d23
	$%a(3G<Q?GD "-3UYY'91=>eii(>BC$
 	
E  	|!1\]jvwxvyhz{{	|  {RaqXYfuvwuxdyzz{ 	; 	; *% 	$::d#D	$ <<!! _RaqX[]a]f]f]hkm]mqu\uXv  DI  JK  JP  JP  IQ  QS  TU  T\  T\  S]  B^  _  	_<<   URaqX[]a]f]f]hkm]mqu\uXv  DI  JK  JR  JR  IS  BT  U  	U ORaqX[]a]f]f]hkm]mqu\uXv  DJ  KL  JM  BN  O  	OOs   BI I4 0K 3J -K .J" AK 	I1I,&I1,I14	J=JJJJK "$K	K K		K O	%AL.(O	.O	
6N O	O	,O>O	O	c                    t         ||t         |xs |dd dd}|r||d<   |r||d   d<   t        j                  |      }dd	d
|  dddg}t        ||      \  }	}
}|	|
|dS )zCreate or update gemini-review-gate check run via gh api.

    status: queued | in_progress | completed
    conclusion (only when completed): success | failure | neutral | cancelled | timed_out | action_required
    N   )titlesummary)namehead_shastatusoutput
conclusionr   r"   z-XPOSTzrepos/z/check-runsz--input-)r6   )rcr3   r4   )
CHECK_NAMErc   rd   r9   )reposhar   r   r   detailsr   r   r5   r   outerrs               r   publish_check_runr      s     &73KgdsmL	G  *$+&!::gD&F4&4iEDt5LBSs33r   c                    t        j                  dddt        |      d| gddd      }|j                  dk(  r|j                  S y	)
zEBest-effort PR diff fetch. Falls back to empty string if unavailable.r*   prdiff--repoTr,   )r-   r"   r/   r   rj   )r0   r1   strr2   r3   )r   rQ   r8   s      r   fetch_pr_diffr      sF    >>	tVS^Xt<$D !{{r   c                   | j                   xs  t        j                  j                  dd      }| j                  xs d}| j
                  }t        |||t               d}| j                  dk(  r| j                  s1t        t        j                  ddi      t        j                         y	t        ||| j                  d
v rdnd| j                  d
v r| j                  nd | j                   xs | j                        }||d<   t        t        j                  |d             |d   dk(  rdS dS |s1t        t        j                  ddi      t        j                         y	| j                  dk(  r5t        ||dd       d|d<   t        t        j                  |d             yt#        ||      \  }}|sJ| j$                  s>d|d<   ||d<   t        ||ddd|        t        t        j                  |d             yd}| j&                  r&	 t)        | j&                        j+                  d      }|s|rt/        ||      }t1        |      }
t3        |
j                  d!d            }|
d"   sd}d#|
j                  dd$       }n|rd%}d&| }nd'}d(}t               |||
j                  d)d      |
j                  d*d      |
j                  d+d      |||
d"   d,	}t5        |       |
d"   rt6        d-t9        j:                  |j=                               j?                          d.z  }|j@                  jC                  d/d/0       |jE                  t        j                  |d             t6        d1| d2z  }|jE                  tG        tI        jH                                      | jJ                  r!t        ||d|||
j                  d!d      3       ||d<   ||d4<   |
j                  d+d      |d+<   t        t        j                  |d             |d%k(  rdS dS # t,        $ r>}	t        t        j                  dd |	 i      t        j                         Y d }	~	'd }	~	ww xY w)5NGH_REPOJonghyukJeon/dev_workspacer   )r   r   r   r   tspublish-onlyro   z&--status required in publish-only mode)file   )successfailureneutral	completedin_progress)r   r   r   publishFr\   r      z--commit-sha requiredpendingzGemini review pending)r   r   stateskippedr   r   z	skipped: rj   rY   rZ   warnzdiff read: r"   rC   zgemini call failed: unknownr   zblocking matches: r   zno blocking issuesrk   rm   rn   )		timestamprQ   rR   rk   rm   rn   r   r&   rC   r>   r?   Tr;   r@   rA   )r   r   r   r   r&   )&r   r|   r}   r~   rQ   rR   r   r   moder   printrc   rd   sysr4   r   r   rV   force	diff_filer   rL   OSErrorr   r   r(   rg   rD   rF   rG   rH   rI   r`   rE   
write_textr   rO   publish_check)r5   r   rQ   rR   r   resultproceedr   r   r   gemini_resultr&   r   r   re   rS   rT   s                    r   gater      s   99O

y2NOD#!IJ#9ZQU]e]ghCyyN"{{$**g'OPQX[XbXbc"*"&++1R"R;Xe&*kk5V&Vt{{\`LL/DKK	
  Idjj5124LA%q,1,djj'#:;<3::N{{i$
=Jab Gdjj512(J?OGV4:: GH$
;9`ijpiq^rsdjj512I~~	LT^^,666HI !$	2		*Mm//;<G
():):7I)N(OP	
&wi0
& Z "&&{A6#''a8#''a8D!
F 6T'',,z7H7H7J*K*U*U*W)XX] ^^	td;TZZUCD!j9$EE  TYY[!12$
;:_fp}  qB  qB  CI  KM  qN  	OCLC	N%)),:C	$**Su
-.i'1.Q.W  	L$**fA3&789

KK	Ls   %P 	Q "3QQ c                    t        j                  d      } | j                  dt        d       | j                  ddd	       | j                  d
t        j
                  j                  dd             | j                  ddg d       | j                  dd       | j                  ddddg       | j                  dd       | j                  ddd       | j                  ddd       | j                         }t        |      S )Nu,   Gemini Review Gate — GitHub check run gate)descriptionz--pr-numberr   )typedefaultz--commit-shaFrj   )requiredr   r   r   r   )r   z--status)rj   r   r   r   r   )r   choicesz--diff-filez--modecallr   z	--summaryz--publish-check
store_truez(publish completion as a GitHub check run)actionhelpz--forcezbypass dedup/debounce)	argparseArgumentParseradd_argumentr   r|   r}   r~   
parse_argsr   )apr5   s     r   mainr   7  s    		 	 -[	\BOOMQO7OONUBO?OOHbjjnnY@\&]O^OOJ4dOeOOM2O.OOHfv~6NOOOOKO,OO%lAkOlOOIl9POQ==?D:r   __main__)returnr   )r"   r   r   	list[str]r   )r5   r   r6   
str | Noner   ztuple[int, str, str])rQ   r   rR   r   r   ztuple[bool, str])re   dictr   None)r   r   r   r   r   r   )Nrj   rj   )r   r   r   r   r   r   r   r   r   r   r   r   r   r   )r   r   rQ   r   r   r   )r5   zargparse.Namespacer   r   )r   r   ))__doc__
__future__r   r   rF   rc   r|   r   r0   r   rO   r   r   pathlibr   r}   r~   r   __file__resolver`   r   rD   r_   r   r!   r   rP   r   r   r(   r9   rV   rg   r   r   r   r   r   __name__exitr   r   r   <module>r      s   6 #    	 	  
  ' Sh1G1G1I1P1P1W1W-XYZ	 7*	x&(+??_ G !
 2
 05"??OD4,U/p zCHHTV r   