
    i(                       d 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	Z	ddl
m
Z
mZmZ ddlmZ ddlmZ  e ee      j&                  j&                        Zeej*                  vrej*                  j-                  de       ddlmZ 	 ddlmZ  eej:                  j=                  d e ee      jA                         j&                  j&                                    d	z  Z!e!jE                  d
d
        e ed            Z#d/dedejH                  fdZ% e%       Z& eej:                  j=                  de            dz  dz  Z'dede(ddfdZ)dedede(fdZ*	 	 	 d0dedededz  dedz  dedefdZ+d1dedededz  de,fdZ-deded e,dedef
d!Z. G d" d#      Z/ G d$ d%      Z0 G d& d'      Z1 G d( d)      Z2 G d* d+      Z3dejh                  fd,Z5d2d-Z6e7d.k(  r e6        yy# e$ rP ej:                  j=                  de      Zeej*                  vrej*                  j-                  de       ddlmZ Y w xY w)3u  
auto_merge.py - 자동 머지 처리 스크립트

.done 파일을 스캔하여 merge_needed=True인 작업을 자동으로 머지하고
테스트를 실행한 후 결과를 로깅합니다.

사용법:
    python3 scripts/auto_merge.py                        # 전체 자동 실행
    python3 scripts/auto_merge.py --task-id task-391.1   # 특정 task만
    python3 scripts/auto_merge.py --dry-run              # 드라이런
    python3 scripts/auto_merge.py --force-merge task-391.1  # 강제 머지
    N)datetime	timedeltatimezone)Path)Any)parse_report)ConfigManagerWORKSPACE_ROOTlogsTparentsexist_ok	   )hoursnamereturnc                    t        j                  |       }|j                  r|S |j                  t         j                         t        j                  t              j                  d      }t        d| dz  }t        j                  |d      }|j                  t         j                         t        j                  t        j                        }|j                  t         j                         t        j                  dd      }|j!                  |       |j!                  |       |j#                  |       |j#                  |       |S )	u<   일별 로그 파일 + 콘솔에 출력하는 로거 생성.z%Y-%m-%dauto_merge_z.logutf-8encodingz'%(asctime)s [%(levelname)s] %(message)s%Y-%m-%dT%H:%M:%S)datefmt)logging	getLoggerhandlerssetLevelDEBUGr   now_KSTstrftimeLOGS_DIRFileHandlerStreamHandlersysstdoutINFO	FormattersetFormatter
addHandler)r   loggertodaylog_filefhch	formatters          C/home/jay/workspace/.worktrees/task-2374-dev7/scripts/auto_merge.py_setup_loggerr2   5   s    t$F
OOGMM"LL''
3EKwd33H			X	8BKK			szz	*BKK!!"KUhiIOOIOOI
b
bM    memorystatepathdatac                    ddl }| j                  j                  dd       |j                  t	        | j                        d| j
                   d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)u>   rename pattern으로 JSON 파일을 atomic하게 생성한다.r   NTr   .z.tmp)dirprefixsuffixwr   r   F   ensure_asciiindent)tempfileparentmkdirmkstempstrr   osfdopenjsondumprename	ExceptionunlinkOSError)r6   r7   rB   fdtmpfs         r1   _atomic_write_jsonrR   Y   s    KKdT23t{{#3a		{!<LU[\GB	YYr31 	=QIIdAE!<	=
		#t	= 	=  	IIcN 	  		sH   B8 3B,B8 ,B51B8 8	C(CC(	C$!C(#C$$C(task_idfieldsc                 d   t         |  dz  }| ddddddddddddd}|j                         rG	 t        j                  |j	                  d	            }t        |t              r|j                  |       |j                  |       t        ||       |S # t        j                  t        f$ r Y :w xY w)
ub   memory/state/{task_id}.json 갱신 (없으면 생성). 기존 필드 보존 + fields 덮어쓰기.z.jsonunknowncodeTNr   )rS   phasekindmerge_requiredteamtimestamp_work_donetimestamp_mergedtimestamp_merge_failedproject_pathpr_urlmerge_commit_sharetry_count
last_errorr   r   )
_STATE_DIRexistsrI   loads	read_text
isinstancedictupdateJSONDecodeErrorrN   rR   )rS   rT   
state_filer5   existings        r1   _update_state_jsonrn   j   s    	//J# "& E 	zz*"6"6"6"HIH(D)X& 
LLz5)L	 $$g. 		s   AB B/.B/
events_dirra   branchreasonc                     | | dz  }|j                         r|S |t        j                  t              j	                  d      |||d}t        ||       t        |d|d   |       |S )us   .merged 마커 atomic 생성 + state.json `phase: merged` 갱신.

    이미 존재하면 no-op (idempotent).
    z.merged%Y-%m-%dT%H:%M:%S+09:00)rS   	merged_atra   rp   rq   mergedrt   )rX   r]   ra   re   r   r   r    r!   rR   rn   )ro   rS   ra   rp   rq   merged_filepayloads          r1   _create_merged_markerry      s     ''22K\\$'001JK,G {G, -)	 r3   rc   c                     t        ||      }t        |j                  dd            dz   }t        |||       | | dz  }	 |j                  t	        |      d       |S # t
        $ r Y |S w xY w)	ui   retry_count를 1 증가시키고 반환한다. state.json 우선, 없으면 .retry_count 파일 fallback.rc   rb   r      rb   rc   z.retry_countr   r   )rn   intget
write_textrF   rN   )ro   rS   rc   r5   	new_count
retry_files         r1   _bump_retry_counterr      s    w:>EEIImQ/014IwI*M	66Jc)nw?   s   A! !	A.-A.rb   c           	          | | dz  }|j                         r|S t        j                  t              j	                  d      }||||r|dd ndd}t        ||       t        |d|||r|dd nd       |S )	u   .merge-failed 마커 atomic 생성 + state.json `phase: merge-failed` 갱신.

    이미 존재하면 no-op (idempotent).
    z.merge-failedrs   N  )rS   	failed_atrb   rc   zmerge-failed  )rX   r^   rb   rc   rv   )ro   rS   rb   rc   failed_filetsrx   s          r1   _create_merge_failed_markerr      s     '-88K	d		$	$%>	?B"+5j$'4	G {G,!'1:ds#t r3   c                   f   e Zd ZdZej
                  j                  d e ee	      j                         j                  j                              dddfdedededz  dedz  d	df
d
Zd	ee   fdZded	efdZded	eeef   fdZded	eeef   fdZdedeeef   d	edz  fdZdededed	eeef   fdZded	eeef   fdZded	efdZdeded	dfdZded	dfdZdedeeef   d	dfdZeded	efd       Zedeeef   d	edz  fd       Zd	eeef   fdZd	eeef   fd Z deded!eded	df
d"Z!y)#
AutoMergeru   자동 머지 처리기.r
   FNworkspace_pathdry_runforce_task_idtarget_task_idr   c                     t        |      | _        | j                  dz  dz  | _        | j                  dz  dz  | _        | j                  dz  dz  | _        || _        || _        || _        y )Nr4   eventsreportszmerge-log.json)r   	workspacero   reports_dir	merge_logr   r   r   )selfr   r   r   r   s        r1   __init__zAutoMerger.__init__   sh     n-..83h>>>H4y@(25EE*,r3   c                    | j                   j                         s$t        j                  d| j                           g S g }t	        | j                   j                  d            D ]W  }|j                  d      }|j                  d      }|j                         r6|j                         rG|j                  |       Y t        j                  dt        |       d       |S )uI   미처리 .done 파일 스캔: .done.clear가 없는 것만 반환한다.u   events_dir 없음: *.done.done.clear.done.mergingu   미처리 .done 파일 
   개 발견)
ro   re   r+   debugsortedglobwith_suffixappendinfolen)r   
done_files	done_file
clear_filemerging_files        r1   scan_done_fileszAutoMerger.scan_done_files   s    %%'LL.t.?@AI!#
 4 4X >? 	-I"..}=J$00AL$$&|/B/B/D!!),	- 	-c*o->jIJr3   r   c                    |j                  d      }	 t        |dd      5 }|j                  t        j                  t        j                  t              j                         dd             ddd       t        j                  d|j                          y	# 1 sw Y   ,xY w# t        $ r% t        j                  d
