
    i$                    6   d Z ddlmZ ddlZddlZddlZddlmZ  ee      j                         j                  j                  Zddddd	d
ddgZddZddZddZddZddZddZddZddZddZd dZd!dZedk(  r ej2                   e              yy)"u  lock_in_verify.py — Lock-in 1 First-line 가드 자동 검증 (CI 강제용).

task-2439에서 도입한 "First-line guard" 패턴이 머지 경로 함수의 *첫 statement*로
유지되는지 AST 기반으로 검사한다. 위반 시 exit_code=1로 CI를 차단한다.

검사 대상:
  1) scripts/anu_confirm_bot/main.py::_execute_approve
       - 첫 문장: cancelled 마커 Path() 할당
       - 그 다음: cancelled .exists() 가드 + early return
       - guard.sh subprocess.run(...)이 *모든* gh pr merge 호출보다 line-number 기준 먼저
  2) scripts/auto_merge.py::AutoMerger.execute_merge
       - 동일 패턴, merge 시그너처는 worktree_manager finish 호출
    )annotationsN)Pathzscripts/anu_confirm_bot/main.py_execute_approve)taskctlmerge)filefuncmerge_signature_tokenszscripts/auto_merge.pyexecute_merge)worktree_managerfinishc                    t        j                  |       D ]@  }t        |t         j                  t         j                  f      s.|j
                  |k(  s>|c S  y N)astwalk
isinstanceFunctionDefAsyncFunctionDefname)treer   nodes      G/home/jay/workspace/.worktrees/task-2524-dev5/scripts/lock_in_verify.py_find_functionr   *   sH     dS__c.B.BCDVZIZK     c                    | rpt        | d   t        j                        rSt        | d   j                  t        j                        r,t        | d   j                  j                  t
              r| dd  S | S )Nr      )r   r   ExprvalueConstantstr)bodys    r   _strip_docstringr"   1   sa    
47CHH-*T!W]]CLL2Y^himnoipivivi|i|  B  _CABxKr   c                R   t        | t        j                        sy| j                  r't        | j                  d   t        j                        syd| j                  d   j
                  j                         vryt        j                  | j                        }d|v xr
 d|v xr d|v S )NFr   	cancelledz
