
     j)                    D   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m	Z	m
Z
 ddlmZmZ ddlmZ h dZh dZe
 G d	 d
             Ze
 G d d             ZddZ	 	 d	 	 	 	 	 	 	 ddZdd	 	 	 	 	 	 	 ddZ	 ddd	 	 	 	 	 ddZddZddZdd dZedk(  r e        yy)!u   divergence_guard.py — local main ↔ origin/main divergence 측정 + dispatch HOLD 판정(fail-closed).

task-2700 Phase 1 모듈 1.
    )annotationsN)asdict	dataclass)datetimetimezone)Path>   finish-taskcodingcallbacksecurity>      문서	   리서치	read-onlydocsreportmonitorwatcherdiagnosedocumentreadonlyresearch	diagnosis	read_onlydocumentationc                  T    e Zd ZU ded<   ded<   ded<   ded<   ded<   ded	<   d
ed<   y)DivergenceResultintaheadbehindstr	local_sha
origin_shabooldivergedmeasured
str | NoneerrorN__name__
__module____qualname____annotations__     G/home/jay/workspace/.worktrees/task-2729-dev6/utils/divergence_guard.pyr   r   !   s&    JKNONNr.   r   c                  @    e Zd ZU ded<   ded<   ded<   ded<   ded<   y	)
HoldDecisionr#   holdr    reasonclassification	task_kindr   
divergenceNr(   r-   r.   r/   r1   r1   ,   s    
JKN  r.   r1   c                    t        j                  dd|g| ddd      }|j                  dk7  ry|j                  j	                         xs dS )u&   ref의 SHA를 반환. 실패 시 None.gitz	rev-parseT   cwdcapture_outputtexttimeoutr   N)
subprocessrun
returncodestdoutstrip)	repo_rootrefresults      r/   resolve_sharG   9   sQ    ^^	S!F A== (D(r.   c                   |r	 t        j                  g d| ddd       t        | d      xs d}t        | |      xs d}t        j                  ddd	d
| dg| ddd      }|j                  dk7  r0|j
                  xs dj                         }t        dd||dd|      S |j                  j                         }	 |j                         }t        |      dk7  rt        d|      t        t        |      \  }	}
|
dkD  xs |	dkD  }t        |
|	|||dd      S # t        $ r Y w xY w# t        $ r%}t        dd||ddt        |            cY d}~S d}~ww xY w)uA  local main ↔ remote_ref 의 divergence 측정.

    do_fetch=True 면 git fetch origin 먼저 시도(실패해도 계속).
    git rev-list --left-right --count <remote_ref>...HEAD 사용:
      left = behind (remote 쪽), right = ahead (HEAD 쪽).
    측정 실패 시 fail-closed: measured=False, diverged=True.
    )r8   fetchoriginT   r:   HEAD r8   zrev-listz--left-rightz--countz...HEAD   r   zgit rev-list failedF)r   r   r!   r"   r$   r%   r'      zunexpected output: N)r?   r@   	ExceptionrG   rA   stderrrC   r   rB   splitlen
ValueErrormapr   r    )rD   
remote_refdo_fetchr!   r"   
rev_resulterroutpartsr   r   excr$   s                r/   measure_divergencer^   G   s    		NN*# Iv.4"IY
39rJ 	
NI*W7MNJ !  9$9@@B!
 	
 


!
!
#C
		u:?23':;;C qy&FQJH _  		F  	
!c(
 	
	
s/   D <?D) 	D&%D&)	E2EEETfail_closedc               <   | t         vrt        dd| dd| |      S |j                  s/|rt        dd|j                   d| |      S t        dd	d
| |      S |j                  r*t        dd|j
                   d|j                   d| |      S t        ddd
| |      S )ui   task_kind + divergence 결과로 HOLD 여부 판정.

    fail-closed: 측정 실패 시에도 HOLD.
    Fz
task_kind=u.    is not in DIVERGENCE_GATE_TASK_KINDS — passNON_GATED_KIND_PASS)r2   r3   r4   r5   r6   Tz-divergence measurement failed (fail-closed): MEASUREMENT_FAILED_HOLDu<   divergence measurement failed but fail_closed=False — pass
CLEAN_PASSz/local main is diverged from origin/main: ahead=z	, behind=DIVERGENCE_HOLDu5   local main is in sync with origin/main — clean pass)DIVERGENCE_GATE_TASK_KINDSr1   r%   r'   r$   r   r   )r5   r6   r`   s      r/   should_holdrg      s     22	},Z[0!
 	
 FzGWGWFXY8#%   U+#%  #))*)J4E4E3FH -!	
 		
 F# r.   )	task_filec               D   g }| rHdD ]C  }| j                  |      }t        |t              s%|j                  |j	                                E |r<	 t        |      j                  dd      }|j                  |j	                                dj                  |      }| rcdD ]^  }| j                  |d      }t        |t              s&|j	                         j                         }|t        v r|c S |t        v s|dk(  s^ y t        D ]  }	|	|v s y y	# t        $ r Y w xY w)