|j                          Y yw xY w)u   원자적 .done.merging 생성으로 처리 선점.

        Returns:
            True: 선점 성공
            False: 이미 선점됨 (FileExistsError)
        r   xr   r   auto_merge.py)
claimed_at
claimed_byNu   선점 성공: Tu   이미 선점됨: F)r   openwriterI   dumpsr   r   r    	isoformatr+   r   r   FileExistsError)r   r   r   rQ   s       r1   	try_claimzAutoMerger.try_claim  s     !,,_=	lC': aJJ*2,,t*<*F*F*H*9 LL?<+<+<*=>?   	LL-l.?.?-@AB	s)   B% AB.*B% B"B% %+CCc                    	 t        j                  |j                  d            }|j                  d      s|j                  |d<   |j                  dd       |S # t         j                  $ rc}d|j                   d| }t
        j                  |       |j                  }| j                  ||       t        d|j                         |d}~ww xY w)	uq   JSON 파싱 → task_id, team_id 등 추출.

        비정상 JSON → escalate 후 ValueError 발생.
        r   r   u   JSON 파싱 실패:  - NrS   merge_neededF)rI   rf   rg   rk   r   r+   errorstemescalate
ValueErrorr   
setdefault)r   r   rawexcrq   rS   s         r1   
parse_donezAutoMerger.parse_done   s    
	O**Y00'0BCC wwy!&^^C	N 	~u-
 ## 	O+INN+;3seDFLL nnGMM'6*3INN3CDE3N	Os   %A C.ACCrS   c                 @   | j                   | dz  }|j                         sOt        | j                   j                  | d            }|r|d   }n t        j                  d|        |dddg dS t        j                  d|        t        t        |            }|S )	u   report_parser.py의 parse_report()로 보고서 분석.

        Returns:
            merge_needed, merge_branch, merge_worktree 포함 dict
        .md*.mdr   u   보고서 파일 없음: FN)rS   r   merge_branchmerge_worktreefilesu   보고서 분석: )	r   re   listr   r+   warningr   r   rF   )r   rS   report_file
candidatesresults        r1   analyze_reportzAutoMerger.analyze_report<  s     &&G9C8!!#d..33wit4DEFJ(m!:7)DE&$)$(&*  	)+78c+./r3   report_datac                    |j                  d      }|rCt        j                  d|      }|r+|j                  d      }t        j                  d|        |S |j                  dg       }|D ]G  }t        j                  d|      }|s|j                  d      }t        j                  d|        |c S  | j                  dz  dz  | d	z  }|j                         rX|j                  d
      }	t        j                  d|	      }|r.d|j                  d       }t        j                  d|        |S t        j                  d|        y)u  프로젝트 경로 해결 순서:
        1. merge_worktree에서 .worktrees 이전 경로 역추적
        2. files 목록에서 /home/jay/projects/... 패턴 추출
        3. task 메타 파일에서 추출 (memory/tasks/{task_id}.md)
        4. 못 찾으면 None
        r   (/home/jay/projects/[^/]+)r|   u+   worktree에서 프로젝트 경로 추출: r   u(   files에서 프로젝트 경로 추출: r4   tasksr   r   r   z /home/jay/projects/([^/\s`'\"]+)z/home/jay/projects/u.   task 메타에서 프로젝트 경로 추출: *   프로젝트 경로를 찾을 수 없음: N)r   rematchgroupr+   r   r   re   rg   searchr   )
r   rS   r   worktreemr_   r   rQ   	task_metacontents
             r1   resolve_project_pathzAutoMerger.resolve_project_path[  sE    ??#346AA wwqzJ<.YZ## , 	$A6:A wwqzG~VW##	$ NNX-7WIS/I	))7);G		=wGA!4QWWQZLAMl^\]##CG9MNr3   r_   team_idc           	         t        t              j                  dz  }t        j                  t        |      d|||ddg}t        j                  d| d| d|        t        j                  |ddd	
      }|j                  j                         }	 |rt        j                  |      ni }|j                  dk7  r?|j!                  d|j"                  j                         xs |      }	t%        d| d|	       t        j                  d| d|j!                  dd              |S # t        j                  $ r d|i}Y w xY w)u   worktree_manager.py finish --action merge 실행.

        Raises:
            RuntimeError: 머지 실패 (충돌 등)
        zworktree_manager.pyfinishz--actionmergeu   머지 실행:  (z) @ Tx   capture_outputtexttimeoutr   r   messageu   머지 실패 []: u   머지 성공: u    → rp   ?)r   __file__rC   r%   
executablerF   r+   r   
subprocessrunr&   striprI   rf   rk   
returncoder   stderrRuntimeError)
r   r_   rS   r   worktree_managercmdr   outputparsed	error_msgs
             r1   execute_mergezAutoMerger.execute_merge  s=     >003HHNN !	
 	ogYb	l^LM	
 $$&	%+1TZZ'rF !

9fmm.A.A.C.MvNI	YKHIIogYeFJJx4M3NOP ## 	%V_F	%s   D$ $D>=D>c           	      2   t        j                         }t        |      }t        j                  d      rg d}d}nd|dz  j                         rK	 t        j                  |dz  j                  d            }d|j                  di       v rg d}d	}nd
dddS nd
dddS 	 t        j                  ||d
d
d      }t        j                         |z
  }|j                  |j                  z   }	|j                   dk(  }
t"        j%                  d| d|
rdnd d|dd       |
|	|dS # t        j                  t        f$ r	 d
dddcY S w xY w# t        j&                  $ r: t        j                         |z
  }d| }t"        j)                  |       d||dcY S w xY w)u   프로젝트 테스트 실행.

        - pytest 있으면 pytest
        - package.json의 test 스크립트 있으면 npm test
        - 타임아웃 300초

        Returns:
            {"passed": bool, "output": str, "duration": float}
        pytest)r   
--tb=short-qzpackage.jsonr   r   testscripts)npmr   z--z--ciznpm testTu)   테스트 스크립트 없음 (건너뜀)        )passedr   durationu&   package.json 파싱 실패 (건너뜀)u#   테스트 러너 없음 (건너뜀)i,  cwdr   r   r   r   u   테스트 결과 [r   PASSFAILr   .1fs)u#   테스트 timeout (300초 초과): F)time	monotonicr   shutilwhichre   rI   rf   rg   r   rk   rN   r   r   r&   r   r   r+   r   TimeoutExpiredr   )r   r_   
start_timeprojectr   runnerpkgr   r   r   r   msgs               r1   	run_testszAutoMerger.run_tests  s    ^^%
|$ <<!0CF&..0mjj'N":!E!Ew!E!WXSWWY337C'F&*6aorss  #.Sadee	J^^ #F ~~'*4H]]V]]2F&&!+FKK,VHC&f7UUWX`adWeeghi$HMM# (('2 m"&2Zhkllm& (( 	J~~'*4H7~FCLL#sII		Js'   AD$ !BE	 $EE	A
FFc                    t         j                  d|        	 t        j                  g d|ddd      }|j                  dk(  rt         j                  d|        yt         j                  d|j                  j                                 y	# t        j                  t        f$ r"}t         j                  d
|        Y d}~y	d}~ww xY w)u   머지 실패 시 되돌리기: git reset --hard HEAD~1.

        Returns:
            True: revert 성공
            False: revert 실패
        u   머지 revert 시도: )gitreset--hardzHEAD~1T   r   r   u   머지 revert 성공: u   머지 revert 실패: Fu   머지 revert 예외: N)r+   r   r   r   r   r   r   r   r   r	  rN   )r   r_   r   r   s       r1   revert_mergezAutoMerger.revert_merge  s     	/~>?	^^4 #F   A%4\NCD5fmm6I6I6K5LMN))73 	LL1#78	s   AB 0B C
(CC
rq   c           
      \   t         j                  d| d|        | j                         }|j                  d      xs# t	        j
                         j                  d      }|j                  d      }|st         j                  d       yd| d	| d