.cancelledmemoryevents)	r   r   AssigntargetsNameidlowerunparser   )stmtsrcs     r   _is_cancelled_path_assignr/   7   s    dCJJ'<<z$,,q/388D$,,q/,,2244
++djj
!C3F8s?Fx3Fr   c                0   t        | t        j                        syt        j                  | j                        }d|j                         vsd|vry| j                  sy| j                  d   }t        |t        j                  t        j                  f      S )NFr$   z	.exists()r   )	r   r   Ifr,   testr+   r!   ReturnRaise)r-   test_srcfirsts      r   _is_cancelled_exists_guardr7   B   so    dCFF#{{499%H(..**k.I99IIaLEecjj#))455r   c                    t        j                  | j                        }|dk7  ryt        j                  |       dv ryt        fd|D              S )Nsubprocess.runFguard.shTc              3  &   K   | ]  }|v  
 y wr    ).0r   r.   s     r   	<genexpr>z*_is_guard_sh_subprocess.<locals>.<genexpr>U   s     7tts{7   )r   r,   r	   any)callguard_var_namesfunc_srcr.   s      @r   _is_guard_sh_subprocessrD   N   sK    {{499%H##
++d
CS7777r   c                   t               }t        j                  |       D ]  }t        |t        j                        st        |j                        dk(  s7t        |j                  d   t        j                        s_t        j                  |j                        }d|v s|j                  |j                  d   j                          |S )Nr   r   r:   )setr   r   r   r'   lenr(   r)   r,   r   addr*   )r	   namesr   	value_srcs       r   _collect_guard_var_namesrK   X   s    eE .dCJJ'C,=,BzRVR^R^_`RacfckckGlDJJ/IY&		$,,q/,,-	.
 Lr   c                :   t        | t        j                  t        j                  f      r7dj	                  d | j
                  D              t        fd|D              ryt        | t        j                        r| j                  D ]  }t        ||      s y y)zOFind any list literal or call whose string-literal contents include all tokens. c              3     K   | ]F  }t        |t        j                        st        |j                  t              s9|j                   H y wr   )r   r   r   r   r    )r=   elts     r   r>   z(_list_contains_tokens.<locals>.<genexpr>e   s8      
jcll.KPZ[^[d[dfiPjCII
s   AAAc              3  &   K   | ]  }|v  
 y wr   r<   )r=   tjoineds     r   r>   z(_list_contains_tokens.<locals>.<genexpr>h   s     +qqF{+r?   TF)
r   r   ListTuplejoineltsallCallargs_list_contains_tokens)r   tokensargrR   s      @r   rZ   rZ   b   s    $399-. 
!%
 
 +F++$!99 	C$S&1	 r   c                    t        |       }d }t        j                  |       D ]H  }t        |t        j                        st        ||      s+||j                  |k  s=|j                  }J |S r   )rK   r   r   r   rX   rD   lineno)r	   
guard_varsearliestr   s       r   _find_first_guard_sh_linenora   q   sa    )$/JH 'dCHH%*A$
*S4;;#9;;' Or   c                ~  	 t               t        j                  |       D ]  }t        |t        j                        st        |j                        dk(  s7t        |j                  d   t        j                        s_t        j                  |j                        	t        	fdD              sj                  |j                  d   j                          dfd}i }g }t        j                  |       D ]  }t        |t        j                        r|j                  }t        |t        j                  t        j                  f      rmt        |j                        dk(  rUt        |j                  d   t        j                        r. ||      r&|j                  ||j                  d   j                  <   t        |t        j                         st        j                  |j"                        dk7  s|j$                  s|j$                  d   }t        |t        j                  t        j                  f      r% ||      r|j'                  |j                         jt        |t        j                        s|j                  |v s|j'                  |j                          t)        |      S )aB  Locate each subprocess.run(cmd, ...) where cmd resolves to a list containing tokens.

    Tokens may be present either as direct string literals in the list, or via
    Path()-assigned variables whose value source-text contains the token. We
    collect both kinds and treat any list element matching either as a hit.
    r   r   c              3  &   K   | ]  }|v  
 y wr   r<   )r=   rQ   rJ   s     r   r>   z+_find_merge_call_linenos.<locals>.<genexpr>   s     2a1	>2r?   c                   t        | t        j                  t        j                  f      rg }g }| j                  D ]v  }t        |t        j
                        r6t        |j                  t              r|j                  |j                         S|j                  t        j                  |             x dj                  |      }dj                  |      |dz   z   t        fdD              ryt        fdD              ryy)NrM   c              3  &   K   | ]  }|v  
 y wr   r<   )r=   rQ   combineds     r   r>   zA_find_merge_call_linenos.<locals>.list_matches.<locals>.<genexpr>   s     1Q1=1r?   Tc              3  &   K   | ]  }|v  
 y wr   r<   )r=   r   
joined_vars     r   r>   zA_find_merge_call_linenos.<locals>.list_matches.<locals>.<genexpr>   s     B$4:%Br?   F)r   r   rS   rT   rV   r   r   r    appendr,   rU   rW   r@   )	r   joined_strsjoined_namesrO   rR   rf   rh   token_var_namesr[   s	        @@r   list_matchesz._find_merge_call_linenos.<locals>.list_matches   s    dSXXsyy12%'K&(Lyy :c3<<0Z		35O&&syy1 ''C(89	:
 XXk*F,/J|j0H1&11B/BBr   r9   )r   ast.ASTreturnbool)rF   r   r   r   r'   rG   r(   r)   r,   r   rW   rH   r*   rS   rT   r^   rX   r	   rY   ri   sorted)
r	   r[   r   rm   list_assignmentsmerge_linesr   	first_argrl   rJ   s
    `      @@r   _find_merge_call_linenosru   {   s    !$O 8dCJJ'C,=,BzRVR^R^_`RacfckckGlDJJ/I2622##DLLO$6$67	8$ (*K 0dCJJ'JJE%#((CII!67C<MQR<RWabfbnbnopbqsvs{s{W|&;?;;$T\\!_%7%78dCHH%{{499%)99		!I)chh		%:;Y@W""4;;/Isxx0Y\\EU5U""4;;/0 +r   c           
     $   g }| j                         s|  dgS | j                  d      }t        j                  |      }t	        ||      }|	|  d| dgS t        t        |j                              }t        |      dk  r|  d| dt        |       dgS t        |d	         s2|j                  |  d| d
