
    i5                    b   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
m
Z
mZ ddlmZ ddlmZ dZ ej"                  d      Zdd	Zdd
ZddZd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%dZ e!dk(  r ejD                   e               yy)&u:  mixed_commit_detector.py — task 브랜치에 섞인 alien commit 탐지기.

task-2459 Phase 2-C 구현. spec: memory/specs/mixed-commit-detector-spec.md (v1.0).

핵심 정책:
  - read-only 분석기. git history mutation 절대 금지 (rebase/cherry-pick/reset 0줄).
  - LLM API 호출 0줄. 외부 네트워크 호출 0줄.
  - mixed 감지 시 .tasks/locks/<id>.frozen 마커 + evidence 저장 후 exit 1.
  - 마커 자동 제거 금지 (회장/아누 수동 처리).

Exit code:
  0 = clean / empty
  1 = mixed
  2 = internal error (git 실패, IO 실패 등)
    )annotationsN)datetimetimezone)Path)Anyz1.0z\[(task-[\w.]+)\]c                 f    t        j                  t        j                        j	                  d      S )z&UTC ISO 8601 (`2026-05-05T18:21:09Z`).z%Y-%m-%dT%H:%M:%SZr   nowr   utcstrftime     N/home/jay/workspace/.worktrees/task-2459-dev5/scripts/mixed_commit_detector.py_now_isor   '   s!    <<%../CDDr   c                 f    t        j                  t        j                        j	                  d      S )u:   파일명용 compact 타임스탬프 (`20260505T182109Z`).z%Y%m%dT%H%M%SZr	   r   r   r   _now_compactr   ,   s!    <<%../?@@r   c                   | j                   j                  dd       t        j                  t	        | j                         | j
                  dz   d      \  }}	 t        j                  |dd      5 }t        j                  ||d	d
       |j                  d       ddd       t        j                  |t	        |              y# 1 sw Y   )xY w# t        $ r' 	 t        j                  |        # t        $ r Y  w xY ww xY w)u'   tempfile + os.replace 로 atomic write.T)parentsexist_ok.z.tmp)dirprefixsuffixwzutf-8)encodingF   )ensure_asciiindent
N)parentmkdirtempfilemkstempstrnameosfdopenjsondumpwritereplace	ExceptionunlinkOSError)pathdatafdtmp_pathfs        r   _atomic_write_jsonr4   1   s    KKdT2##TYY_VLB
YYr31 	QIIdAE!<GGDM	 	

8SY'	 	  	IIh 	  		sH   C 2+C'C C
C 	DC10D1	C=:D<C==Dc                P    t        j                  dg| z   t        |      dd|      S )uU   git read-only 명령 실행. mutation 명령은 호출자에서 절대 사용 금지.gitT)cwdcapture_outputtexttimeout)
subprocessrunr$   )argsr7   r:   s      r   _git_runr>   D   s,    >>	$H r   c                   d}t        d|  d| d| g|      }|j                  dk7  r3t        d|j                   d|j                  j	                                g }|j
                  }|s|S |j                  d	      }d}|d
z   t        |      k  rR||   j                  d      }	||dz      }
||d
z      }|	r|j                  |	|
|d       |dz  }|d
z   t        |      k  rR|S )u2  `git log <base>..<head>` 로 commit SHA + subject 수집.

    git 명령은 ``git_dir`` 에서 실행한다. ``git_dir`` 는 task worktree 의 경로여야
    하며, evidence/freeze 저장 경로(workspace)와는 분리된다.
    NUL 종료 포맷을 사용해 subject 안의 `|` 깨짐을 회피.
    z%H%x00%s%x00%an%x00logz..z--pretty=format:r7   r   zgit log failed (exit z):  r   r      )shasubjectauthor   )
r>   
returncodeRuntimeErrorstderrstripstdoutsplitlenlstripappend)base_refhead_refgit_dirfmtrescommitsoutpartsirD   rE   rF   s               r   collect_commitsrZ   S   s     C
	8*Bxj)-=cU+CDC ~~#CNN#33szz7G7G7I6JK
 	
 G
**C IIfE	A
a%#e*
Ahood#A,q1uNN37fMN	Q a%#e*
 Nr   c                ,    t         j                  |       S )u{   subject 메시지 전체에서 [task-...] 토큰 추출.

    spec ★: prefix anchor 금지, 메시지 전체 매치.
    )TASK_REfindall)rE   s    r   extract_task_tokensr^   w   s    
 ??7##r   c                |   g }t               }d}d}| D ][  }t        |d         }d}|s|dz  }n%|D ]   }	|j                  |	       |	|k(  r|dz  }d}" |j                  |d   |d   ||d       ] t	        ||hz
        }