t        j                  t              j                  d       }t        j                  t              t        d      z   j                  d      }dd|d|d|d|dg
}	 t        j                  |ddd      }	|	j                  dk(  rt         j!                  d|        yt         j                  d|	j"                  j%                                 y# t        j&                  t(        f$ r"}
t         j                  d|
        Y d}
~
yd}
~
ww xY w)u   아누에게 에스컬레이션 통보.

        .env.keys에서 CHAT_ID, KEY 로드 후 cokacdir --cron으로 1분 후 통보.
        u   에스컬레이션: r   COKACDIR_CHAT_IDchat_idCOKACDIR_KEY_ANUu9   COKACDIR_KEY_ANU 미설정 - 에스컬레이션 건너뜀Nz[auto_merge] u:    처리 중 문제 발생 - 수동 확인 필요.
사유: 	   
시각: rs   r|   )minutesr   cokacdir--cron--at--chat--key--onceT
   r   r   u"   에스컬레이션 등록 완료: u   cokacdir 등록 실패: u   cokacdir 실행 예외: )r+   r   _load_env_keysr   _CfgMgrget_instanceget_constantr   r   r   r    r!   r   r   r   r   r   r   r   r	  rN   )r   rS   rq   env_keysr  anu_keypromptat_timer   r   r   s              r1   r   zAutoMerger.escalate  s   
 	-gYc&BC&&(,,12dg6J6J6L6Y6YZc6d,,12LLTU G9 %h ||D)223LMNP 	 <<%	!(<<FFGZ[ 
	;^^C4QSTF  A%@	JK!9&--:M:M:O9PQR))73 	;LL3C59::	;s   >A E0 ?0E0 0F+	F&&F+c           
         | j                         }|j                  d      xs# t        j                         j	                  d      }|j                  d      }|st
        j                  d       y| j                   d}| d| d| d	}d
d|ddd|d|dg
}	 t        j                  |ddd      }|j                  dk(  rt
        j                  d|        yt
        j                  d|j                  j                                 y# t        j                  t         f$ r"}	t
        j                  d|	        Y d}	~	yd}	~	ww xY w)uC   머지 성공 후 아누에게 완료 통보를 스케줄링한다.r  r  r  u4   COKACDIR_KEY_ANU 미설정 - 아누 통보 건너뜀Nz+/scripts/completion-handler-instructions.mdu    완료 처리. u    읽고 task_id=u   로 실행하라.r  r  r  1mr  r   r!  Tr"  r   r   u   아누 통보 등록 완료: u   아누 통보 등록 실패: u   아누 통보 예외: )r#  r   r$  r%  r&  r+   r   r   r   r   r   r   r   r   r   r	  rN   )
r   rS   r'  r  r(  instructions_pathr)  r   r   r   s
             r1   
notify_anuzAutoMerger.notify_anu6  sC   &&(,,12dg6J6J6L6Y6YZc6d,,12LLOP#~~..YZ9,->,??OPWyXij 
	9^^C4QSTF  A%;G9EF!>v}}?R?R?T>UVW))73 	9LL1#788	9s   A D	 0D	 	E"D??Er   c                 V   d|vr*t        j                  t              j                         |d<   d|vr||d<   | j                  j                         r1	 t        j                  | j                  j                  d            }ndg i}|d   j                  |       | j                  j                  j                  dd       | j                  j                  t        j                  |dd	
      d       t        j!                  d|        y# t        j                  $ r dg i}Y w xY w)u8   merge-log.json에 결과 추가 (기존 entries 유지).	timestamprS   r   r   entriesTr   Fr>   r?   u   merge-log.json 기록: N)r   r   r    r   r   re   rI   rf   rg   rk   r   rC   rD   r   r   r+   r   )r   rS   r   log_datas       r1   
log_resultzAutoMerger.log_result]  s    f$"*,,t"4">">"@F;F" 'F9 >>  "++/::dnn6N6NX_6N6`+a "2H""6*##D4#@!!$**XERS"T_f!g.wi89 '' +%r?+s   /D D('D(c                 2    | j                  d      r| dd S | S )u   team_id → team_short 변환. 'dev1-team' → 'dev1', 'dev2-team' → 'dev2'.

        이미 short 형식이면 그대로 반환.
        z-teamN)endswith)r   s    r1   _team_id_to_shortzAutoMerger._team_id_to_shortx  s#     G$3B<r3   c                    | j                  d      }|r)t        j                  d|      }|r|j                  d      S | j                  d      }|r8t        j                  d|j	                  d            }|r|j                  d      S y)u   보고서 데이터에서 team_short를 역추출한다.

        merge_branch: 'task/task-391.1-dev1' → 'dev1'
        merge_worktree: '/home/jay/projects/App/.worktrees/task-391.1-dev1' → 'dev1'
        r   z
-(dev\d+)$r|   r   /N)r   r   r   r   rstrip)r   rp   r   r   s       r1   _extract_team_short_from_reportz*AutoMerger._extract_team_short_from_report  sw     0		-0Awwqz! ??#34		-)=>Awwqz!r3   c                    | j                   dz  }i }|j                         st        j                  d|        |S |j	                  d      j                         D ]  }|j                         }|r|j                  d      r'|j                  d      r|t        d      d }d|v sK|j                  d      \  }}}|j                         ||j                         <    |S )	u+   .env.keys 파싱 (export KEY=VALUE 형식).	.env.keysu   .env.keys 없음: r   r   #export N=)
r   re   r+   r   rg   
splitlinesr   
startswithr   	partition)r   env_filer   linekey_values          r1   r#  zAutoMerger._load_env_keys  s    >>K/!# LL-hZ89M&&&8CCE 	4D::<D4??3/y)C	N,-d{ $s 3Q&+kkmsyy{#	4 r3   c                    ddddd}| j                   rJ| j                  | j                    dz  }|j                         st        j	                  d|        |S |g}n| j                         }|D ]z  }|j                  }t        j                         }	 | j                  |      }|j                  d|      }|j                  dd	      }	 | j                  |      }	|	j                  d      xs |j                  dd      }|s&t        j%                  d|        |dxx   dz  cc<   | j&                  |k7  r7| j)                  |      sXt        j%                  d|        |dxx   dz  cc<   |j+                  d      }|j                         s| j)                  |       |dxx   dz  cc<   | j-                  |      }| j/                  |	      }|r|}| j                  r5t        j%                  d| d| d       | j1                  ||dddd       | j3                  ||	      }|sOd| }t        j                  |       | j!                  ||       |dxx   dz  cc<   | j#                  ||d|       	 | j5                  |||      }| j?                  |      }t        j                         |z
  }|d(   sd)| d*|d+   dd,  }t        j                  |       | jA                  |       | j!                  ||       |dxx   dz  cc<   | j1                  ||d-d||j                  d.      d/|d+   dd0 |d-d1	       |d2xx   dz  cc<   | j1                  ||d3d||j                  d.      d4|d5d6       t        j%                  d7| d8|d9d:       	 tC        | j                  ||j                  d;      |j                  d.      d<=       | j                  rj| jE                  |       } t        j%                  d?|        |S # t        $ r |dxx   dz  cc<   Y w xY w# t        $ rd}
d
|
 }t        j                  |       | j                  s| j!                  ||       |dxx   dz  cc<   | j#                  ||d|       Y d}
~
d}
~
ww xY w# t6        $ r}
t9        |
      }t        j                  d| d|        	 t;        | j                  ||      }|dk\  r2t=        | j                  |||        t        j                  d!| d"       n1# t        $ r%}t        j	                  d#| d$|        Y d}~nd}~ww xY w| j!                  ||       |dxx   dz  cc<   | j1                  ||d%d|d&|t        j                         |z
  d'       Y d}
~
+d}
~
ww xY w# t        $ r&}t        j	                  d>| d$|        Y d}~d}~ww xY w)@ux   메인 실행 루프.

        Returns:
            {"processed": N, "merged": N, "escalated": N, "skipped": N}
        r   )	processedru   	escalatedskipped.doneu   대상 .done 파일 없음: rK  r|   rS   r   dev1u   보고서 분석 실패: report_errorNr   Fu/   머지 불필요, notify-completion에 위임: rL  u   이미 처리 중: r   rJ  u   [DRY-RUN] 머지 대상: z (team=)r   T)rS   actionr   statusr   no_project_pathu   머지 충돌/실패: r   r{      r}   u
   [옵션D] u+    5회 retry 초과 → .merge-failed 생성u&   [옵션D] retry/marker 갱신 실패 (z): merge_failedr   )rS   rQ  r   r  rR  r   duration_secondsr   u$   테스트 실패 후 머지 revert: 
