
    i{_                    L   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	m	Z	m
Z
 ddlmZ  eej                  j                  dd            ZddZddd	Z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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         yy)*u   start_task_guard.py — 봇 작업 시작 단계 가드 시스템

task-2452 silent corruption 재발 방지를 위한 가드.
8개(실제 9개) 검증을 순서대로 수행하고, 첫 실패 즉시 exit 1.
    )annotationsN)datetimetimezone)PathWORKSPACE_ROOTz/home/jay/workspacec                 f    t        j                  t        j                        j	                  d      S )u(   UTC ISO 8601 타임스탬프 (Z suffix).z%Y-%m-%dT%H:%M:%SZ)r   nowr   utcstrftime     I/home/jay/workspace/.worktrees/task-2524-dev5/scripts/start_task_guard.py_nowr      s!    <<%../CDDr   c                j    t        d|  t        j                         t        j                  |       y)u   stderr 출력 후 exit.z[GUARD ERROR] fileN)printsysstderrexit)msgcodes     r   _dier   "   s"    	N3%
 szz2HHTNr   c                     t        d|         y)u   stdout 성공 메시지.z[GUARD OK] N)r   r   s    r   _okr   (   s    	Ku
r   c                @    t        d|  t        j                         y)u   stderr 경고 메시지.z[GUARD WARN] r   N)r   r   r   r   s    r   _warnr   -   s    	M#
cjj1r   c                   | j                   j                  dd       t        j                  | j                   d      \  }}	 t	        j
                  |dd      5 }t        j                  ||dd	
       ddd       t	        j                  ||        y# 1 sw Y    xY w# t        $ r' 	 t	        j                  |        # t        $ r Y  w xY ww xY w)u1   tempfile + os.replace 를 이용한 atomic write.T)parentsexist_okz.tmp)dirsuffixwutf-8encodingF   )ensure_asciiindentN)parentmkdirtempfilemkstemposfdopenjsondumpreplace	ExceptionunlinkOSError)pathdatafdtmp_pathfs        r   _atomic_writer<   2   s    KKdT2##FCLB	YYr31 	=QIIdAE!<	=


8T"	= 	=  	IIh 	  		sH   B  B5B  BB   	C*C ?C 	C	CCCc                P    t        j                  dg| z   t        |      dd|      S )u"   git 명령 실행 (timeout 30초).gitT)cwdcapture_outputtexttimeout)
subprocessrunstr)argsr?   rB   s      r   _git_runrG   B   s,    >>	$H r   c                    t        j                         }|dz  dz  }||  dz  }| t               ||d}|r|j                  |       	 t	        ||       y# t
        $ r}t        d|        Y d}~yd}~ww xY w)u   실패 evidence JSON 저장.memoryeventsz.start-guard-fail.json)task_id	timestampfailed_checkreason   evidence 저장 실패: N)r   r?   r   updater<   r4   r   )	rK   rN   	check_numextrar?   
events_direvidence_pathr8   es	            r   _save_fail_evidencerV   Q   s     ((*Cx(*JG9,B!CCMV!	D E.mT* .(,--.s   A 	A6A11A6c                    t        j                         }|dz  dz  }||  dz  }| |t               |||dd}	 t        ||       y# t        $ r}	t        d|	        Y d}	~	yd}	~	ww xY w)u   성공 evidence JSON 저장.rI   rJ   z.start-guard.jsonPASS)rK   botrL   checks_passedworktreebranchresultrO   N)r   r?   r   r<   r4   r   )
rK   rY   rZ   r[   r\   r?   rS   rT   r8   rU   s
             r   _save_success_evidencer^   e   s}    
((*Cx(*JG9,=!>>MV&D.mT* .(,--.s   A 	A&A!!A&c           	        t        j                         }|  d| }t        dz  }||z  }t        |      }|j                  j
                  dk(  r|j
                  |k(  s-d| d| }t        | |d|t        |      d       t        |       t        d| d       |t        k(  s|t        t              k(  r d	| }t        | |d
d|i       t        |       t        d       t        ddg|      }|j                  dk7  r5d|j                  j                          }t        | |d       t        |       |j                  j                         }	d|  d| }
|	|
k7  r$d|	 d|
 }t        | |d|	|