t	        |r||hz  n	t                     }t        |
      |
||t        d |D              ||d	S )
u>   commit 리스트로부터 mixed 여부 + 토큰 분석 수행.r   rE   FrC   TrD   )rD   rE   tokensalienc              3  ,   K   | ]  }|d    s	d  yw)ra   rC   Nr   ).0cs     r   	<genexpr>zdetect_mixed.<locals>.<genexpr>   s      Eq!G* Es   
)mixedalien_tasksmixed_tasks   本_task_token_countalien_token_countuntagged_commit_countrV   )setr^   addrP   sortedboolsum)rV   current_task_id
per_commit
all_tokensuntagged	own_countrd   r`   is_alientra   rh   s               r   detect_mixedrx      s    J5JHI 
$Qy\2MH $q!'NI#H$ 	xY< !		

* : 112E:&7735QKe" )  EJ EE!) r   c                :    |dz  dz  |  dz  }t        ||       |S )u4   `.tasks/locks/<id>.frozen` JSON 마커 atomic write..taskslocks.frozen)r4   )task_idr0   	workspacer/   s       r   write_freeze_markerr      s,    x')wiw,??DtT"Kr   c                V    t               }|dz  dz  | z  d| dz  }t        ||       |S )zD`.tasks/evidence/<id>/mixed-commit-<ts>.json` evidence atomic write.rz   evidencezmixed-commit-z.json)r   r4   )r}   r0   r~   tsr/   s        r   write_evidencer      s:    	Bx*,w6=E9RRDtT"Kr   c                |    t        g d|       }|j                  dk(  r|j                  j                         xs dS y)u.   현재 HEAD branch 이름 (detached 시 None).)zsymbolic-refz--shortz-qHEADrA   r   Nr>   rH   rL   rK   )rS   rU   s     r   resolve_branchr      s6    
<'
JC
~~zz!)T)r   c                t    t        d| g|      }|j                  dk(  r|j                  j                         S y )Nz	rev-parserA   r   r   )refrS   rU   s      r   resolve_shar      s5    
K%7
3C
~~zz!!r   c                D   |dk(  rt        |      n|}t        ||      }t        ||      }| t               t        ||||||d   |d   |d   |d   |d   |d   |d   d	}|d   r8d
j	                  |d         }	|  d|d    d|	 d|  d|d<   d|  d|d<   d|d<   |S d|d<   |S )Nr   rf   rh   rg   ri   rj   rk   rV   )r}   verified_atdetector_version
branch_refresolved_branchrQ   base_shahead_sharf   rh   rg   ri   rj   rk   rV   , u    브랜치에서 alien commit u   건 감지 (z). .tasks/locks/u2   .frozen 생성됨. 회장/아누만 수동 처리.escalation_messagez.tasks/locks/r|   freeze_marker_pathrC   	exit_coder   )r   r   r   DETECTOR_VERSIONjoin)
r}   r   rQ   rS   analysisr   r   r   payload	alien_strs
             r   _build_payloadr      s     $.#7wZ  8W-H:w/H z, *'".. ()? @%&9:!)*A!BI&G$ IIh}56	i5+,-\) E#9$VX 	$%
 +8y(H$%  N  !Nr   c                J    |ry t        t        j                  | d             y )NFr   )printr(   dumps)objquiets     r   _emitr      s    	$**Su
-.r   c           	     N
   t        j                  d      }|j                  dd       |j                  ddd	       |j                  d
dd	       |j                  dt        j                  j                  d      xs t        j                         d	       |j                  dd dd       |j                  dddd       |j                  ddd       |j                  |       }t        |j                        j                         }|j                         st        d| t        j                         y|j                  r#t        |j                        j                         n!t        j                          j                         }|j                         st        d| t        j                         y	 t#        |j$                  |j&                  |      }|s>d#|j0                  |j&                  |j$                  d$d$d%}t3        ||j4                         y$t7        ||j0                        }	 t9        |j0                  |j&                  |j$                  ||      }	|d(   sW|d)   d$kD  rd*nd+}
|
|j0                  |j&                  |j$                  t;        |      |d)   d$d,}t3        ||j4                         y$|j<                  r]|j4                  s t        t?        j@                  |	d-.             t        d/d0jC                  |d1          t        j                         y2	 tE        |j0                  |	|      }tG        |j0                  |	|      }d(|j0                  |d1   tK        |jM                  |            tK        |jM                  |            d2d4}t3        ||j4                         t        d!|	d5    t        j                         y2# t(        j*                  $ r t        d t        j                         Y yt,        $ r(}t        d!| t        j                         Y d }~yd }~wt.        $ r(}t        d"| t        j                         Y d }~yd }~ww xY w# t(        j*                  $ r t        d&t        j                         Y yt.        $ r(}t        d'| t        j                         Y d }~yd }~ww xY w# tH        $ rT}t        d3| t        j                         |j4                  s t        t?        j@                  |	d-.             Y d }~yd }~ww xY w)6Nz5Detect alien-task commits on the current task branch.)descriptionr}   u   현재 task id (예: task-2459))helpz--branchr   u   검사 대상 ref (기본 HEAD))defaultr   z--basezorigin/mainu$   비교 base ref (기본 origin/main)z--workspaceWORKSPACE_ROOTu   워크스페이스 루트 — evidence/freeze 파일 저장 경로 결정용. git 조회는 --git-dir 또는 cwd 에서 수행한다.z	--git-dirrS   u   git 조회 디렉토리 (task worktree 경로). 미지정 시 현재 cwd 를 사용. workspace 와 분리되어 운영 환경의 main repo root vs task worktree 혼동을 방지한다.)r   destr   z--json
store_true	json_onlyu7   dry-run: stdout JSON만, evidence/freeze 저장 안 함)actionr   r   z--quietu   정상 stdout 메시지 억제)r   r   z-[mixed-commit-detector] workspace not found: )filer   z+[mixed-commit-detector] git_dir not found: z)[mixed-commit-detector] git log timed outz[mixed-commit-detector] z*[mixed-commit-detector] unexpected error: emptyr   )statusr}   r   rQ   	n_commitsr   z/[mixed-commit-detector] git rev-parse timed outz.[mixed-commit-detector] payload build failed: rf   rk   clean_with_untaggedclean)r   r}   r   rQ   r   rk   r   Fr   z0[mixed-commit-detector] DRY-RUN mixed detected: r   rg   rC   z9[mixed-commit-detector] failed to write marker/evidence: )r   r}   rg   freezer   r   r   )'argparseArgumentParseradd_argumentr&   environgetgetcwd
parse_argsr   r~   resolveexistsr   sysrJ   rS   r7   rZ   basebranchr;   TimeoutExpiredrI   r,   r}   r   r   rx   r   rN   r   r(   r   r   r   r   r.   r$   relative_to)argvparserr=   r~   rS   rV   excrW   r   r   r   freeze_pathevidence_pathsummarys                 r   mainr     s   $$KF 	(IJ

F9Z[
-.T   

/0?BIIKF	   F  	 F	   ,-M   T"DT^^$,,.I;I;G	
  /3lld4<< ((*
@R@R@TG>>9'C	
 !$))T[['B"  ||++		
 	c4::GT\\2H LL$++tyy'8
 G /014 " 	 ||++		W%-.E%F
 	c4:: ~~zz$**W59:>yy-0124	

 )$,,K&t||WiH <<.k--i8911)<=G 
'4:: 

"7+?#@"ABZZ S $$ 7	
  (.SZZ@ 8>	
 4 $$ ?cjjQ <SEB	
 T  GuM	

 zz$**W59:sa   ;!O 3-Q$ &.S .Q!Q!
P--Q!9QQ!$.SSR??S	T$A
TT$__main__)returnr$   )r/   r   r0   dictr   None)   )r=   	list[str]r7   r   r:   intr   zsubprocess.CompletedProcess)rQ   r$   rR   r$   rS   r   r   
list[dict])rE   r$   r   r   )rV   r   rq   r$   r   r   )r}   r$   r0   r   r~   r   r   r   )rS   r   r   
str | None)r   r$   rS   r   r   r   )r}   r$   r   r$   rQ   r$   rS   r   r   r   r   r   )r   r   r   ro   r   r   )N)r   zlist[str] | Noner   r   )#__doc__
__future__r   r   r(   r&   rer;   r   r"   r   r   pathlibr   typingr   r   compiler\   r   r   r4   r>   rZ   r^   rx   r   r   r   r   r   r   r   __name__exitr   r   r   <module>r      s    #   	 	  
  '    
"**)
*E
A
&!H$&R*** * 	*
 * 
*Z/bJ zCHHTV r   