r   r   revertedrp   failr   )	rS   rQ  r   r  rp   test_resulttest_outputrV  rR  ru   auto_mergedpasssuccess)rS   rQ  r   r  rp   rZ  rV  rR  u   완료: u    머지 성공 (r  r  ra   auto_merge_success)ra   rp   rq   u(   [옵션D] .merged 마커 생성 실패 (u   처리 완료: )#r   ro   re   r+   r   r   r   r  r  r   r   r   r   rL   r   r   r   _finalize_done_filer   r   r   r   r7  r;  r3  r   r   r   rF   r   r   r  r  ry   r.  )r   statsr   r   rS   r
  	done_dataactual_task_idr   r   r   rq   r   r   
team_shortreport_team_shortr_   merge_resultretry_n
marker_excrZ  r   s                         r1   r   zAutoMerger.run  s    /011YZ [ T-@-@,A*GGI##%!=i[IJ#J--/J# o	0InnG)J OOI6	
 ']]9g>NmmIv6G	"11.A "-!@!hIMMR`bgDhLMnM]^_i A%  !!W,~~i0KK"5gY ?@)$)$ '22=A
!((*NN9-+!# //8J $ D D[ Q .
 ||77GwzlZ[\]"#1"+(,"+	   44^[QLEnEUVV$nf5k"a'"((NDUW]^##11,PZ[J ..6K~~'*4Hx(??OrR]^fRghlilRmQnoV$!!,/nf5k"a'""#1",(,#/"."2"28"<'-'28'<Ud'C,4",
  (Oq OOO-+$(+*..x8#)(0'	 KK(>"22B8C.PRST	k%OO"%1%5%56H%I'++H5/ <</_o	0f 	oeW-.[  k"a'"  4SE:V$||MM.&9k"a'"((NNTZ[@   !S5n5ESQRm1$//>^deG!|3 OO*(/'-	 z.1AAl%mn  mNN%KNK[[^_i^j#kllm nf5k"a'""#1"0(,#/")!',0NN,<z,I C!r  k!I.IYY\]g\hijjks   PP%9R8V#P"!P"%	R.ARR
V &VATV	UT?:V?UAVV #	W,WWrQ  c           
      F    | j                  |||d|t        |      d       y)u[   처리 완료 후 로그만 기록. .done 파일은 삭제하지 않음 (아누가 처리).rL  )rS   rQ  rR  rq   r   N)r3  rF   )r   r   rS   rQ  rq   s        r1   r`  zAutoMerger._finalize_done_filez  s)    " #  ^		
r3   )"__name__
__module____qualname____doc__rG   environr   rF   r   r   resolverC   boolr   r   r   r   ri   r   r   r   r   r   r  r  r   r.  r3  staticmethodr7  r;  r#  r   r`   r3   r1   r   r      sV   " !jjnn-=s4>CYCYC[CbCbCiCi?jk$(%)-- - Tz	-
 d
- 
-&d *4 D 8D T#s(^ 8c d38n >$C $d38n $QTW[Q[ $T%# % %c %dSVX[S[n %V0Jc 0Jd38n 0Jl  <,; ,;S ,;T ,;d!9# !9$ !9N:# :tCH~ :$ :6 3 3   T#s(^ d
  ,S#X 2FT#s(^ FP
T 
C 
 
VY 
^b 
r3   r   c                       e Zd ZdZddedz  ddfdZdee   fdZdedefdZ	dded	e
dee   fd
Zdde
dee   fdZdde
defdZdedee   ddfdZdee   fdZy)BatchWatchdoguA   L1: .done 파일 수집 → batch_id 기준 전팀 완료 감지.Nr   r   c           
      4   t        |xsX t        j                  j                  dt	        t        t
              j                         j                  j                                    | _        | j                  dz  dz  | _	        | j                  dz  dz  | _
        y )Nr
   r4   r   ztask-timers.json)r   rG   rn  r   rF   r   ro  rC   r   ro   
timer_filer   r   s     r1   r   zBatchWatchdog.__init__  sq    kbjjnn-=s4>CYCYC[CbCbCiCi?jk
 ..83h>..836HHr3   c                    | j                   j                         s$t        j                  d| j                           g S g }t	        | j                   j                  d            D ]  }|j                  d      }|j                  d      }|j                         s|j                         rF	 t        j                  |j                  d            }|j                  d      }|s|j                  d|j                         |j                  |        t        j!                  d	t#        |       d
       |S # t        j                  t        f$ r Y w xY w)u   memory/events/*.done 파일 수집 → JSON 파싱 → batch_id가 있는 것만 반환.

        .done.clear가 없는 것만 처리 (기존 AutoMerger.scan_done_files 패턴 참조).
        u#   [BatchWatchdog] events_dir 없음: r   r   r   r   r   batch_idrS   u-   [BatchWatchdog] batch_id 포함 .done 파일 r   )ro   re   r+   r   r   r   r   rI   rf   rg   rk   rN   r   r   r   r   r   r   )r   resultsr   r   r   r   ry  s          r1   r   zBatchWatchdog.scan_done_files  s*   
 %%'LL>t>OPQI  4 4X >? 	 I"..}=J$00AL  "l&9&9&;jj!4!4g!4!FG wwz*HNN9inn5NN3!	 $ 	CCL>Q[\] (('2 s   ,%D88EEry  c                    | j                   j                         sdddg |dS 	 t        j                  | j                   j	                  d            }|j                  di       }|j                         D ci c]  \  }}|j                  d	      |k(  s|| }}}t        |      }|dk(  rdddg |dS d}	g }
|j                         D ]2  \  }}|j                  d
d      }|dv r|	dz  }	"|
j                  |       4 |	|k(  }|||	|
|dS # t        j
                  t        f$ r*}t        j                  d|        dddg |dcY d}~S d}~ww xY wc c}}w )u   task-timers.json을 직접 읽어 batch_id 기준 완료 체크.

        Returns:
            {"complete": bool, "total": int, "done": int, "pending": list[str], "batch_id": str}
        Fr   )completetotaldonependingry  r   r   u0   [BatchWatchdog] task-timers.json 읽기 실패: Nr   ry  rR   r~  	completedr|   )rv  re   rI   rf   rg   rk   rN   r+   r   r   itemsr   r   )r   ry  r7   r   r   tidr   matchedr}  
done_countr  rR  r|  s                r1   check_batch_completionz$BatchWatchdog.check_batch_completion  sr    %%' %1Yabb	c::doo777IJD
 "%.3kkm`dtxx
?SW_?_39``GA: %1Yabb
  	$ICXXh+F..a
s#	$ &  
 	
