
    qi             
          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& G d d      Z' G d d      Z( G d d      Z) G d d      Z* G d d      Z+dejX                  fdZ-ddZ.e/dk(  r e.        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 7w xY w) u  
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          )/home/jay/workspace/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    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 )Nmemoryeventsreportszmerge-log.json)r   	workspace
events_dirreports_dir	merge_logr7   r8   r9   )selfr6   r7   r8   r9   s        r1   __init__zAutoMerger.__init__Y   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 파일 
   개 발견)
r?   existsr+   debugsortedglobwith_suffixappendinfolen)rB   
done_files	done_file
clear_filemerging_files        r1   scan_done_fileszAutoMerger.scan_done_filesl   s    %%'LL.t.?@AI!#
 4 4X >? 	-I"..}=J$00AL$$&|/B/B/D!!),	- 	-c*o->jIJr3   rR   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)
        rG   xr   r   auto_merge.py)
claimed_at
claimed_byNu   선점 성공: Tu   이미 선점됨: F)rM   openwritejsondumpsr   r   r    	isoformatr+   rJ   r   FileExistsError)rB   rR   rT   fs       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 파싱 실패:  - Ntask_idmerge_neededF)r]   loads	read_textJSONDecodeErrorr   r+   errorstemescalate
ValueErrorget
setdefault)rB   rR   rawexcreasonre   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re   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)re   rf   merge_branchmerge_worktreefilesu   보고서 분석: )	r@   rI   listrL   r+   warningrJ   r   str)rB   re   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
        rx   (/home/jay/projects/[^/]+)   u+   worktree에서 프로젝트 경로 추출: ry   u(   files에서 프로젝트 경로 추출: r;   tasksru   r   r   z /home/jay/projects/([^/\s`'\"]+)z/home/jay/projects/u.   task 메타에서 프로젝트 경로 추출: *   프로젝트 경로를 찾을 수 없음: N)rn   rematchgroupr+   rJ   r>   rI   rh   searchr{   )
rB   re   r   worktreemproject_pathry   ra   	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timeoutrp   r   messageu   머지 실패 []: u   머지 성공: u    → branch?)r   __file__parentr%   
executabler|   r+   rO   
subprocessrunr&   stripr]   rg   ri   
returncodern   stderrRuntimeError)
rB   r   re   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whichrI   r]   rg   rh   rn   ri   OSErrorr   r   r&   r   r   r+   rO   TimeoutExpiredrj   )rB   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   rO   rj   r   r   r   r   )rB   r   r   rq   s       r1   revert_mergezAutoMerger.revert_mergec  s     	/~>?	^^4 #F   A%4\NCD5fmm6I6I6K5LMN))73 	LL1#78	s   AB 0B C
(CC
rr   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   에스컬레이션: rd   COKACDIR_CHAT_IDchat_idCOKACDIR_KEY_ANUu9   COKACDIR_KEY_ANU 미설정 - 에스컬레이션 건너뜀Nz[auto_merge] u:    처리 중 문제 발생 - 수동 확인 필요.
사유: 	   
시각: %Y-%m-%dT%H:%M:%S+09:00r   )minutesr   cokacdir--cron--at--chat--key--onceT
   r   r   u"   에스컬레이션 등록 완료: u   cokacdir 등록 실패: u   cokacdir 실행 예외: )r+   r{   _load_env_keysrn   _CfgMgrget_instanceget_constantrj   r   r   r    r!   r   r   r   r   rO   r   r   r   r   )rB   re   rr   env_keysr   anu_keypromptat_timer   r   rq   s              r1   rl   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   rn   r   r   r   r+   rj   r>   r   r   r   rO   r{   r   r   r   r   )
rB   re   r   r   r   instructions_pathr   r   r   rq   s
             r1   
notify_anuzAutoMerger.notify_anu  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 유지).	timestampre   r   r   entriesTr   F   ensure_asciiindentu   merge-log.json 기록: N)r   r   r    r_   rA   rI   r]   rg   rh   ri   rN   r   mkdir
write_textr^   r+   rJ   )rB   re   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_short  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'
        rw   z
-(dev\d+)$r   rx   /N)rn   r   r   r   rstrip)r   r   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>   rI   r+   rJ   rh   
splitlinesr   
startswithrP   	partition)rB   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 ]A  }|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                  |       | j=                  |       | j!                  ||       |dxx   dz  cc<   | j1                  ||d&d||j                  d'      d(|d$   dd) |d&d*	       |d+xx   dz  cc<   | j1                  ||d,d||j                  d'      d-|d.d/       t        j%                  d0| d1|d2d3       | j                  r1| j?                  |       D t        j%                  d4|        |S # t        $ r |dxx   dz  cc<   Y xw 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|        | j!                  ||       |dxx   dz  cc<   | j1                  ||dd|d|t        j                         |z
  d        Y d}
~
od}
~
ww xY w)5ux   메인 실행 루프.

        Returns:
            {"processed": N, "merged": N, "escalated": N, "skipped": N}
        r   )	processedmerged	escalatedskipped.doneu   대상 .done 파일 없음: r  r   re   r   dev1u   보고서 분석 실패: report_errorNrf   Fu/   머지 불필요, notify-completion에 위임: r  u   이미 처리 중: rF   r  u   [DRY-RUN] 머지 대상: z (team=)r7   T)re   actionrf   statusr   no_project_pathu   머지 충돌/실패: rd   merge_failedrj   )re   r  rf   r   r  rj   duration_secondsr   u$   테스트 실패 후 머지 revert: 
r   i  revertedr   faili  )	re   r  rf   r   r   test_resulttest_outputr  r  r  auto_mergedpasssuccess)re   r  rf   r   r   r  r  r  u   완료: u    머지 성공 (r   r   u   처리 완료: ) r9   r?   rI   r+   r{   rU   rk   r   r   rs   rm   rn   r   	Exceptionrj   r7   rl   _finalize_done_filerO   r8   rb   rM   r   r   r   r   r   r   r|   r   r   r   )rB   statsrR   rQ   re   r   	done_dataactual_task_idr   r   rq   rr   rf   rS   
team_shortreport_team_shortr   merge_resultr  r   s                       r1   r   zAutoMerger.run/  s)    /011YZ [ T-@-@,A*GGI##%!=i[IJ#J--/J# T	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[, ..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 <</iT	0p 	oeW-.e  k"a'"  4SE:V$||MM.&9k"a'"((NNTZ[@   S5n5ESQRnf5k"a'""#1"0(,#/")!',0NN,<z,I %sD   OO,9QO)(O),	Q5AQQ	S$%A4SS$r  c           
      F    | j                  |||d|t        |      d       y)u[   처리 완료 후 로그만 기록. .done 파일은 삭제하지 않음 (아누가 처리).r  )re   r  r  rr   rR   N)r   r|   )rB   rR   re   r  rr   s        r1   r!  zAutoMerger._finalize_done_file  s)    " #  ^		
r3   )"__name__
__module____qualname____doc__osenvironrn   r|   r   r   resolver   boolrC   rz   rU   rb   dictr   rs   r   r   r   r   r   rl   r   r   staticmethodr   r   r   r   r!   r3   r1   r5   r5   V   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 2kT#s(^ kZ
T 
C 
 
VY 
^b 
r3   r5   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 기준 전팀 완료 감지.Nr6   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
   r;   r<   ztask-timers.json)r   r-  r.  rn   r|   r   r/  r   r>   r?   
timer_filerB   r6   s     r1   rC   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 없음: rE   rF   rG   r   r   batch_idre   u-   [BatchWatchdog] batch_id 포함 .done 파일 rH   )r?   rI   r+   rJ   rK   rL   rM   r]   rg   rh   ri   r   rn   ro   rk   rN   rO   rP   )rB   resultsrR   rS   rT   rp   r:  s          r1   rU   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r:  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pendingr:  r   r   u0   [BatchWatchdog] task-timers.json 읽기 실패: Nr   r:  r   r?  	completedr   )r7  rI   r]   rg   rh   ri   r   r+   r{   rn   itemsrP   rN   )rB   r:  datarq   r   tidrO   matchedr>  
done_countr@  r  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   r:  r  rA  rB  r   tzinfo      @r   re   elapsed_hours)r7  rI   r]   rg   rh   ri   r   r+   r{   r   r   r   utcrn   rD  fromisoformatrM  replacetotal_secondsrm   	TypeErrorrN   round)rB   r:  rJ  rE  rq   r   r   stalerF  rO   r  	start_rawstart_dtrP  s                 r1   	check_ttlzBatchWatchdog.check_ttlM  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   r:  r   rL  rN  Fr  rA  )r?  rC  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 쓰기 실패: )r:  expired_tasksrP  )r7  rI   r]   rg   rh   ri   r   r+   r{   r   r   r   rQ  rn   rD  ro   rN   rR  rM  rS  rm   rU  rT  r    r_   r   r^   rO   rj   rV  )rB   r[  rE  rq   r   r   	batch_maprF  rO   bidexpired_resultsr:  	task_list	oldest_dtr  rX  rY  rP  rb  changedr  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_batchesr:  rb  rP  u.   [BatchWatchdog] batch 만료 정리: batch_id=z, expired_tasks=r_  r`  ra  r  z.done.expiredz
batch TTL u   h 초과)r:  re   rP  r^  rr   Fr   r   r   r   u&   [BatchWatchdog] 만료 마커 생성: u-   [BatchWatchdog] 만료 마커 생성 실패: rd   NrO  )ri  r+   rJ   r{   r?   rI   rM   r   r]   r^   r   r   r    r_   rO   r   r   rj   send_ttl_warningrN   rP   )rB   r[  expired_listexpired_batch_idsitemr:  rb  rP  rF  rR   expired_markerrq   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)re   (rP  r   zh)Nr3  ).0ts     r1   	<genexpr>z1BatchWatchdog.send_ttl_warning.<locals>.<genexpr>:  s*     !dSTQy\N!Ao4Fs3K2"N!ds   [BatchWatchdog] batch_id=u     TTL 초과 경고
정체 task: r   r   r   r   r   r   r   r   r   Tr   r   r   u3   [BatchWatchdog] TTL 경고 등록 완료: batch_id=u3   [BatchWatchdog] cokacdir TTL 경고 등록 실패: u(   [BatchWatchdog] cokacdir 실행 예외: r>   rI   rh   r  r   r  rP   r  rn   r   r   r   r+   rj   joinr   r   r    r!   r   r   r   rO   r{   r   r   r   )rB   r:  rt  r  env_datar  kr  vr   r   stale_summaryr   r   r   rq   s                   r1   rm  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 목록
        r:  rA  r|  : r?  r   r>  u    완료, pending=r@  u   [BatchWatchdog] TTL 초과: r=  u/   [BatchWatchdog] 전팀 완료 확인: batch_id=rk  r   u>   [BatchWatchdog] batch TTL 만료 정리 완료: expired_count=z
, batches=rl  )rU   setrn   addrK   rI  r+   rO   rZ  r{   rm  rN   rs  )
rB   
done_itemsseen_batch_idsrp  rd  completed_batchesr:  
completionrt  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@)r)  r*  r+  r,  r|   rC   rz   r1  rU   rI  floatrZ  ri  rs  rm  r   r3  r3   r1   r5  r5    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   r5  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 시뮬레이션으로 충돌 검증.Nr6   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
   r;   r<   r=   )r   r-  r.  rn   r|   r   r/  r   r>   r?   r@   r8  s     r1   rC   zPreFlightCheck.__init__  sq    kbjjnn-=s4>CYCYC[CbCbCiCi?jk
 ..83h>>>H4y@r3   r:  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}]
        rE   r   r   r:  re   rw   r   rA  ru   rv   r   rx   r   r   ry   u)   [PreFlightCheck] 보고서 파싱 실패 r  N)re   r   r   [PreFlightCheck] batch_id=u   개 branch 수집)r?   rI   rK   rL   r]   rg   rh   ri   r   rn   rk   r@   rz   r   r|   r   r   r   r   r+   rJ   rN   rO   rP   )rB   r:  branchesrR   rp   re   rw   r   r}   r~   r   r   r   f_pathm2rq   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__)r   ry   )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   rI   r+   r{   r   r   r   r   r   r&   r  r   rN   rj   rP   )rB   r   r  r  r   rq   r   r   conflict_files_resultra   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)r   r  rv  ry   N   unknown)r~  )ry  cs     r1   r{  z5PreFlightCheck.send_conflict_alert.<locals>.<genexpr>g  s:     $wde(}Btyy7BQ7P7]T]6^%_$ws   -/   r  u    머지 충돌 감지
