
    naiCr                       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
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.                  j1                  de       ddlmZ 	 ddlmZ  eej>                  jA                  d e ee      jE                         j*                  j*                                    d	z  Z#e#jI                  d
d
        e ed            Z%d(dedejL                  fdZ' e'       Z( G d d      Z)dedededz  dede*f
dZ+dededz  dededz  de,e   f
dZ-dedede.de*fdZ/ G d d      Z0 G d d       Z1 G d! d"      Z2 G d# d$      Z3dejh                  fd%Z5d)d&Z6e7d'k(  r e6        yy# e$ rP ej>                  jA                  de      Z!e!e
j.                  vre
j.                  j1                  de!       ddlmZ Y kw 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          C/home/jay/workspace/.worktrees/task-2516-dev3/scripts/auto_merge.py_setup_loggerr2   7   s    t$F
OOGMM"LL''
3EKwd33H			X	8BKK			szz	*BKK!!"KUhiIOOIOOI
b
bM    c                   .   e Zd ZU 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d1de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z  fd"Z!g d#Z"ee   e#d$<   d%ee   d&edz  d	efd'Z$ded(ed	e%fd)Z&d%ee   d	efd*Z'ded	e%fd+Z(	 	 d2ded%ee   ded(ed	ef
d,Z)d-ed	dfd.Z*deded/eded	df
d0Z+y)3
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__[   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_filesn   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           	         | j                   dz  dz  | dz  }|j                         rt        d| d      | j                   dz  dz  }t        j                  dt        |      d	|gd
d
d      }|j                  dk7  r,t        d| d|j                  j                         dd        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)!uY  worktree_manager.py finish --action merge 실행.

        Lock-in 1 First-line: cancelled 검사 + guard.sh가 함수 첫 statement.
        Lock-in 2 Hard stop: 가드 실패 시 worktree_manager finish subprocess 진입 0회 보장.

        Raises:
            RuntimeError: 머지 실패 (충돌 등) 또는 cancelled/guard 차단.
        r;   r<   z
.cancelledu   cancelled — merge blocked [u   ] (.cancelled 마커 존재)scriptszguard.shbashzpre-pushT<   capture_outputtexttimeoutr   zguard.sh pre-push FAIL []: iNzworktree_manager.pyfinishz--actionmergeu   머지 실행:  (z) @ x   rp   messageu   머지 실패 [u   머지 성공:     → branch?)r>   rI   RuntimeError
subprocessrunr|   
returncodestderrstripr   __file__parentr%   
executabler+   rO   r&   r]   rg   ri   rn   )rB   r   re   r   _cancelled_marker	_guard_sh_gpworktree_managercmdr   outputparsed	error_msgs                r1   execute_mergezAutoMerger.execute_merge  s    !NNX5@gYjCYY##%!>wiGcdeeNNY.;	nnfc)nj'J[_fjtvw>>Q!9'#cjjFVFVFXY]Y^F_E`abb>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   :G G('G(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-qpackage.jsonr   r   testr   )npmr   z--z--ciznpm testTu)   테스트 스크립트 없음 (건너뜀)        )passedr   durationu&   package.json 파싱 실패 (건너뜀)u#   테스트 러너 없음 (건너뜀),  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_tests9  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	merge_shac                    t         j                  d| d|xs d d       	 dd|g}|s8t        j                  |ddgz         j	                         j                         }|}t        j                  |dd	d
d|gz          t         j                  d| d| d       y# t        j                  $ r"}t         j                  d|        Y d}~yd}~wt        j                  t        f$ r"}t         j                  d|        Y d}~yd}~ww xY w)u9  git revert <merge_sha>로 안전하게 되돌리기. hard reset 절대 사용 금지.

        Args:
            project_path: 프로젝트 경로
            merge_sha: 되돌릴 머지 커밋 SHA (없으면 HEAD 사용)

        Returns:
            True: revert 성공
            False: revert 실패
        u   머지 revert 시도: z (sha=HEAD)git-C	rev-parserevertz	--no-editz-m1u   머지 revert 성공: Tu   git revert 실패: NFu   머지 revert 예외: )r+   r{   r   check_outputdecoder   
check_callrO   CalledProcessErrorrj   r   r   )rB   r   r   cwd_argsheaderq   s          r1   revert_mergezAutoMerger.revert_mergeo  s    	/~VIDWQWCXXYZ[	t\2H!..x;:O/OPWWY__a 	!!(hT3PY-Z"Z[KK0fYKqQR,, 	LL.qc23))73 	LL1#78	s$   A8B D	.CD	'DD	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 ]  }|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|       |	j                  d      xs |j                  dd      }|	j                  dg       }| j5                  ||||      }t        j%                  d | d!|        |d"k(  rd#| }t        j	                  |       | j!                  ||       |dxx   dz  cc<   | j7                  ||j                  d$      dd"d||r|r| j9                  ||      ndddd%dd&       	 | j;                  || j=                  |            r)dd'lm }  |||D cg c]  }tC        |       c}d(d)i       <|d+k(  rd,| }t        j%                  |       | j!                  ||       |dxx   dz  cc<   	 dd-l"m#} |j                  d$      }|r0 ||tI        |      tK        |      | jM                  |      d.d.d/       | j7                  ||j                  d$      dd+d||r|r| j9                  ||      ndddd%dd&       	 | jO                  |||      }|j                  d8d      }| jS                  |      }t        j                         |z
  }|d9   sd:| d;|d<   dd=  }t        j                  |       | jU                  ||       | j!                  ||       |dxx   dz  cc<   | j1                  ||d>d||j                  d?      d6|d<   dd@ |d>dA	       | j7                  ||j                  d$      ||d||r|r| j9                  ||      ndd6dd%d>d&       $|dBxx   dz  cc<   	 tW        jX                  g dC|dddDE      }|jZ                  dk(  r|j\                  j_                         nd}te        | jf                  ||dF       ti        | jf                  |||      }	 | jM                  |      } tk        | jf                  ||        |D ]*  }!| jM                  |!      }"tk        | jf                  |!|"       , 	 | j1                  ||dFd||j                  d?      dH|dIdJ       | j7                  ||j                  d$      ||d||r|r| j9                  ||      nddHdHd%dBd&       t        j%                  dK| dL|dMdN       | j                  r| jm                  |        t        j%                  dO|        |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}