+ $$g. 	cNNMcUST %1Yabb	c
 as)   /D 8EEE$E	E	E	ttl_hoursc                 `   | j                   j                         sg S 	 t        j                  | j                   j	                  d            }t        j                  t        j                        }|j                  di       }g }|j                         D ]  \  }}	|	j                  d      |k7  r|	j                  dd      }
|
d	v r2|	j                  d
      }|sF	 t        j                  |      }|j                    |j#                  t        j                        }||z
  j%                         dz  }||k\  s|j+                  |t-        |d      d        |S # t        j
                  t        f$ r$}t        j                  d|        g cY d}~S d}~ww xY w# t&        t(        f$ r Y w xY w)u   TTL 2시간 초과 task 감지.

        task-timers.json에서 batch_id 일치하는 task 중
        start_time이 ttl_hours 이상 경과한 running task를 반환한다.

        Returns:
            [{"task_id": str, "elapsed_hours": float}]
        r   r   u9   [BatchWatchdog] TTL 체크 중 timer_file 읽기 실패: Nr   ry  rR  r  r  r
  tzinfo      @r>   rS   elapsed_hours)rv  re   rI   rf   rg   rk   rN   r+   r   r   r   r   utcr   r  fromisoformatr  replacetotal_secondsr   	TypeErrorr   round)r   ry  r  r7   r   r   r   staler  r   rR  	start_rawstart_dtr  s                 r1   	check_ttlzBatchWatchdog.check_ttl  s    %%'I	::doo777IJD
 ll8<<("% 	YICxx
#x/XXh+F...I#11)<??*'//x||/DH!$x > > @6 I 	)u]TU?VWX+	Y. ? $$g. 	NNVWZV[\]I	2 	* s0   /E AFF3FFFF-,F-batch_ttl_hoursc                     | j                   j                         sg S 	 t        j                  | j                   j	                  d            }t        j                  t        j                        }|j                  di       }i }|j                         D ]<  \  }}|j                  d      }	|	s|j                  |	g       j!                  ||f       > g }
|j                         D ]  \  }}d}|D ]d  \  }}|j                  d      }|s	 t        j"                  |      }|j$                   |j'                  t        j                        }|||k  r|}f |u||z
  j-                         d	z  }||k  rg }d
}|D ]  \  }}|j                  dd      }|dvsd||   d<   t        j                  t.              j1                         ||   d<   |j!                  |       d}t        j                  d| d| d|dd        |rO	 | j                   j3                  t        j4                  |d
d      d       t        j7                  d| d|        |ss|
j!                  ||t;        |d      d        |
S # t        j
                  t        f$ r$}t        j                  d|        g cY d}~S d}~ww xY w# t(        t*        f$ r Y w xY w# t        $ r"}t        j9                  d|        Y d}~d}~ww xY w)u  batch 전체의 생성 시점 기반 자동 만료 감지.

        task-timers.json에서 batch_id 기준 모든 task를 조회하고,
        해당 batch의 가장 오래된 start_time을 batch 생성 시점으로 간주한다.
        batch_ttl_hours 경과 시 미완료 task를 "expired" 상태로 마킹한다.

        Returns:
            [{"batch_id": str, "expired_tasks": list[str], "elapsed_hours": float}]
        r   r   u;   [BatchWatchdog] check_batch_ttl: timer_file 읽기 실패: Nr   ry  r
  r  r  FrR  r  )r~  r  expiredr  
expired_atTu+   [BatchWatchdog] batch TTL 만료: batch_id=z
, task_id=
, elapsed=.2fhr>   r?   u?   [BatchWatchdog] task-timers.json 업데이트 완료: batch_id=z
, expired=u0   [BatchWatchdog] task-timers.json 쓰기 실패: )ry  expired_tasksr  )rv  re   rI   rf   rg   rk   rN   r+   r   r   r   r   r  r   r  r   r   r  r  r  r   r  r  r    r   r   r   r   r   r  )r   r  r7   r   r   r   	batch_mapr  r   bidexpired_resultsry  	task_list	oldest_dtrG  r  r  r  r  changedrR  s                        r1   check_batch_ttlzBatchWatchdog.check_batch_ttl  s#    %%'I	::doo777IJD
 ll8<<("% 8:	 	BIC((:&C$$S"-44c4[A	B
 ')#,??#4 8	Hi)-I$ 4 HH\2	 '55i@H.#+#3#38<<#3#H (Hy,@$,	   9_;;=FM. (*MG& 
	T(B/!AA+4E#Jx(/7||D/A/K/K/ME#J|,!((-"GNNEhZ P##&%z-1DAG
 [OO..tzz$U[\/]ho.pKK$$,:ZH &&$,)6).}a)@e8	t U $$g. 	NNXY\X]^_I	: #I. B  [LL#STWSX!YZZ[sI   /I< A
J<AK<J9J4.J94J9<KK	K=K88K=c                    | j                  |      }|st        j                  d       dg dS g }|D ]H  }|d   }|d   }|d   }t        j                  d| d	| d
|dd       | j                  j                         r|D ]  }| j                  | dz  }	|	j                         s&|	j                  d      }
|
j                         rH	 |
j                  t        j                  |||t        j                  t              j                         d| dddd      d       t        j                  d|
j                           |D cg c]  }||d	 }}| j%                  ||       |j'                  |       K t)        |      |dS # t         $ r0}t        j#                  d|
j                   d|        Y d}~?d}~ww xY wc c}w )u6  만료된 batch를 정리한다.

        1. check_batch_ttl()로 만료된 batch 탐지
        2. 만료된 batch의 .done 파일에 .done.expired 마커 생성
        3. send_ttl_warning()으로 아누에게 경고

        Returns:
            {"expired_count": int, "expired_batches": list[str]}
        )r  u&   [BatchWatchdog] 만료된 batch 없음r   )expired_countexpired_batchesry  r  r  u.   [BatchWatchdog] batch 만료 정리: batch_id=z, expired_tasks=r  r  r  rM  z.done.expiredz
batch TTL u   h 초과)ry  rS   r  r  rq   Fr>   r?   r   r   u&   [BatchWatchdog] 만료 마커 생성: u-   [BatchWatchdog] 만료 마커 생성 실패: r   Nr  )r  r+   r   r   ro   re   r   r   rI   r   r   r   r    r   r   r   rN   r   send_ttl_warningr   r   )r   r  expired_listexpired_batch_idsitemry  r  r  r  r   expired_markerr   stale_tasks_for_warns                r1   cleanup_expired_batchesz%BatchWatchdog.cleanup_expired_batches}  s    ++O+LLLAB%&2>>')  '	/D ,H'+O'<M#'#8MNN@
 K!!.z-9LAO %%'( |C $SE- ?I ''))2)>)>)O-446| . 9 9$(JJ8@7:=J:B,,t:L:V:V:X8B?BSS[6\)* 6;/0
%& .5 !: !" !'.TUcUhUhTi,j k)|2 an#nY\m$T#n #n!!(,@A$$X.O'	/T !!230
 	
 $+ | &/\]k]p]p\qqtuxty-z { {| $os   A:FG		G%GGstale_tasksc           
      T   | j                   dz  }i }|j                         r|j                  d      j                         D ]  }|j	                         }|r|j                  d      r'|j                  d      r|t        d      d }d|v sK|j                  d      \  }}}|j	                         ||j	                         <    |j                  d      xs# t        j                         j                  d	      }	|j                  d
      }
|
st        j                  d       ydj                  d |D              }d| d| dt        j                   t"              j%                  d       }dd|ddd|	d|
dg
}	 t'        j(                  |ddd      }|j*                  dk(  rt        j-                  d|        yt        j/                  d|j0                  j	                                 y# t&        j2                  t4        f$ r"}t        j                  d|        Y d}~yd}~ww xY w) uK   TTL 초과 시 Telegram 경고. AutoMerger의 env key 로드 패턴 사용.r=  r   r   r>  r?  Nr@  r  r  r  uA   [BatchWatchdog] COKACDIR_KEY_ANU 미설정 - TTL 경고 건너뜀, c              3   :   K   | ]  }|d     d|d   dd  yw)rS   (r  r  zh)Nrr  ).0ts     r1   	<genexpr>z1BatchWatchdog.send_ttl_warning.<locals>.<genexpr>  s*     !dSTQy\N!Ao4Fs3K2"N!ds   [BatchWatchdog] batch_id=u     TTL 초과 경고
정체 task: r  rs   r  r  r  r,  r  r   r!  Tr"  r   r   u3   [BatchWatchdog] TTL 경고 등록 완료: batch_id=u3   [BatchWatchdog] cokacdir TTL 경고 등록 실패: u(   [BatchWatchdog] cokacdir 실행 예외: r   re   rg   rA  r   rB  r   rC  r   r$  r%  r&  r+   r   joinr   r   r    r!   r   r   r   r   r   r   r	  rN   )r   ry  r  rD  env_datarE  krG  vr  r(  stale_summaryr)  r   r   r   s                   r1   r  zBatchWatchdog.send_ttl_warning  s    >>K/#%?? **G*<GGI 4zz|ts3??9-I 01D$;"nnS1GAq!*+'')HQWWY'4 ,,12dg6J6J6L6Y6YZc6d,,12LL\]		!dXc!dd'z 2)? +||D)223LMNP 	 
	K^^C4QSTF  A%QRZQ[\]!TU[UbUbUhUhUjTklm))73 	KLLCC5IJJ	Ks   :A G, ;0G, ,H'H""H'c                 x   | j                         }t               }|D ](  }|j                  dd      }|s|j                  |       * g }t	        |      D ]  }| j                  |      }t        j                  d| d|d    d|d    d|d	           | j                  |      }|r*t        j                  d
|        | j                  ||       |d   st        j                  d|        |j                  |        | j                         }	|	d   dkD  r!t        j                  d|	d    d|	d           |S )u4  메인 루프:
        1. .done 수집
        2. 고유 batch_id 추출
        3. 각 batch_id에 대해:
           - check_batch_completion
           - TTL 체크 → 경고
           - 전팀 완료 batch_id 목록 반환 (L2로 전달)

        Returns:
            완료된 batch_id 목록
        ry  r  r  : r~  r9  r}  u    완료, pending=r  u   [BatchWatchdog] TTL 초과: r|  u/   [BatchWatchdog] 전팀 완료 확인: batch_id=r  r   u>   [BatchWatchdog] batch TTL 만료 정리 완료: expired_count=z
, batches=r  )r   setr   addr   r  r+   r   r  r   r  r   r  )
r   
done_itemsseen_batch_idsr  r  completed_batchesry  
completionr  cleanup_results
             r1   r   zBatchWatchdog.run  sq    ))+
 $'5 	(D((:r*C""3'	(
 (*~. 	3H44X>JKK+H:Rf%&a
7(;'< =%i013 ..2K!=k]KL%%h<*%MhZXY!((2	3$ 557/*Q.NN!!/!@ A B)*;<=? ! r3   N)g       @)g      8@)rj  rk  rl  rm  rF   r   r   ri   r   r  floatr  r  r  r  r   rr  r3   r1   rt  rt    s    KIsTz IT Id F&
s &
t &
X-# -% -$t* -fYu YT
 Y~>
u >
 >
H2K 2K4: 2K$ 2Kp1!T#Y 1!r3   rt  c                       e Zd ZdZddedz  ddfdZdedee   fdZded	ee   defd
Z	dedee   ddfdZ
dedefdZy)PreFlightChecku>   L2: git merge --no-commit 시뮬레이션으로 충돌 검증.Nr   r   c           
      4   t        |xsX t        j                  j                  dt	        t        t
              j                         j                  j                                    | _        | j                  dz  dz  | _	        | j                  dz  dz  | _
        y )Nr
   r4   r   r   )r   rG   rn  r   rF   r   ro  rC   r   ro   r   rw  s     r1   r   zPreFlightCheck.__init__5  sq    kbjjnn-=s4>CYCYC[CbCbCiCi?jk
 ..83h>>>H4y@r3   ry  c                    g }| j                   j                         s|S t        | j                   j                  d            D ]  }	 t	        j
                  |j                  d            }|j                  d      |k7  r?|j                  d|j                        }|j                  d      }|j                  dd      }|s
| j                  | d	z  }|j                         s.t        | j                  j                  | d
            }	|	r|	d   }|j                         r	 t        t        |            }
|
j                  d      }|s|
j                  d      xs d}t        j                   d|      }|r|j#                  d      }|sB|
j                  dg       D ]-  }t        j                   d|      }|s|j#                  d      } n |s|j+                  |||xs dd        t&        j-                  d| dt/        |       d       |S # t        j                  t        f$ r Y w xY w# t$        $ r%}t&        j)                  d| d|        Y d}~d}~ww xY w)u9  batch_id에 해당하는 task들의 merge branch 목록 수집.

        1. memory/events/{task_id}.done → JSON에서 merge_branch 추출
        2. memory/reports/{task_id}.md → report_parser로 merge_branch 추출

        Returns:
            [{"task_id": str, "branch": str, "project_path": str}]
        r   r   r   ry  rS   r   r_   r  r   r   r   r   r   r|   r   u)   [PreFlightCheck] 보고서 파싱 실패 r  N)rS   rp   r_   [PreFlightCheck] batch_id=u   개 branch 수집)ro   re   r   r   rI   rf   rg   rk   rN   r   r   r   r   r   rF   r   r   r   rL   r+   r   r   r   r   )r   ry  branchesr   r   rS   r   r_   r   r   r   r   r   f_pathm2r   s                   r1   find_merge_branchesz"PreFlightCheck.find_merge_branches@  sN     "%%'O 4 4X >? 0	Ijj!4!4g!4!FG wwz"h.ggi8G 77>2L77>26L  "..G9C@"))+!%d&6&6&;&;wit<L&M!NJ!&0m%%'c&23{3C&D'2~'F+'27G'H'NBH ")F QA /0wwqz#/.9oogr.J !.F)+2OQW)XB')79xx{(-	!. #*".(4(:U0	d 	0
"S]OK\]^a (('2 F % c'PQXPYY[\_[`%abbcs1   %H%BH5:H5H21H25	I#>II#r_   r  c           	         g }t        |      }|j                         s!t        j                  d|        ddg dgdS 	 t	        j
                  g d|ddd	       	 |D ]  }	 t	        j
                  dddd|g|ddd	      }|j                  dk7  rt	        j
                  g d|ddd	      }|j                  j                         D 	cg c]#  }	|	j                         s|	j                         % }
}	|j                  ||
d       t        j                  d| d|
        t	        j
                  g d|ddd	       n8t	        j
                  g d|ddd	       t	        j
                  g d|ddd	        	 	 t	        j
                  g d|ddd	       t        |      dk(  }||dS # t        j                  t        f$ r#}t        j                  d