d       t        |       t        d|	 d       |	}|j                  dd      d   }d| }|j                  |      r|dt!        |        }n|}|| k7  r$d| d|  }t        | |d|| d       t        |       t        d| d       t        g d|      }|j                  dk7  r5d|j                  j                          }t        | |d        t        |       |j                  j                         j                  d!      }d"}|D ]P  }|j#                  d#      s|t!        d#      d j                         }t        |      |k(  st        |      |k(  sNd$} n |s d%| }t        | |d d|i       t        |       t        d&       t        d'd(g|      }|j                  dk7  r5d)|j                  j                          }t        | |d*       t        |       |j                  j                         }|r d+| }t        | |d*d,|i       t        |       t        d-       t        ddgt              }|j                  dk7  r5d.|j                  j                          }t        | |d/       t        |       |j                  j                         }|d0k7  r d1| }t        | |d/d2|i       t        |       t        d3d4gt              }t        d3d5gt              }|j                  dk7  s|j                  dk7  rd6}t        | |d/       t        |       |j                  j                         }|j                  j                         }||k7  r$d7| d8| }t        | |d/||d9       t        |       t        d:|dd;  d       |	|
k7  r$d<|	 d|
 }t        | |d;|	|
d       t        |       |	j#                  d      r)|	|
k7  r$d=|	 d|
 }t        | |d;|	|
d       t        |       t        d>|	 d       |d?z  d@z  |  dAz  }|j%                         r)dB| }t        | |dCdDt        |      i       t        |       t        dE       t'        | |||	       t)        | |dC||	       t+        |        t        dF|  dG|        y)Hu"   --task --bot 메인 모드 검증.-z
.worktreesuU   [검증 #1 실패] 현재 디렉토리가 전용 worktree가 아닙니다.
  현재: u   
  기대:    )r?   expectedu*   검증 #1 통과: worktree 경로 확인 ()uB   [검증 #2 실패] 메인 워크스페이스에서 작업 금지: r(   r?   uC   검증 #2 통과: 메인 워크스페이스 외부에서 작업 중r\   z--show-currentr?   r   u5   [검증 #3 실패] git branch --show-current 실패:    ztask/uP   [검증 #3 실패] 현재 branch가 올바른 형식이 아닙니다.
  현재: )current_branchexpected_branchu(   검증 #3 통과: branch 형식 확인 (/Nu[   [검증 #4 실패] branch의 task-id와 --task 인자가 불일치.
  branch에서 추출: u   
  --task 인자:    )branch_task_idarg_task_idu)   검증 #4 통과: branch task-id 일치 ()r[   list--porcelainu-   [검증 #5 실패] git worktree list 실패:    