~
Ld}
~
ww xY wc c}w # t        $ r#}t        j	                  d*|        Y d}~Fd}~ww xY w# t        $ r#}t        j	                  d0|        Y d}~d}~ww xY w# tP        $ r}
tC        |
      }t        j                  d1| d2|        | j!                  ||       |dxx   dz  cc<   | j1                  ||d3d|d4|t        j                         |z
  d5       | j7                  ||j                  d$      d|d|dd6dd%d7d&       Y d}
~
fd}
~
ww xY w# tV        j`                  tb        f$ r d}Y w xY w# t        $ r#}#t        j	                  dG|#        Y d}#~#d}#~#ww xY w)Pux   메인 실행 루프.

        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=r   r7   T)re   actionrf   statusr   no_project_pathrw    ry   )r   r   u   Tier 분류 결과: r   tier3u%   Tier 3 — manual approval required: 	pr_numberrX   )re   r*  r   tiercapability_sha
diff_filesdiff_loc	qc_resultscope_guardmergeroutcome)send_scope_violationrr   scope_guard_failu5   scope violation alert 실패 (기존 흐름 유지): tier2u!   Tier 2 — 1-tap approval queue: )send_approval_cardr   )ry   level	additions	deletionsu;   Tier 2 inline button 발송 실패 (기존 흐름 유지): u   머지 충돌/실패: rd   merge_failedrj   )re   r%  rf   r   r&  rj   duration_secondsfailrejectedr   r   u$   테스트 실패 후 머지 revert: 
r   i  revertedr   i  )	re   r%  rf   r   r   test_resulttest_outputr;  r&  r  )r   r   r   r   r   auto_mergedz[checklist] update failed: passsuccess)re   r%  rf   r   r   r@  r;  r&  u   완료: u    머지 성공 (r   r   u   처리 완료: )7r9   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   classify_tier_append_audit	_diff_loc_has_protected_path_load_capability_snapshotscripts.scope_violation_alertr3  r|   scripts.anu_confirm_bot.mainr6  intrP   _load_task_levelr   r   r   r   r   r   r   r&   r   r   r   _sync_task_timersr>   _sweep_stale_completed_update_plan_checklistr   )$rB   statsrR   rQ   re   r   	done_dataactual_task_idr   r   rq   rr   rf   rS   
team_shortreport_team_shortr   rw   diff_files_for_tierr+  r3  p_excr6  pr_nummerge_resultr   r@  r   merge_sha_resultmerge_commit_shaswept_task_ids_task_level
_swept_tid_swept_level_checklist_excs$                                       r1   r   zAutoMerger.run>  s	    /011YZ [ T-@-@,A*GGI##%!=i[IJ#J--/J# n	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]^ '??>:_immN\^>_L-8__Wb-I%%#)#	 & D KK.~.>eD6JK w@@PQv&nf5k"a'"""#1%.]];%?%' '*,&9R^coDNN<$Nuv%.'0"1#. c//0CTEcEcdrEstV,^Na=bc!f=bem  pB  eC  D  w<^<LMF#nf5k"a'"
iO&]];7F*>3v;%()<%=%)%:%:>%J),3I  ""#1%.]];%?%' '*,&9R^coDNN<$Nuv%.'0"1#. ##11,PZ[H %((b9I ..6K~~'*4Hx(??OrR]^fRghlilRmQnoV$!!,	:nf5k"a'""#1",(,#/"."2"28"<'-'28'<Ud'C,4",
 ""#1%.]];%?%. $*,&9R^coDNN<$Nuv%+'0"1#-   (Oq O