|        Y d}~d}~ww xY wc c}	w # t        j                  t        f$ r;}t        j                  d|        |j                  |d| gd       Y d}~d}~ww xY w# t        j                  t        f$ r"}t        j                  d|        Y d}~d}~ww xY w# 	 t	        j
                  g d|ddd	       w # t        j                  t        f$ r"}t        j                  d|        Y d}~w d}~ww xY wxY w)uL  git merge --no-commit 시뮬레이션.

        1. git stash (현재 변경 보호)
        2. 각 branch에 대해 git merge --no-commit --no-ff 시도
        3. 충돌 발생 시 git merge --abort
        4. git stash pop

        Returns:
            {"passed": bool, "conflicts": [{"branch": str, "files": list[str]}]}
        u-   [PreFlightCheck] 프로젝트 경로 없음: F__project_not_found__)rp   r   )r   	conflicts)r  stashz--include-untrackedTr  r   u#   [PreFlightCheck] git stash 실패: Nr  r   z--no-commitz--no-ff<   r   )r  diffz--name-onlyz--diff-filter=U   u'   [PreFlightCheck] 충돌 감지: branch=z, files=)r  r   z--abort)r  r  r  HEADu/   [PreFlightCheck] merge 시뮬레이션 예외: z__exception__: )r  r  popu'   [PreFlightCheck] git stash pop 실패: )r   re   r+   r   r   r   r	  rN   r   r&   rA  r   r   r   r   )r   r_   r  r  r  r   rp   r   conflict_files_resultrQ   conflict_filesr   s               r1   simulate_mergezPreFlightCheck.simulate_merge  s    !#	|$~~NNJ<.YZ#>U`b3c2dee		HNN7 #A	P" 3]2]'^^	6J('+! "F ((A-0:M ,+/!%$&1- >S=Y=Y=d=d=f)tjkjqjqjs!''))t)t!((F^)TU)PQWPXX`ao`p'qr #7 ,+/!%$& #7 ,+/!%$& #> ,+/!%$&S3]n	P+$#' Y1$ y99O ))73 	HNN@FGG	H. *u< #117; ]LL#RSVRW!XY$$OTWSXBYAZ%[\\] --w7 P!HNOOP	P+$#' --w7 P!HNOOPs    F, J $A&G0
G+ G+2BG08J =I ,G(G##G(+G00I	0H?9J ?IJ J I==JK"J$#K"$K=KK"KK"r  c           
      Z   | j                   dz  }i }|j                         r|j                  d      j                         D ]  }|j	                         }|r|j                  d      r'|j                  d      r|t        d      d }d|v sK|j                  d      \  }}}|j	                         ||j	                         <    |j                  d      xs# t        j                         j                  d	      }	|j                  d
      }