충돌: u   
수동 검토 필요
시각: r   r   r   r   r   r   r   r   Tr   r   r   u7   [PreFlightCheck] 충돌 알림 등록 완료: batch_id=u.   [PreFlightCheck] 충돌 알림 등록 실패: u)   [PreFlightCheck] cokacdir 실행 예외: r}  )rB   r:  r  r  r  r  r  r  r  r   r   conflict_summaryr   r   r   rq   s                   r1   send_conflict_alertz"PreFlightCheck.send_conflict_alertQ  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_countr:  r   rA  r   u6   [PreFlightCheck] 프로젝트 경로 없음 (branches=r  r   r  )r  r+   r{   rn   ro   rN   rD  r  extendrP   r  )rB   r:  branch_infosproject_maprO   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  )r)  r*  r+  r,  r|   rC   rz   r1  r  r  r  r   r3  r3   r1   r  r    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/ 실행.Nr6   r   c           
         t        |xsX t        j                  j                  dt	        t        t
              j                         j                  j                                    | _        | j                  dz  dz  | _	        y )Nr
   r;   r=   )
r   r-  r.  rn   r|   r   r/  r   r>   r@   r8  s     r1   rC   zIntegrationTestRunner.__init__  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>   rI   r+   r{   r   r   r   r   r|   r&   r   r   r   r   intr   rO   r   rj   r   FileNotFoundError)rB   r  	test_pathr   r   r   r   r   r  r   fail_mr   rq   s                r1   r   zIntegrationTestRunner.run_tests  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#r:  r  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   r   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   rA  Ni  z
```
r   r   u*   [IntegrationTestRunner] 리포트 생성: )
r@   r   r   r   r    r!   rn   r   r+   rO   )rB   r:  r  r}   r  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)
자동 완료 처리 확인 요청
시각: r   r   rA  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>   rI   rh   r  r   r  rP   r  rn   r   r   r   r+   rj   r   r   r    r!   r   r   r   rO   r{   r   r   r   )rB   r:  r  r  r  r  r  r  r  r   r   r  r  r   r   output_tailr   r   rq   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   )r:  r   r  r   report)r   r  r  rn   r|   )rB   r:  r  report_paths       r1   r   zIntegrationTestRunner.runf  si    nn&**8[A;/ !(+%//,:#
C8+&
 	
r3   r  )ztests/integration/)r)  r*  r+  r,  r|   rC   r1  r   r   r  r  r   r3  r3   r1   r  r    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 오케스트레이터.Nr6   r7   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   r-  r.  rn   r|   r   r/  r   r>   r7   r5  watchdogr  	preflightr  test_runner)rB   r6   r7   s      r1   rC   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)r:  l2l3z&[GraduatedAutoGate][DRY-RUN] batch_id=u   : L2/L3 건너뜀Tr7   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+   rO   r  r   rP   r7   rN   r  r{   r  )rB   r  r"  r:  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)	r)  r*  r+  r,  r|   r0  rC   r1  r   r3  r3   r1   r  r  y  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 파서 구성.rX   u!   자동 머지 처리 스크립트)progdescriptionz	--task-idu'   특정 task만 처리 (예: task-391.1))helpz	--dry-run
store_trueu,   드라이런: 실제 동작 없이 분석만)r  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    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 메인 진입점.)r7   )r7   r9   r8   Fr   r   N)r  
parse_args	graduatedr  r7   r   r5   re   force_mergeprintr]   r^   )r  argsgater   mergers        r1   mainr    sz    ^FD~~ 6LL<<**

 	$**V%
:;r3   __main__)
auto_merge)r   N)0r,  r  r]   r   r-  r   r   r   r%   r   r   r   r   pathlibr   typingr   r|   r   r   _ws_rootpathinsertreport_parserr   config.loaderr	   r   ImportErrorr.  rn   	_env_rootr/  r"   r   r    Loggerr2   r+   r5   r5  r  r  r  r  r  r  r)  r3  r3   r1   <module>r     s      	 	   
  2 2   tH~$$++,388HHOOAx  &76 

/T(^5K5K5M5T5T5[5[1\]^agg td +	"# w~~ 4 
Q

 Q

t\! \!H[
 [
F	z
 z
DM Mjh-- "<& zF ]6  7

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