(#->>0$#'$  GWFaFaefFf#3#:#:#@#@#Blp  dnnn>NP]^ 4 0.,N
O"33NC&t~~~{S"0 UJ#'#8#8#DL*4>>:|TU OO-+$(+*..x8#)(0'	 -!*{!;!* &("5NZ_k|\ Jqr!'#)-' KK(>"22B8C.PRST <</]	n	0d	 	oeW-.Y	  k"a'"  4SE:V$||MM.&9k"a'"((NNTZ[B >c  cNN%Z[_Z`#abbc( ! iNN%`ae`f#ghhi.   !S5n5ESQRnf5k"a'""#1"0(,#/")!',0NN,<z,I ""#1%.]];%?%' $*,&9$%%+'0"1#-  C!z --w7 (#' (   O!<^<LMNNOs   \7] ._	_ 
_	+A	_89`'"AcAd 7]]	_A^<<__			_5_00_58	`$``$'	c0B ccc=<c= 	d,	d''d,c                 R   | j                   dz  dz  | dz  }|j                         st        j                  d|        y	 t	        j
                  |j                  d            S # t        j                  t        f$ r%}t        j                  d| d	|        Y d}~yd}~ww xY w)
u:   memory/capabilities/{task_id}.json 로드. 없으면 None.r;   capabilitiesz.jsonu   capability snapshot 없음: Nr   r   u"   capability snapshot 로드 실패 : )
r>   rI   r+   rJ   r]   rg   rh   ri   r   r{   )rB   re   cap_filerq   s       r1   rK  z$AutoMerger._load_capability_snapshot  s    >>H,~=7)5@QQ LL7zBC	::h00'0BCC$$g. 	NN?y3%PQ	s   $A( (B&B!!B&)
z	CLAUDE.mdz	memory/**z
.github/**zscripts/task-scope-guard.shzscripts/auto_merge.pyzscripts/finish-task.shzdispatch.pyzscripts/post_merge_probe.pyzscripts/auto_revert.pyzbot_settings/**_GLOBAL_PROTECTED_PATHSr-  
capabilityc                 4   g }|r|j                  dg       }t        | j                        |z   }|D ]e  }|D ]^  }t        j                  ||      s)t        j                  ||j	                  d      dz         sBt
        j                  d| d|          y g y)uY   capability의 forbidden_paths glob 매칭 또는 글로벌 보호 경로 매칭 시 True.forbidden_pathsz/**z/*u   보호 경로 매칭: r   TF)rn   rz   rh  fnmatchr  r+   rJ   )rB   r-  ri  forbidden_patternsall_patternsra   patterns          r1   rJ  zAutoMerger._has_protected_path  s     )+!+0A2!FD889<NN 	 A'  ??1g.'//!W^^TYEZ]aEa2bLL#9!E'!KL 	 
 r3   r   c           	      \   |r|sy	 t        j                  dd|ddd| gddd	      }|j                  dk7  r1t        j	                  d
|j
                  j                                 y|j                  j                         }d}d}t        j                  d|      }t        j                  d|      }|rt        |j                  d            }|rt        |j                  d            }||z   S # t         j                  t        f$ r"}	t        j                  d|	        Y d}	~	yd}	~	ww xY w)uA   git diff --shortstat main..{branch} → 변경 LOC 합계 반환.r   r   r   diffz--shortstatzmain..T   r   u   _diff_loc git diff 실패: z(\d+)\s+insertionz(\d+)\s+deletionr   u   _diff_loc 예외: N)r   r   r   r+   rJ   r   r   r&   r   r   rN  r   r   r   r{   )
rB   r   r   r   r   
insertionsr9  m_insm_delrq   s
             r1   rI  zAutoMerger._diff_loc  s   6	^^lFMVF8CTU#	F   A%:6==;N;N;P:QRS]]((*FJIII2F;EII16:E Q0
A/		))))73 	NN/u56	s   A!C0 )BC0 0D+	D&&D+c                     g d}|D ]5  }t        |      j                  }||v st        j                  d|         y y)u&   의존성 파일 변경 여부 감지.)r   zpackage-lock.jsonzpyproject.tomlzrequirements.txtzpoetry.locku    의존성 파일 변경 감지: TF)r   r   r+   rJ   )rB   r-  dep_patternsra   basenames        r1   _has_dependency_changez!AutoMerger._has_dependency_change  sK    
  	AAw||H<'?sCD	 r3   c                    | j                   dz  dz  | dz  }|j                         r|j                  d      }t        j                  d|t        j
                        }|rR|j                  d      }t        j                  d|t        j                        }|rt        |j                  d            S t        j                  d	|      }|rt        |j                  d            S | j                   dz  d
z  }|j                         r^	 t        j                  |j                  d            }	|	j                  di       }
||
v r!|
|   j                  d      }|t        |      S yy# t        j                  t        t        f$ r Y yw xY w)u]   task 파일 YAML frontmatter level 또는 task-timers.json work_level에서 추출. 기본 0.r;   r   ru   r   r   ^---\s*\n(.*?)\n---r   z^level\s*:\s*(\d+)u!   작업\s*레벨\s*[:\s]+Lv\.(\d+)task-timers.json
work_levelr   )r>   rI   rh   r   r   DOTALLr   r   	MULTILINErN  r]   rg   rn   ri   r   rm   )rB   re   	task_filer   r   frontmatterlv_match	lv_match2
timer_filedatar   wls               r1   rO  zAutoMerger._load_task_level  sd    NNX-7WIS/I	))7);G/"))DAggaj99%:KVx~~a011		"FPI9??1-.. ^^h.1CC
zz*"6"6"6"HI"-e#w++L9B~"2w  ((':> s   	AE' 'FFc           
         | j                  |      }| j                  |      }|dk\  s| j                  ||      rt        j	                  d| d|        y|dk(  st        |      dkD  s| j                  |      r(t        j	                  d| d| dt        |              y	|d
k  r3|r| j                  ||      dk  rt        j	                  d| d|        yt        j	                  d| d|        y	)uy   task 속성 + diff 정보를 바탕으로 tier 분류.

        Returns:
            "tier1" | "tier2" | "tier3"
           u   classify_tier → tier3: task=z, lv=r)  r      u   classify_tier → tier2: task=, files=r5  r   r   u   classify_tier → tier1: task=tier1u(   classify_tier → tier2(fallback): task=)rK  rO  rJ  r+   rO   rP   ry  rI  )rB   re   r-  r   r   ri  lvs          r1   rG  zAutoMerger.classify_tier  s     33G<
""7+ 7d..z:FKK8	rdKL 7c*o)T-H-H-TKK8	rd(SVWaSbRcde 7LDNN<,PSV,VKK8	rdKL 	>wiuRDQRr3   recordc           
         | j                   dz  dz  }|j                  dd       |dz  }d}|j                         rM	 |j                  d      j	                         D cg c]  }|j                         s| }}t        |      dz   }|j                  d	t        j                  t              j                                ||d
<   t        j                  |d      dz   }	 t        |dd      5 }t!        j"                  |t         j$                         	 |j'                  |       t!        j"                  |t         j(                         	 ddd       t*        j-                  d| d|j/                  d       d|j/                  d              yc c}w # t        $ r d}Y w xY w# t!        j"                  |t         j(                         w xY w# 1 sw Y   xY w# t        $ r"}	t*        j1                  d|	        Y d}	~	yd}	~	ww xY w)uT   memory/audit/auto-merge.log에 JSONL append. fcntl.flock으로 동시 쓰기 안전.r;   auditTr   zauto-merge.logr   r   r   r   sequenceF)r  r>  aNu   audit log 기록 (seq=z): re   r   r2  u   audit log 쓰기 실패: )r>   r  rI   rh   r  r   rP   r   ro   r   r   r    r_   r]   r^   r[   fcntlflockLOCK_EXr\   LOCK_UNr+   rJ   rn   rj   )
rB   r  	audit_dir	audit_logr  llinesr  ra   rq   s
             r1   rH  zAutoMerger._append_audit(  s   NNX-7	t4 00	 $-$7$7$7$I$T$T$VdqZ[ZaZaZcddu:> 	+x||D'9'C'C'EF%zzz&u5<		<iw7 21Au}}-2GGDMKK5==12 LL1(3vzz)?T>UUZ[a[e[efo[pZqrs# e  KK5==12 2  	<LL4SE:;;	<sr   "F  F6F:F !G /%GF'&$GAG F F$#F$'&GGGG 	H%HH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   rF  zAutoMerger._finalize_done_fileG  s)    " #  ^		
r3   )r(  )r(  r(  ),__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   rK  rh  __annotations__rJ  rN  rI  ry  rO  rG  rH  rF   r3   r1   r5   r5   X   sE   " !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 /j0Jc 0Jd38n 0Jl  d B,; ,;S ,;T ,;d!9# !9$ !9N:# :tCH~ :$ :6 3 3   T#s(^ d
  ,S#X 2ET#s(^ EV

 
 
*T#Y d3i TD[ UY  c 3 3 :c t "  J    I  	 
   
 L<D <T <>
T 
C 
 
VY 
^b 
r3   r5   r>   re   r^  rr   c                    | dz  dz  }|j                         sy| dz  dz  }|j                  j                  dd       t        |d      5 }t	        j
                  |t        j                         	 t        |dd	
      5 }t        j                  |      }ddd       j                  di       }	||	vr.	 t	        j
                  |t        j                         ddd       y|	|   }
|
j                  d      dk(  r.	 t	        j
                  |t        j                         ddd       yd|
d<   t        j                  t        j                        j!                         |
d<   |r||
d<   d| |
d<   |j#                  d      }t        |dd	
      5 }t        j$                  ||dd       ddd       t'        j(                  ||       t*        j-                  d| d| d       	 t	        j
                  |t        j                         ddd       y# 1 sw Y   ~xY w# 1 sw Y   vxY w# t	        j
                  |t        j                         w xY w# 1 sw Y   yxY w)u   머지 성공 시 task-timers.json status를 'completed'로 갱신 (atomic write).

    이미 completed면 no-op. flock으로 동시성 보호.
    Returns:
        True: sync 수행됨, False: skip (이미 completed 또는 task 없음)
    r;   r|  Fz.task-timers.lockTr   wrr   r   Nr   r&  	completedend_timer^  zauto_merge:	closed_byz	.json.tmpr   r  z[task-timers sync] u    → completed (reason=r   )rI   r   r  r[   r  r  r  r]   loadro   r  rn   r   r   r   utcr_   rM   dumpr  renamer+   rO   )r>   re   r^  rr   timers_path	lock_path	lock_filera   r  r   ttmps               r1   rP  rP  [  s    h&);;KH$'::I4$7	i	 2Iu}}-	2k39 $Qyy|$ OOGR0Ee#( KK	5==192 2 gAuuX+-  KK	5==192 2 &AhK$LL6@@BAjM(8$%*6(3AkN ))+6Cc31 AQ		$a@AIIc;'KK-gY6MfXUVWXKK	5==192 2$ $&A A KK	5==192 2sm   %I1H*?HH*4$I!H*;$I(A'H*H):H*$$IH	H*H'	#H**&IIIprimary_task_idr   c                    ||g S 	 t        j                  dddd|g|ddd      }|j                  dk7  rg S |j                  }t        j                  d	|      }t               }g }|D ].  }	|	|k(  s|	|v r|j                  |	       |j                  |	       0 g }
|D ]$  }		 t        | |	|d
      }|r|
j                  |	       & |
r$t        j                  dt        |
       d|
        |
S # t        $ r g cY S w xY w# t        $ r Y kw xY w)u  머지 commit message에 묶인 다른 task들(chore batch)을 status=completed로 sweep.

    Args:
        workspace: 워크스페이스 루트
        merge_commit_sha: 방금 머지된 커밋 SHA. None이면 sweep 스킵.
        primary_task_id: 이미 _sync_task_timers로 처리된 task — sweep 대상에서 제외
        project_path: git log 실행 위치. None이면 sweep 스킵.

    Returns:
        sweep된 task_id 목록 (이미 completed인 것 제외).
    r   logz-1z--format=%BTr   r   r   ztask-\d+(?:\.\d+)?auto_merged_sweepz[task-timers sweep] z tasks: )r   r   r   r&   rE  r   findallsetaddrN   rP  r+   rO   rP   )r>   r^  r  r   r   bodyall_task_idsseenunique_otherstidsweptsynceds               r1   rQ  rQ    sF   " <#7	E40@A
 !I}} ::3T:LUD!M "/!SD[S!	" E 	&y#7GI\]FS!	 *3u:,hugFGL/  	$  		s(   /C, C, !!C=,C:9C:=	D	D	r7  c                 &   |dk  ry	 d}| dz  dz  | dz  }|j                         r|j                  d      }t        j                  d	|t        j                        }|rUt        j
                  d
|j                  d      t        j                        }|r|j                  d      j                         }|sK| dz  dz  j                  d      D ]1  }	 |j                  d      }	||	v r|j                  j                  } n3 |sy| dz  dz  |z  dz  }
|
j                         sy|
j                  d      }t        j                  dt        j                  |      z   dz   t        j                        }ddt        j                  dt         ffd}|j#                  ||      }dk(  ryddl}|j'                  |
j                  d      \  }}	 t)        j*                  |dd      5 }|j-                  |       ddd       t)        j.                  ||
       t2        j5                  d| d| d       y# t        $ r Y qw xY w# 1 sw Y   LxY w# t        $ r' 	 t)        j0                  |        # t        $ r Y  w xY ww xY w# t        $ r Y yw xY w)un  Lv.3+ task 머지 시 memory/plans/{project}/checklist.md의 해당 task 항목을 [x]로 체크.

    Args:
        workspace: 워크스페이스 루트
        task_id: 체크할 task ID
        level: task work_level (Lv.0~4). 3 미만이면 no-op.

    Returns:
        True: 체크리스트 수정됨, False: skip (level<3, 항목 없음, 이미 체크됨)
    r  FNr;   r   ru   r   r   r{  z^project\s*:\s*(.+)$r   plansz*/checklist.mdzchecklist.mdz^(\s*-\s*)\[\s\](.*\bz\b.*)$r   r   r   c                 Z    dz  | j                  d      dz   | j                  d      z   S )Nr   z[x]r   )r   )r   counts    r1   	_replacerz)_update_plan_checklist.<locals>._replacer  s,    QJE;;q>E)EKKN::r3   z.tmp)dirsuffixr  z[checklist] u    → [x] in plans/z/checklist.mdT)rI   rh   r   r   r~  r   r   r  r   rL   r   r   rE  compileescapeMatchr|   subtempfilemkstempr  fdopenr\   r  unlinkr+   rO   )r>   re   r7  r   r  r   r   