|
st        j                  d       ydj                  d |dd D              }d| d| dt        j                   t"              j%                  d       }dd|ddd|	d|
dg
}	 t'        j(                  |ddd      }|j*                  dk(  rt        j-                  d|        yt        j/                  d|j0                  j	                                 y# t&        j2                  t4        f$ r"}t        j                  d |        Y d}~yd}~ww xY w)!u"   충돌 시 아누 Telegram 알림.r=  r   r   r>  r?  Nr@  r  r  r  uE   [PreFlightCheck] COKACDIR_KEY_ANU 미설정 - 충돌 알림 건너뜀z; c              3   b   K   | ]'  }|d     ddj                  |d   dd       xs d  ) yw)rp   r  r  r   N   rV   )r  )r  cs     r1   r  z5PreFlightCheck.send_conflict_alert.<locals>.<genexpr>  s:     $wde(}Btyy7BQ7P7]T]6^%_$ws   -/rT  r  u    머지 충돌 감지
충돌: u   
수동 검토 필요
시각: rs   r  r  r  r,  r  r   r!  Tr"  r   r   u7   [PreFlightCheck] 충돌 알림 등록 완료: batch_id=u.   [PreFlightCheck] 충돌 알림 등록 실패: u)   [PreFlightCheck] cokacdir 실행 예외: r  )r   ry  r  rD  r  rE  r  rG  r  r  r(  conflict_summaryr)  r   r   r   s                   r1   send_conflict_alertz"PreFlightCheck.send_conflict_alert  s   >>K/#%?? **G*<GGI 4zz|ts3??9-I 01D$;"nnS1GAq!*+'')HQWWY'4 ,,12dg6J6J6L6Y6YZc6d,,12LL`a99$wirsutuiv$ww(
 3'( )||D)223LMNP 	 
	L^^C4QSTF  A%UV^U_`a!OPVP]P]PcPcPeOfgh))73 	LLLDSEJKK	Ls   =A G/ >0G/ /H*H%%H*c                    | j                  |      }|s t        j                  d| d       dg d|dS i }|D ];  }|j                  d      xs d}|j	                  |g       j                  |d          = g }|j                         D ]M  \  }}|st        j                  d	| d
       "| j                  ||      }	|	d   r:|j                  |	d          O t        |      dk(  }
|
s| j                  ||       |
|t        |      |dS )u   L2 실행:
        1. find_merge_branches
        2. simulate_merge (프로젝트별)
        3. 충돌 시 send_conflict_alert → return {"passed": False, ...}
        4. PASS 시 return {"passed": True, ...}
        r  u   : merge branch 없음Tr   )r   r  branch_country  r_   r  rp   u6   [PreFlightCheck] 프로젝트 경로 없음 (branches=rP  r   r  )r  r+   r   r   r   r   r  r  extendr   r  )r   ry  branch_infosproject_mapr   ppall_conflictsr_   r  
sim_resultr   s              r1   r   zPreFlightCheck.run'  s9    //9NN7zAVWX"QT\]] -/  	BD.)/RB""2r*11$x.A	B %'&1&7&7&9 	>"L(!WX`Waabcd,,\8DJh'$$Z%<=	> ]#q($$X}= &- 	
 	
r3   r  )rj  rk  rl  rm  rF   r   r   ri   r  r  r  r   rr  r3   r1   r  r  2  s    HAsTz AT AAC ADJ ANb:3 b:$s) b: b:P2LC 2LDJ 2L4 2Lp&
C &
D &
r3   r  c                   r    e Zd ZdZddedz  ddfdZddedefdZded	edefd
Z	ded	eddfdZ
dedefdZy)IntegrationTestRunneru%   L3: pytest tests/integration/ 실행.Nr   r   c           
         t        |xsX t        j                  j                  dt	        t        t
              j                         j                  j                                    | _        | j                  dz  dz  | _	        y )Nr
   r4   r   )
r   rG   rn  r   rF   r   ro  rC   r   r   rw  s     r1   r   zIntegrationTestRunner.__init__X  s\    kbjjnn-=s4>CYCYC[CbCbCiCi?jk
  >>H4y@r3   test_dirc           	      L   t        |      }|j                         s| j                  |z  }|j                         s"t        j                  d|        dd| dddS t        j                         }	 t        j                  dt        |      dd	d
gt        | j                        ddd      }t        j                         |z
  }|j                  |j                  z   }|j                  dk(  }d}t        j                  d|      }	|	rt!        |	j#                  d            }t        j                  d|      }
|
r|t!        |
j#                  d            z  }t        j%                  d|rdnd d|dd| d       ||||dS # t        j&                  $ r> t        j                         |z
  }d| }t        j)                  d|        d||ddcY S t*        t,        f$ rE}t        j                         |z
  }d| }t        j)                  d|        d||ddcY d}~S d}~ww xY w)u{   pytest 실행.

        Returns:
            {"passed": bool, "output": str, "duration": float, "test_count": int}
        u7   [IntegrationTestRunner] 테스트 디렉터리 없음: Tu+   테스트 디렉터리 없음 (건너뜀): r   r   )r   r   r   