Fz	worktree TuT   [검증 #5 실패] git worktree list에 해당 worktree 미존재.
  탐색 경로: u3   검증 #5 통과: git worktree list에서 확인됨statusrm   u&   [검증 #6 실패] git status 실패:    uM   [검증 #6 실패] working tree가 clean하지 않습니다.
  변경사항:
dirty_filesu$   검증 #6 통과: working tree cleanuC   [검증 #7 실패] 메인 워크스페이스 branch 확인 실패:    mainu^   [검증 #7 실패] 메인 워크스페이스가 main branch가 아닙니다.
  현재 branch: main_branch	rev-parseHEADorigin/mainu<   [검증 #7 실패] HEAD 또는 origin/main SHA 조회 실패u^   [검증 #7 실패] 메인 워크스페이스 HEAD가 origin/main과 불일치.
  HEAD:        z
  origin/main: )main_head_shaorigin_main_shauA   검증 #7 통과: 메인 워크스페이스 main == origin/main (   uS   [검증 #8 실패] 현재 HEAD가 올바른 task branch가 아닙니다.
  현재: uL   [검증 #8 실패] 다른 task 브랜치가 현재 HEAD입니다.
  현재: u-   검증 #8 통과: HEAD branch 일치 확인 (rI   rJ   z
.cancelleduU   [검증 #9 실패] 취소된 task는 시작할 수 없습니다.
  cancelled 마커: 	   cancelled_markeru*   검증 #9 통과: .cancelled 마커 없음u   가드 통과 완료. task=z, bot=)r   r?   r   rE   r+   namerV   r   r   rG   
returncoder   stripstdoutsplitendswithlen
startswithexists_write_lock_filer^   _run_taskctl)rK   rY   r?   expected_dir_nameworktree_baseexpected_worktree_pathcwd_strrN   r]   rf   rg   branch_partsafter_slashr#   rj   worktree_linesfound_worktreelinewt_pathstatus_outputmain_branch_resultmain_current_branchmain_head_resultorigin_main_resultry   rz   cancelled_paths                              r   run_main_guardr   }   s   
((*C #)1SE*"\1M*->> #hGJJOO|+<M0M 	 "/02 	
 	GVQSQgMh0ijV
4WIQ?@
 n3~+> >UV]U^_GVQ0@AV
MO
 x!12<FAHI\I\I^H_`GVQ/V]]((*NgYau-O('( )()+ 	
 	GVQ>fu0vwV
2>2B!DE "L$$S!,Q/KYFF#$^F|4$ &&4%5 6  'y* 	
 	GVQ>bi0jkV
3N3C1EF
 9sCFA@ATATAV@WXGVQ/V]]((*006NN ??;'3{+,-335GG}#tG}8N'N!%   'y* 	 	GVQ0@AV
=?
 x/S9FA9&--:M:M:O9PQGVQ/VMM'')M  -0 	 	GVQ0NOV./ "8-=">NS$$)VWiWpWpWvWvWxVyzGVQ/V,3399;f$  346 	 	GVQ@S0TUV  f 5>J!;">NS""a'+=+H+HA+MOGVQ/V$++113M(//557O'+_ --.0 	
 	VQ+P	
 	V
KMZ\[\L]K^^_`a ('( )()+ 	
 	GVQ>fu0vwV   )n.O'( )()+ 	
 	GVQ>fu0vwV
77GqIJ
 8^h.G9J1GGN##1"24 	 	GVQ1CSEX0YZV
46
 Wc7N;7CG^D
%gYfSE:;r   c                   t        j                         }|dz  dz  }||  dz  }t               }| |t        j                         ||||d}	 t        ||       t        d|        y# t        $ r}	t        d|	        Y d}	~	yd}	~	ww xY w)u   lock 파일 생성..taskslocks.lock)rK   rY   pidheartbeat_timestamp
started_atr[   r\   u   lock 파일 생성: u   lock 파일 생성 실패: N)	r   r?   r   r/   getpidr<   r   r4   r   )
rK   rY   r[   r\   r?   	locks_dir	lock_pathtsr8   rU   s
             r   r   r   _  s    
((*Ch(Iwiu--I	Byy{!D1i&"9+./ 1+A3/001s   A' '	B0BBc                   t        j                         }|dz  dz  }|j                         st        d       y	 t	        j
                  dt        |      d| gt        |      d      }|j                  d	k7  rt        d
|j                   d       yy# t        j                  $ r t        d       Y yt        $ r}t        d| d       Y d}~yd}~ww xY w)u!   taskctl run 호출 (best-effort).scriptsz
taskctl.pyu9   scripts/taskctl.py 미존재 — taskctl 호출 건너뜀Npython3rD      )r?   rB   r   u#   taskctl run 비정상 종료 (code=u   ) — lock/evidence는 유지u5   taskctl run timeout (30s) — lock/evidence는 유지u   taskctl run 실패: u    — lock/evidence는 유지)
r   r?   r   r   rC   rD   rE   r   TimeoutExpiredr4   )rK   r?   taskctl_pathr]   rU   s        r   r   r   u  s    
((*C?\1L IJFL)5':C

 !78I8I7JJghi "$$ GEF F$QC'CDEEFs   AB C2C:CCc                    t        j                         }|dz  dz  |  dz  }|j                         s*d| }t        | |dt	        |      |d       t        |       t        d| d|        t        | |       y	)
u   --takeover-from 모드.rI   handoffsz.jsonu*   [TAKEOVER 실패] handoff JSON 미존재: r   )handoff_pathtakeover_fromu#   handoff JSON 확인 (takeover_from=): N)r   r?   r   rV   rE   r   r   r   )rK   rY   r   r?   r   rN   s         r   run_takeoverr     s    
((*C>J.G9E1BBL =l^L .O		
 	V
-m_C~NO7C r   c                `   ddl }t        j                         }t        g d|      }|j                  dk7  r&t        d|j                  j                                 |j                  j                         }|s t        d       t        j                  d       |j                  d      }|j                  d      }t               }|D ]D  }|j                  |j                               }	|	s%|j!                  |	j#                  d	             F d
}
g }t%        |      d	kD  rd}
|D ]  }|| k7  s	d}
|j'                  |        |
s't        d|xs d        t        j                  d       t        ddg|      }t        ddg|      }|j                  dk(  r|j                  j                         nd}|j                  dk(  r|j                  j                         nd}t)               }|t+        |      |||d}|dz  dz  |  dz  }	 t-        ||       t        d|        |dz  dz  |  dz  }	 t-        ||       d|  dt3        |       dt3        |       d}t        |       y# t.        $ r}t1        d|        Y d}~ad}~ww xY w# t.        $ r}t1        d|        Y d}~jd}~ww xY w) u   mixed commit 검사.r   N)logzorigin/main..HEADz--pretty=format:%srd   u   git log 실패: uA   origin/main..HEAD 범위에 커밋 없음 — mixed commit 없음ro   z^\[(task-\w+)\]ra   FTu'   mixed commit 없음. 감지된 prefix: u   (없음)rv   rx   rw   unknown)detected_atmixed_tasksalien_tasksbase_shahead_shar   r   z.frozenu   freeze 마커 생성: u   freeze 마커 생성 실패: rI   rJ   z.mixed-commit.jsonrO   u9   [check-mixed 실패] mixed commit 감지!
  현재 task: u   
  감지된 prefix: u   
  이질 task: u0   
  자동 복구 금지 — 수동 처리 필요)rer   r?   rG   r   r   r   r   r   r   r   r   r   compilesetmatchaddgroupr   appendr   rl   r<   r4   r   sorted)rK   r   r?   r]   
log_outputcommit_messagespatternfound_prefixesr   mmixedr   prefixbase_sha_resulthead_sha_resultr   r   r   frozen_datafrozen_pathrU   rT   s                         r   run_check_mixedr     s   
((*C:F A 3 3 5678$$&JOP &&t,Ojj+,G"uN +MM#))+&qwwqz*+ EK
>Q  'WEv&'
 5n6R
5STU ];EOV4#>O1@1K1Kq1P%%++-V_H1@1K1Kq1P%%++-V_H	BN+"K .7*y-@@K3k;/$[M23
 (NX-7);M0NNM.m[1
! #%n56 7 -. /:	;  	I#  3-aS1223  .(,--.s0   	I( 2J (	J	1JJ		J-J((J-c                 d   t        j                         } | dz  dz  }|j                         s t        d       t	        j
                  d       t        |j                  d            }|s t        d       t	        j
                  d       t        j                  t        j                        }d}d}|D ]  }	 t        |dd	
      5 }t        j                  |      }ddd       j%                  dd      }
	 t        j&                  |
j)                  dd            }||z
  j-                         }||k  r|j%                  d|j.                        }dt1        |       d| d}i ||t3               t1        |      d}| dz  dz  | dz  }	 t5        ||       	 |j7                          t        d|j"                   d| d       |dz  } t        d!| d"       t	        j
                  d       y# 1 sw Y   xY w# t        $ r&}	t!        d|j"                   d|	        Y d}	~	wd}	~	ww xY w# t*        $ r t!        d|j"                   d|
        Y w xY w# t        $ r}	t!        d|	        Y d}	~	d}	~	ww xY w# t        $ r&}	t!        d |j"                   d|	        Y d}	~	d}	~	ww xY w)#u   stale lock 정리.r   r   u:   locks 디렉토리 없음 — 정리할 lock 파일 없음r   z*.locku   lock 파일 없음i  rr%   r&   Nu   lock 파일 읽기 실패 (r   r    Zz+00:00u   heartbeat 파싱 실패 (rK   z
heartbeat u   초 경과 (기준: u   초))cleanup_reason
cleanup_atelapsed_secondsrI   rJ   z.lock-cleanup.jsonrO   u   stale lock 삭제: z (rc   ra   u   lock 삭제 실패 (u&   cleanup-stale 완료. 삭제된 lock: u   개)r   r?   r   r   r   r   rl   globr   r	   r   r
   openr1   loadr4   r   r~   getfromisoformatr3   
ValueErrortotal_secondsstemintr   r<   r5   )r?   r   
lock_filesnow_tsstale_threshold_secondscleaned	lock_filer;   	lock_datarU   heartbeat_strheartbeat_dtelapsedrK   rN   evidence_datarT   s                    r   run_cleanup_staler     s   
((*Ch(IHIinnX./J !\\(,,'F"G (A		iw7 )1 IIaL	) "&;R@	#11-2G2GX2VWL
 L(779,, --	9>>:c'l^+?@W?XX\]

$&"7|	
 h1wi?Q4RR	2-7	A%inn%5RxqABqLGM(AT 
0	=>HHQKS) ) 	/	/?s1#FG	  	-inn-=SPQ	*  	2,QC011	2  	A((8A3?@@	Asr   7G?G2G?6%H1I1J 2G<	7G??	H.H))H.1$II	I=%I88I= 	J/	J**J/c                    t        j                         }|dz  dz  |  dz  }|j                         st        d|        	 t	        |dd      5 }t        j                  |      }ddd       t               d
<   	 t        ||       t        d|        t        j                  d       y# 1 sw Y   GxY w# t        $ r}t        d	|        Y d}~yd}~ww xY w# t        $ r}t        d|        Y d}~bd}~ww xY w)u   heartbeat 갱신.r   r   r   u1   [update-heartbeat 실패] lock 파일 미존재: r   r%   r&   Nu   lock 파일 읽기 실패: r   u   heartbeat 갱신: u   heartbeat 갱신 실패: r   )r   r?   r   r   r   r1   r   r4   r   r<   r   r   r   )rK   r?   r   r;   r   rU   s         r   run_update_heartbeatr   =  s    
((*Ch(gYe+<<I@LM)S73 	+q"iilI	+ (,vI#$.i+ ,- HHQK	+ 	+ *1#./  .(,--.sH   B8 B,&B8 <C ,B51B8 8	CCC	C=%C88C=c                 H   t        j                  dt         j                        } | j                  ddd       | j                  ddd	       | j                  d
ddd       | j                  dddd       | j                  dddd       | j                  dddd       | S )Nu:   봇 작업 시작 가드 시스템 (task-2454 Phase 1 MVP))descriptionformatter_classz--taskTASK_IDu   task ID (예: task-2454))metavarhelpz--botBOTu   bot 이름 (예: dev4)z--takeover-fromBRANCHr   u   takeover 시작 branch)r   destr   z--check-mixed
store_truecheck_mixedu   mixed commit 검사 모드)actionr   r   z--cleanup-stalecleanup_staleu   stale lock 정리 모드z--update-heartbeatupdate_heartbeatu   heartbeat 갱신 모드)argparseArgumentParserRawDescriptionHelpFormatteradd_argument)parsers    r   _build_parserr   [  s    $$P <<F ):TU
5MN
)8/5  7
=9  ;
),_7  9
,\HZ6  8Mr   c                    t               } | j                         }|j                  rt                y |j                  r3|j
                  s| j                  d       t        |j
                         y |j                  r3|j
                  s| j                  d       t        |j
                         y |j                  rU|j
                  r|j                  s| j                  d       t        |j
                  |j                  |j                         y |j
                  r-|j                  r!t        |j
                  |j                         y | j                          t        j                   d       y )Nu;   --update-heartbeat 모드에는 --task 가 필요합니다.u6   --check-mixed 모드에는 --task 가 필요합니다.uB   --takeover-from 모드에는 --task 와 --bot 이 필요합니다.ra   )r   
parse_argsr   r   r   taskerrorr   r   r   r   rY   r   r   
print_helpr   r   )r   rF   s     r   rt   rt   m  s    _FD yyLLVWTYY'yyLLQR		"yyLL]^TYY$*<*<=yyTXXtyy$((+
HHQKr   __main__)returnrE   )ra   )r   rE   r   r   r  None)r   rE   r  r  )r7   r   r8   dictr  r  )r   )rF   z	list[str]r?   r   rB   r   r  zsubprocess.CompletedProcess)N)
rK   rE   rN   rE   rQ   r   rR   zdict | Noner  r  )rK   rE   rY   rE   rZ   r   r[   rE   r\   rE   r  r  )rK   rE   rY   rE   r  r  )
rK   rE   rY   rE   r[   rE   r\   rE   r  r  )rK   rE   r  r  )rK   rE   rY   rE   r   rE   r  r  )r  r  )r  zargparse.ArgumentParser)"__doc__
__future__r   r   r1   r/   rC   r   r-   r   r   pathlibr   environr   r   r   r   r   r   r<   rG   rV   r^   r   r   r   r   r   r   r   r   rt   __name__r   r   r   <module>r     s   
 #   	  
  '  bjjnn%57LMNE

2
 .(.0_<D1,F2!,Nj=H<$%P zF r   