proj_matchchecklist_path
cl_contentchecklist_fileoriginalro  r  updatedr  tmp_fdtmp_pathra   r  s                      @r1   rR  rR    s    qyB" (72y_D	))7);G/"))DAYY'>
BLLY
(..q1779G #,x#7'#A"G"GHX"Y !/!9!97!9!KJ*,"0"7"7"<"< - "X-7'ANR$$&!++W+=**$ryy'99IELL
 	;RXX 	;# 	;
 ++i2A: 	#++0E0Ef+U		639 !Q !IIh/ 	l7)+=gYmTUU ! >! !  			(#   	  s   C
J ,H5 J J &BJ +$J I (I:I J 5	I>J IJ I
I 	JI10J1	I=:J<I==JJ 	JJ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<   r|  )r   r  r  rn   r|   r   r  r   r>   r?   r  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(  r  r  r   )r  rI   r]   rg   rh   ri   r   r+   r{   rn   itemsrP   rN   )rB   r  r  rq   r   r  rO   matchedr  
done_countr  r&  r  s                r1   check_batch_completionz$BatchWatchdog.check_batch_completionI  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&  r(  r  r   tzinfo      @r   re   elapsed_hours)r  rI   r]   rg   rh   ri   r   r+   r{   r   r   r   r  rn   r  fromisoformatr  replacetotal_secondsrm   	TypeErrorrN   round)rB   r  r  r  rq   r   r   staler  rO   r&  	start_rawstart_dtr  s                 r1   	check_ttlzBatchWatchdog.check_ttlu  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   r  r  Fr&  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 쓰기 실패: )r  expired_tasksr  )r  rI   r]   rg   rh   ri   r   r+   r{   r   r   r   r  rn   r  ro   rN   r  r  r  rm   r  r  r    r_   r  r^   rO   rj   r  )rB   r  r  rq   r   r   	batch_mapr  rO   bidexpired_resultsr  	task_list	oldest_dtr  r  r  r  r  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  r  r  u.   [BatchWatchdog] batch 만료 정리: batch_id=z, expired_tasks=r   r  r  r"  z.done.expiredz
batch TTL u   h 초과)r  re   r  r  rr   Fr   r  r   r   u&   [BatchWatchdog] 만료 마커 생성: u-   [BatchWatchdog] 만료 마커 생성 실패: rd   Nr  )r
  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  r  r  r  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   (r  r   zh)Nr  ).0r  s     r1   	<genexpr>z1BatchWatchdog.send_ttl_warning.<locals>.<genexpr>b  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  r  r  env_datar  kr  vr   r   stale_summaryr   r   r   rq   s                   r1   r  zBatchWatchdog.send_ttl_warningK  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  r(  r  rf  r  r  r  u    완료, pending=r  u   [BatchWatchdog] TTL 초과: r  u/   [BatchWatchdog] 전팀 완료 확인: batch_id=r  r   u>   [BatchWatchdog] batch TTL 만료 정리 완료: expired_count=z
, batches=r  )rU   r  rn   r  rK   r  r+   rO   r  r{   r  rN   r  )
rB   
done_itemsseen_batch_idsr  r  completed_batchesr  
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@)r  r  r  r  r|   rC   rz   r  rU   r  floatr  r
  r  r  r   r  r3   r1   r  r    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   r  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@   r  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   r(  ru   rv   r   rx   r   r   ry   u)   [PreFlightCheck] 보고서 파싱 실패 rf  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   rE  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   r0  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-untrackedTrr  r   u#   [PreFlightCheck] git stash 실패: Nr   r   z--no-commitz--no-ffr   r   )r   rq  z--name-onlyz--diff-filter=U   u'   [PreFlightCheck] 충돌 감지: branch=r  )r   r   z--abort)r   resetz--hardr   u/   [PreFlightCheck] merge 시뮬레이션 예외: z__exception__: )r   r7  popu'   [PreFlightCheck] git stash pop 실패: )r   rI   r+   r{   r   r   r   r   r   r&   r  r   rN   rj   rP   )rB   r   r0  r6  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"r6  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   rf  r  ry   Nr  unknown)r  )r  cs     r1   r  z5PreFlightCheck.send_conflict_alert.<locals>.<genexpr>  s:     $wde(}Btyy7BQ7P7]T]6^%_$ws   -/r  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  r6  r  r  r  r   r  r!  r   r   conflict_summaryr   r   r   rq   s                   r1   send_conflict_alertz"PreFlightCheck.send_conflict_alerty  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   r6  branch_countr  r   r(  r   u6   [PreFlightCheck] 프로젝트 경로 없음 (branches=r   r   r6  )r3  r+   r{   rn   ro   rN   r  r=  extendrP   rC  )rB   r  branch_infosproject_maprO   ppall_conflictsr   r0  
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   r  r3  r=  rC  r   r  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@   r  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   rN  r   rO   r   rj   r   FileNotFoundError)rB   rO  	test_pathr   r   r   r   r   rQ  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**: rQ  r   z
- **duration**: r   r   r   zs
- **generated_at**: z