test_countr   r   r   z--no-headeriX  r   z(\d+)\s+passedr|   z(\d+)\s+failedu    [IntegrationTestRunner] 결과: r  r  r   r  zs, z tests)u*   통합 테스트 timeout (600초 초과): z[IntegrationTestRunner] Fu   pytest 실행 실패: N)r   is_absoluter   re   r+   r   r  r  r   r   rF   r&   r   r   r   r   r~   r   r   r	  r   rN   FileNotFoundError)r   r  	test_pathr
  r   r   r   r   r  r   fail_mr  r   s                r1   r  zIntegrationTestRunner.run_testsb  s(    N	$$&1I!NNTU^T_`aG	{S	  ^^%
$	[^^3y><}M'#F ~~'*4H]]V]]2F&&!+F J		+V4A _
YY0&9Fc&,,q/22
KK2V62P QS>ZL9 %H\fgg(( 	[~~'*4H>ykJCLL3C59:#sXYZZ*+ 	[~~'*4H*3%0CLL3C59:#sXYZZ		[s&   2DF   AH#H#:HH#H#ry  rZ  c                    | j                   j                  dd       | j                   d| dz  }|d   rdnd}t        j                  t              j                  d      }d	| d
| d| d|j                  dd       d|j                  dd      dd| d|j                  dd      dd  d}|j                  |d       t        j                  d|        |S )uG   결과 리포트 생성: memory/reports/batch-{batch_id}-integration.mdTr   zbatch-z-integration.mdr   r  r  rs   z"# Integration Test Report - batch-z

- **batch_id**: z
- **status**: z
- **test_count**: r  r   z
- **duration**: r   r   r  zs
- **generated_at**: z

## Output

```
r   r  Ni  z
```
r   r   u*   [IntegrationTestRunner] 리포트 생성: )
r   rD   r   r   r    r!   r   r   r+   r   )r   ry  rZ  r   rR  now_strr   s          r1   generate_reportz%IntegrationTestRunner.generate_report  s   td;&&6(?)KK&x0f,,t$--.GH 1
 ;'j )#H %!!,q!A B C*z3?D E##*) ,OOHb1%489B 	 	w9@NOr3   c                 @   | j                   dz  }i }|j                         r|j                  d      j                         D ]  }|j	                         }|r|j                  d      r'|j                  d      r|t        d      d }d|v sK|j                  d      \  }}}|j	                         ||j	                         <    |j                  d      xs# t        j                         j                  d	      }	|j                  d
      }
|
st        j                  d       y|d   rdnd}|j                  dd      }|j                  dd      }|d   r9d| d| d| d|ddt        j                  t               j#                  d       
}nP|j                  dd      dd }d| d| d| d|dd| dt        j                  t               j#                  d       }d d!|d"d#d$|	d%|
d&g
}	 t%        j&                  |d'd'd()      }|j(                  dk(  rt        j+                  d*| d+| d,       yt        j-                  d-|j.                  j	                                 y# t$        j0                  t2        f$ r"}t        j                  d.|        Y d}~yd}~ww xY w)/u   아누에게 Telegram 결과 전송.

        PASS: 자동 완료 보고
        FAIL: 실패 로그 → 아누 판단
        r=  r   r   r>  r?  Nr@  r  r  r  uL   [IntegrationTestRunner] COKACDIR_KEY_ANU 미설정 - 결과 전송 건너뜀r   r  r  r  r   r   r   z![IntegrationTestRunner] batch_id=u!    통합 테스트 완료
결과: r   u   개 테스트, r  u.   s)
자동 완료 처리 확인 요청
시각: rs   r   r  iu!    통합 테스트 실패
결과: u   s)
로그(말미):
u   
수동 판단 필요
시각: r  r  r  r,  r  r   r!  Tr"  r   u>   [IntegrationTestRunner] 결과 전송 등록 완료: batch_id=z []u5   [IntegrationTestRunner] 결과 전송 등록 실패: u0   [IntegrationTestRunner] cokacdir 실행 예외: )r   re   rg   rA  r   rB  r   rC  r   r$  r%  r&  r+   r   r   r   r    r!   r   r   r   r   r   r   r	  rN   )r   ry  rZ  rD  r  rE  r  rG  r  r  r(  rR  r  r   r)  output_tailr   r   r   s                      r1   send_resultz!IntegrationTestRunner.send_result  s    >>K/#%?? **G*<GGI 4zz|ts3??9-I 01D$;"nnS1GAq!*+'')HQWWY'4 ,,12dg6J6J6L6Y6YZc6d,,12LLgh&x0f __\15
??:s3x 3H: >!("ZL~ N#<<-667PQRT  &//(B7>K3H: >!("ZL~ N$$/= 1#<<-667PQR	T  
	S^^C4QSTF  A%\]e\ffhiohppqrs!VW]WdWdWjWjWlVmno))73 	SLLKC5QRR	Ss   ,AI" 10I" "J;JJc                     | j                         }| j                  ||      }| j                  ||       ||d   |j                  dd      |j                  dd      t	        |      dS )u8   L3 실행: run_tests → generate_report → send_resultr   r  r   r   r   )ry  r   r  r   report)r  r  r  r   rF   )r   ry  rZ  report_paths       r1   r   zIntegrationTestRunner.run  si    nn&**8[A;/ !(+%//,:#
C8+&
 	
r3   r  )ztests/integration/)rj  rk  rl  rm  rF   r   ri   r  r   r  r  r   rr  r3   r1   r  r  U  s    /AsTz AT A8[# 8[ 8[| $ 4 6CSC CSd CSt CSR
C 
D 
r3   r  c                   8    e Zd ZdZddedz  deddfdZdefdZy)	GraduatedAutoGateu(   3-Layer Auto-Gate 오케스트레이터.Nr   r   r   c           
         t        |xsX t        j                  j                  dt	        t        t
              j                         j                  j                                    | _        || _	        t        t	        | j                              | _        t        t	        | j                              | _        t        t	        | j                              | _        y )Nr
   )r   rG   rn  r   rF   r   ro  rC   r   r   rt  watchdogr  	preflightr  test_runner)r   r   r   s      r1   r   zGraduatedAutoGate.__init__  s    kbjjnn-=s4>CYCYC[CbCbCiCi?jk
 %c$..&9:'DNN(;<0T^^1DEr3   c           
         t         j                  d       | j                  j                         }t         j                  dt	        |       d       t	        |      ddg d}|D ]z  }|ddd}| j
                  r@t         j                  d| d	       d
|d<   |d   j                  |       |dxx   dz  cc<   Vt         j                  d|        | j                  j                  |      }||d<   |d   sJt         j                  d| dt	        |d          d       |dxx   dz  cc<   |d   j                  |       t         j                  d|        | j                  j                  |      }||d<   |d   r't         j                  d| d       |dxx   dz  cc<   n&t         j                  d| d       |dxx   dz  cc<   |d   j                  |       } t         j                  d|d    d|d    d|d           |S ) u  L1 → L2 → L3 순차 실행.

        1. L1: batch watchdog → 완료된 batch_id 목록
        2. 각 batch_id에 대해:
           a. L2: pre-flight check → FAIL이면 alert + skip
           b. L3: integration test → PASS이면 자동 보고, FAIL이면 로그

        Returns:
            {"batches_checked": int, "passed": int, "failed": int, "details": list}
        u,   [GraduatedAutoGate] 3-Layer Auto-Gate 시작u   [GraduatedAutoGate] L1 완료: u   개 batch 처리 대상r   )batches_checkedr   faileddetailsN)ry  l2l3z&[GraduatedAutoGate][DRY-RUN] batch_id=u   : L2/L3 건너뜀Tr   r  r   r|   z2[GraduatedAutoGate] L2 Pre-Flight Check: batch_id=r  z%[GraduatedAutoGate] L2 FAIL batch_id=u	   : 충돌 r  u   건 → skipr  z2[GraduatedAutoGate] L3 Integration Test: batch_id=r  z%[GraduatedAutoGate] L3 PASS batch_id=u   : 자동 완료 보고z%[GraduatedAutoGate] L3 FAIL batch_id=u+   : 테스트 실패 → 아누 판단 대기u$   [GraduatedAutoGate] 완료: checked=r
  z	, passed=z	, failed=)
r+   r   r  r   r   r   r   r  r   r  )r   r  ra  ry  detail	l2_result	l3_results          r1   r   zGraduatedAutoGate.run#  s<    	BC !MM--/5c:K6L5MMdef  ##45	!
 * #	,H2:$d%SF||DXJN_`a$(y!i ''/h1$ KKLXJWX**84I$F4LX&;H:]SVW`alWmSnRoo{| h1$i ''/ KKLXJWX((,,X6I$F4L"CH:Mcdeh1$!FxjP   A  Bh1$)##F+G#	,J 	259J3K2L MHo&ih/@B	
 r3   )NF)	rj  rk  rl  rm  rF   rp  r   ri   r   rr  r3   r1   r  r    s6    2FsTz F4 FTX FAT Ar3   r  c                      t        j                  dd      } | j                  dd       | j                  ddd	
       | j                  ddd       | j                  ddd
       | S )u   argparse 파서 구성.r   u!   자동 머지 처리 스크립트)progdescriptionz	--task-idu'   특정 task만 처리 (예: task-391.1))helpz	--dry-run
store_trueu,   드라이런: 실제 동작 없이 분석만)rQ  r  z--force-mergeTASK_IDu&   강제 머지 (이미 처리됐어도))metavarr  z--graduateduh   3-Layer Graduated Auto-Gate 실행 (L1 BatchWatchdog → L2 PreFlightCheck → L3 IntegrationTestRunner))argparseArgumentParseradd_argument)parsers    r1   build_parserr  l  s}    $$7F *ST
L?mn
Aij
w  
 Mr3   c                  X   t               } | j                         }|j                  r't        |j                        }|j                         }n<t        |j                  |j                  |j                        }|j                         }t        t        j                  |dd             y)u   CLI 메인 진입점.)r   )r   r   r   Fr>   r?   N)r  
parse_args	graduatedr  r   r   r   rS   force_mergeprintrI   r   )r  argsgater   mergers        r1   mainr&  }  sz    ^FD~~ 6LL<<**

 	$**V%
:;r3   __main__)
auto_merge)NNmerge_observedr  )r   N)8rm  r  rI   r   rG   r   r  r   r%   r  r   r   r   pathlibr   typingr   rF   r   rC   _ws_rootr6   insertreport_parserr   config.loaderr	   r$  ImportErrorrn  r   	_env_rootro  r"   rD   r    Loggerr2   r+   rd   ri   rR   rn   ry   r~   r   r   r   rt  r  r  r  r  r  r&  rj  rr  r3   r1   <module>r3     s      	 	   
  2 2   tH~$$++,388HHOOAx  &76 

/T(^5K5K5M5T5T5[5[1\]^agg td +	"# w~~ 4 
 "**..!18<=H7R
T  $ " s t B $(" Dj $J	
  
>D 3 C$J Z]   	
 
Hl

 l

j\! \!H[
 [
F	z
 z
DM Mjh-- "<& zF Y;  7

/:I 9%667s   G- -AII