t        j                  |d	         d d         t        |d         s2|j                  |  d| dt        j                  |d         d d         t        |      }||j                  |  d| d       t        ||      }	|	s|j                  |  d| d| d       |*|	r(||	d	   k\  r |j                  |  d| d| d|	d	    d       |S )Nz: file not foundzutf-8)encoding::z: function not found   z: body too short (z stmts)r   uB   : 첫 statement가 cancelled 마커 Path 할당이 아님 — got x   r   uT   : 두 번째 statement가 cancelled.exists() 가드+return/raise 가 아님 — got u'   : guard.sh subprocess.run 호출 없음z: merge subprocess(u   ) 호출 없음z: guard.sh line(u   )이 merge subprocess line(u!   )보다 늦음 — Lock-in 위반)exists	read_textr   parser   r"   listr!   rG   r/   ri   r,   r7   ra   ru   )
	file_path	func_namemerge_tokenserrorsr.   r   r	   r!   guard_linenomerge_linenoss
             r   verify_functionr      sC   F+-.//


w

/C99S>D$	*D|+R	{*>?@@DO,D
4y1}+R	{*<SYKwOPP$T!W-2i[0rsvs~s~  @D  EF  @G  tH  IM  JM  tN  sO  P  	Q%d1g.2i[  1E  FI  FQ  FQ  RV  WX  RY  FZ  [_  \_  F`  Ea  b  	c.t4L2i[0WXY,T<@M2i[0CL>Q`abMlmTUFV6VkI;&6|nD_`mno`p_q  rS  T	
 Mr   c                 p   t        j                  d      } | j                  dt        t                     | j                  dd       | j                         }t        |j                        j                         }g }t        D ]U  }||d   z  }t        ||d	   |d
         }|r|j                  |       2|j                  r?t        d|d    d|d	           W |rAt        dt        j                         |D ]   }t        d| t        j                         " y|j                  st        d       y)Nu    Lock-in First-line 가드 검증)descriptionz--workspace)defaultz--quiet
store_true)actionr   r	   r
   zPASS  rx   u'   FAIL  Lock-in First-line 가드 위반:)r   z  - r   u4   PASS  Lock-in First-line 가드 모든 함수 통과r   )argparseArgumentParseradd_argumentr    	WORKSPACE
parse_argsr   	workspaceresolveCHECKSr   extendquietprintsysstderr)parserrY   ws
all_errorsspectargeterrses           r   mainr      s   $$1STF
s9~>
	,7D	dnn		%	%	'BJ ;d6l"vtF|T:R5STd#F4<.4<.9:; 7cjjI 	/AD*3::.	/::DEr   __main__)r   rn   r   r    ro   zast.FunctionDef | None)r!   list[ast.stmt]ro   r   )r-   zast.stmtro   rp   )rA   zast.CallrB   set[str]ro   rp   )r	   ast.FunctionDefro   r   )r   rn   r[   tuple[str, ...]ro   rp   )r	   r   ro   z
int | None)r	   r   r[   r   ro   z	list[int])r   r   r   r    r   r   ro   z	list[str])ro   int)__doc__
__future__r   r   r   r   pathlibr   __file__r   parentr   r   r   r"   r/   r7   rD   rK   rZ   ra   ru   r   r   __name__exitr<   r   r   <module>r      s    #  
 
 N""$++22	 2""6 ("@
"G	680f:0 zCHHTV r   