## Output

```
r   r(  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   rQ  r   r   r   z![IntegrationTestRunner] batch_id=u!    통합 테스트 완료
결과: r   u   개 테스트, r   u.   s)
자동 완료 처리 확인 요청
시각: r   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>   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&  rQ  r   r   output_tailr   r   rq   s                      r1   send_resultz!IntegrationTestRunner.send_resultE  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   rQ  r   r   r   )r  r   rQ  r   report)r   rX  r\  rn   r|   )rB   r  r@  report_paths       r1   r   zIntegrationTestRunner.run  si    nn&**8[A;/ !(+%//,:#
C8+&
 	
r3   r)  )ztests/integration/)r  r  r  r  r|   rC   r  r   r   rX  r\  r   r  r3   r1   rM  rM    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   rM  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   r  watchdogr,  	preflightrM  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   ri  r   r   z2[GraduatedAutoGate] L2 Pre-Flight Check: batch_id=rj  z%[GraduatedAutoGate] L2 FAIL batch_id=u	   : 충돌 r6  u   건 → skiprh  z2[GraduatedAutoGate] L3 Integration Test: batch_id=rk  z%[GraduatedAutoGate] L3 PASS batch_id=u   : 자동 완료 보고z%[GraduatedAutoGate] L3 FAIL batch_id=u+   : 테스트 실패 → 아누 판단 대기u$   [GraduatedAutoGate] 완료: checked=rg  z	, passed=z	, failed=)
r+   rO   rc  r   rP   r7   rN   rd  r{   re  )rB   r&  rS  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|   r  rC   r  r   r  r3   r1   ra  ra    s6    2FsTz F4 FTX FAT Ar3   ra  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%  rr  z--force-mergeTASK_IDu&   강제 머지 (이미 처리됐어도))metavarrr  z--graduateduh   3-Layer Graduated Auto-Gate 실행 (L1 BatchWatchdog → L2 PreFlightCheck → L3 IntegrationTestRunner))argparseArgumentParseradd_argument)parsers    r1   build_parserrz    s}    $$7F *ST
L?mn
Aij
w  
 Mr3   c                  8   t               } | j                         }|j                   xr |j                   }|rRt        j
                  j                  d      dk7  r0t        dt        j                         t        j                  d       |j                  r't        |j                        }|j                         }n<t        |j                  |j                  |j                        }|j                         }t        t!        j"                  |dd	
             y)u   CLI 메인 진입점.

    task-2449 Fix 5: 외부 직접 호출 차단 가드.
    TASKCTL_INVOKED=1 환경변수가 set 되어 있지 않으면 exit 1.
    --dry-run / --graduated 모드는 가드 면제 (분석/리포팅 용도).
    TASKCTL_INVOKEDr   u   [BLOCKED] auto_merge.py merge 경로는 taskctl로만 호출 가능합니다.
          (TASKCTL_INVOKED=1 환경변수 미설정)
          → python3 scripts/taskctl.py merge <task-id>)filer   )r7   )r7   r9   r8   Fr   r  N)rz  
parse_argsr7   	graduatedr  r  rn   printr%   r   exitra  r   r5   re   force_merger]   r^   )ry  args_is_merge_pathgater   r1  s         r1   mainr  	  s     ^FD
 %<dnn*<N"**..):;sBG 		
 	 ~~ 6LL<<**

 	$**V%
:;r3   __main__)
auto_merge)r   N)8r  rv  r  rl  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   r  rP  rz   rQ  rN  rR  r  r,  rM  ra  rw  rz  r  r  r  r3   r1   <module>r     sL        	 	   
  2 2   tH~$$++,388HHOOAx  &76 

/T(^5K5K5M5T5T5[5[1\]^agg td +	"# w~~ 4 
z
 z
F *2 *2 *2sTz *2[^ *2cg *2Z55Dj5 5 *	5
 
#Y5pPd PS P P Pp\! \!H[
 [
F	z
 z
DM Mjh-- "#<L zF OH  7

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