u   task_meta dict 또는 task_file 내용에서 task kind 추론.

    기본값은 안전측 "coding"(게이트 대상).
    read_only/diagnosis/문서/리서치 키워드만 있으면 "read_only" 등 비게이트 반환.
    )kindr5   typemode	task_modetitledescriptionutf-8replace)encodingerrors )rj   r5   rk   rM   r   r
   )get
isinstancer    appendlowerr   	read_textrQ   joinrC   rf   _READ_ONLY_KEYWORDS)
	task_metarh   textskeyvalcontentcombinedfieldvkws
             r/   classify_task_kindr      s     E] 	*C--$C#s#SYY[)	*
 	9o///SGLL) xxH 2 	'E--r*C#s#IIK%%'22H++qK/?&	' " > +  		s   ;D 	DDc                D   t        |       | dz  }|j                  j                  dd       t        |      }t	        j
                  t        j                        j                         |d<   |j                  t        j                  |dd      d	       t        |      S )
u]   <events_dir>/<task_id>.divergence-hold.json 에 decision을 JSON으로 기록. 경로 반환.z.divergence-hold.jsonT)parentsexist_ok
written_atrP   Findentensure_asciirp   )rr   )r   parentmkdirr   r   nowr   utc	isoformat
write_textjsondumpsr    )
events_dirtask_iddecisionpathpayloads        r/   write_hold_markerr     s    
	)>??DKKdT2XG$LL6@@BGLOODJJwquEPWOXt9r.   c                 "   t        j                  d      } | j                  ddd       | j                  ddd       | j                  d	d d
       | j                  ddd       | j                  ddd       | j                  dd d       | S )Nz5Measure local/origin/main divergence and decide HOLD.)ro   z--repo-rootTzgit repo root path)requiredhelpz	--task-idztask ID (e.g. task-2700)z--task-kindz"task kind (default: auto-classify))defaultr   z--remote-reforigin/mainzremote ref to compare againstz--fetch
store_truez%run git fetch origin before measuring)actionr   z--events-dirz#directory to write hold marker JSON)argparseArgumentParseradd_argument)ps    r/   _build_parserr     s    K	A NN=46JNKNN;4NNONN=$5YNZNN>=?^N_NN9\8_N`NN>46[N\Hr.   c                   t               }|j                  |       }t        j                  j	                  |j
                        }t        ||j                  |j                        }|j                  r|j                  n	t               }t        ||d      }d }|j                  r-|j                  r!t        |j                  |j                  |      }|j                  ||j                  |j                   |j"                  t%        |      |d}t'        t)        j*                  |dd             t-        j.                  |j                  rd       y d	       y )
N)rW   rX   Tr_   )r   r5   r2   r4   r3   r6   marker_pathrP   Fr      r   )r   
parse_argsosr   abspathrD   r^   rW   rI   r5   r   rg   r   r2   r   r   r4   r3   r   printr   r   sysexit)	argvparserargsrD   divr5   r   r   outputs	            r/   mainr   ,  s    _FT"D/I Y4??TZZ
XC #'..6H6JI 9ct<H #K8=='xP <<"11//Sk"F 
$**VAE
:; HH(--Q'Q'r.   __main__)rD   r    rE   r    returnr&   )r   F)rD   r    rW   r    rX   r#   r   r   )r5   r    r6   r   r`   r#   r   r1   )N)r|   zdict | Nonerh   r&   r   r    )r   r    r   r    r   r1   r   r    )r   zargparse.ArgumentParser)r   zlist[str] | Noner   None)__doc__
__future__r   r   r   r   r?   r   dataclassesr   r   r   r   pathlibr   rf   r{   r   r1   rG   r^   rg   r   r   r   r   r)   r-   r.   r/   <module>r      s'   #   	  
 ) ' N      ! ! !)  $LLL L 	Ln 	;; ; 	;
 ;F "+ !++ + 		+d	  (F zF r.   