
     j                       d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	m
Z
mZ ddlmZ ddlmZ ddlmZ ddlmZmZmZmZmZ  ej0                  e      Z ej6                  d	ej8                        Z ej6                  d
ej8                        Z ej6                  dej8                        Z ej6                  dej8                        Z  ej6                  dej8                        Z! ej6                  dej8                        Z" ej6                  dejF                        Z$ e%g d      Z& G d de'e      Z(e G d d             Z)e G d d             Z*	 	 	 	 	 	 	 	 	 	 	 	 d&dZ+d'dZ,d(dZ-	 d)	 	 	 	 	 d*dZ.	 	 	 	 d+dZ/d,dZ0	 	 	 	 	 	 	 	 d-dZ1	 	 d.	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d/dZ2d0dZ3d1d Z4d!Z5d2d"Z6d3d#Z7d4d$Z8ed%k(  r e8        yy)5u  utils/auto_gemini_triage.py — task-2511 5 모듈 #3.

회장 명시: Gemini review thread 자동 분류/resolve.
PR #61류 unresolved-thread blocker를 사람 개입 없이 자동 해소 가능하게 만든다.
산출물은 정책 문서가 아니라 실행 가능한 코드 + 회귀 테스트 16건.

Critical 7종 (contracts freeze 기준):
  1. FORBIDDEN_PATH_INTRUSION
  2. REPLACEMENT_PR_AUTO_CREATION_FAILED_FOR_CONTAMINATED_DIFF
  3. GEMINI_REAL_BUG_REQUIRES_SCOPE_EXPANSION
  4. BLOCK_OVERRIDE_REQUIRED_OR_REASON_INSUFFICIENT
  5. DEPENDENCY_CYCLE_OR_SERIAL_ONLY_COLLISION
  6. REPLACEMENT_PR_FAILED
  7. POST_MERGE_SMOKE_FAILED

7-state TriageVerdict:
  FALSE_POSITIVE / STYLE_ONLY / OUTDATED / CODE_ALREADY_FIXED /
  MINOR_FIX_ALLOWED / REAL_BUG_IN_SCOPE / REAL_BUG_SCOPE_EXPANSION

CLI:
  python3 utils/auto_gemini_triage.py --pr <N> [--dry-run | --apply]
      [--task-file <path>] [--fixture <path>]
  -> ReviewGateStatus JSON + exit code
  exit 0: all clean, exit 1: unresolved, exit 2: blocking
    )annotationsN)asdict	dataclass)Enum)Path)Optional)CriticalEscalationTypeEscalationPacketGeminiStatusGeminiTriageResultReviewGateStatusua   grep|regex|wrapper|helper\s*pattern|false[-\s]?positive|이미\s*처리|정상\s*동작|의도적zhnaming|formatting|readability|consistency|code\s*style|style\s*only|nit:|optional:|f-string|\.format\(\)z^\bbug\b|\bcrash\b|\berror\b|\bfail\b|\bbroken\b|\bincorrect\b|\bwrong\b|\bnull\b|\bexception\bzp(?:[\w\-]+/)+[\w\-]+\.(?:py|js|ts|json|yaml|yml|sh|md|txt)|\*\*/\*\.\w+|\b[\w\-]+\.(?:py|js|ts|json|yaml|yml|sh)u9   bool\s*flag|변수명|rename|is_\w+|enabled|disabled|flagz</home/[\w/\-\.]+|/usr/[\w/\-\.]+|/var/[\w/\-\.]+|C:\\[\w\\]+z1```[\s\S]*?```|`[^`]+`|def\s+\w+\s*\(|class\s+\w+)FALSE_POSITIVE
STYLE_ONLYOUTDATEDCODE_ALREADY_FIXEDMINOR_FIX_ALLOWEDc                  (    e Zd ZdZdZdZdZdZdZdZ	y)	TriageVerdictr   r   r   r   r   REAL_BUG_IN_SCOPEREAL_BUG_SCOPE_EXPANSIONN)
__name__
__module____qualname__r   r   r   r   r   r   r        //home/jay/workspace/utils/auto_gemini_triage.pyr   r   p   s)    %NJH-++9r   r   c                  J    e Zd ZU ded<   ded<   ded<   ded<   ded	<   d
ed<   y)ThreadTriageOutcomestr	thread_idr   verdictboolauto_resolveddismiss_commentz Optional[CriticalEscalationType]escalation_typedictevidenceNr   r   r   __annotations__r   r   r   r   r   ~   s$    N55Nr   r   c                  ^    e Zd ZU ded<   ded<   ded<   ded<   ded<   ded	<   d
ed<   ded<   y)TriageReportint	pr_numberlist[ThreadTriageOutcome]threadsunresolved_countauto_resolved_countblocking_thread_countr"   merge_readinessr   review_gate_statusr   triage_summaryNr(   r   r   r   r+   r+      s1    N&&((&&r   r+   c                z   | j                  dd      }| j                  dd      }| j                  dg       }|rdj                  d |D              nd}|r|dd	 nd}	|r"t        |t        j                  d
ddd
dd      S t
        j                  |      }
|
D ]y  |D ]r  }|v s.|v s*t        j                  t        j                  |            s5t        |t        j                  dd| d d| dt        j                  d|d      c c S  { t        j                  |      }t        j                  |      }|xs |}|D|j                         }|rdnd}t        |t        j                   d
d| d| dd||rdnddd      S t"        j                  |      rtt$        j                  |      s_t"        j                  |      }t        |t        j&                  d
d|r|j                         nd dd|r|j                         nddd      S |D ]  }|j                  dd      }|j                  dd      |j                  dg       |j)                         D cg c]1  }t+        |j-                  d             d!k\  s!|j-                  d       3 }}t/        fd"|dd# D              }t1        |      xr |dd	 |v }|
rrt/        fd$|
D              nd}|s|s|st        |t        j2                  d
d%|r|dd	 nd& d'| d(| dd|d|rd)n|rd*nd+d,      c S  |
D ]X  t/        fd-D              }|rst        |t        j                  dd. d/ d0 dt        j4                  dd1      c S  t6        j                  |      r||
 xs t/        fd2|
D              }|ss_t6        j                  |      }t        |t        j8                  d
d3|r|j                         nd dd|r|j                         nddd      S t        |t        j:                  dd4ddd5|	d6      S c c}w )7u  7-state classifier — 정규식 + outdated flag + commit body/diff 매칭.

    분류 우선순위 (top-down):
    1. isOutdated=True → OUTDATED
    2. forbidden path 매칭 → REAL_BUG_SCOPE_EXPANSION + FORBIDDEN_PATH_INTRUSION (보안 우선)
    3. false-positive 패턴 (regex/wrapper) 또는 hardcoded path 코멘트 → FALSE_POSITIVE
    4. style hint (bug 단어 부재) → STYLE_ONLY
    5. 후속 commit 매칭 → CODE_ALREADY_FIXED
    6. expected_files 차집합 (path 추출) → REAL_BUG_SCOPE_EXPANSION + GEMINI_REAL_BUG_REQUIRES_SCOPE_EXPANSION
    7. minor fix (expected_files 안) → MINOR_FIX_ALLOWED
    8. 기본 → REAL_BUG_IN_SCOPE

    Codex G1 권고 반영: false-positive/style/fixed 검사가 expected_files
    scope expansion 검사보다 먼저 실행되어, benign 코멘트가 단순 path 언급만으로
    Critical escalate되는 사고를 방지한다.
    id 
isOutdatedFcomments c              3  @   K   | ]  }|j                  d d        yw)bodyr8   N)get).0cs     r   	<genexpr>z"classify_thread.<locals>.<genexpr>   s     8!AEE&"%8s   N   Tun   [AUTO-OUTDATED] 이 스레드는 PR head 변경 후 outdated 처리되었습니다. Evidence: isOutdated=True.)outdatedmatched_patternr    r!   r#   r$   r%   r'   z0[AUTO-REAL_BUG_SCOPE_EXPANSION] forbidden path 'uh   ' 수정 요구 감지. 이 변경은 금지된 파일 경로에 영향을 미칩니다. Evidence: path=z, forbidden=.)rD   rC   forbidden_pathzregex/wrapper false-positiveu(   hardcoded path 코멘트 (PR #56 패턴)z[AUTO-FALSE_POSITIVE] u+   으로 판단됩니다. Evidence: matched='z'.false_positivehardcoded_path)rD   matched_kindrC   ud   [AUTO-STYLE_ONLY] 코드 스타일 의견으로 semantic 동등 판단됩니다. Evidence: matched=')rD   rC   shamessagefilesz:`'",./()[]   c              3  b   K   | ]&  }|r"|j                         j                         v  ( y wN)lower)r?   word
commit_msgs     r   rA   z"classify_thread.<locals>.<genexpr>  s0      
 JJLJ,,..
s   ,/
   c              3  H   K   | ]  t        fd D                yw)c              3  2   K   | ]  }|v xs |v   y wrP   r   )r?   cfpaths     r   rA   z,classify_thread.<locals>.<genexpr>.<genexpr>  s!     @Rd
(dbj(@   Nany)r?   rX   commit_filess    @r   rA   z"classify_thread.<locals>.<genexpr>  s$      
 @<@@
   "u/   [AUTO-CODE_ALREADY_FIXED] 이미 후속 commit zN/Au3   으로 반영되었습니다. Evidence: commit_sha=z, sha_mentioned=commit_body_keywordsha_mentionedpath_in_commit)fix_commit_sharC   rD   c              3  ~   K   | ]4  }|v xs* |v xs$ j                  |      xs |j                         6 y wrP   )endswithr?   efrX   s     r   rA   z"classify_thread.<locals>.<genexpr>4  sE      
 $JN$"*Nb(9NR[[=NN
s   :=u;   [AUTO-REAL_BUG_SCOPE_EXPANSION] expected_files 외 파일 'uU   ' 수정 요구 감지. scope expansion이 필요한 변경입니다. Evidence: path=z, expected_files=)rD   rC   expected_filesc              3  H   K   | ]  t        fd D                yw)c              3  2   K   | ]  }|v xs |v   y wrP   r   rd   s     r   rA   z,classify_thread.<locals>.<genexpr>.<genexpr>N  s!     BRd
(dbj(BrY   NrZ   )r?   rX   rf   s    @r   rA   z"classify_thread.<locals>.<genexpr>M  s$      .
 B>BB.
r]   ur   [AUTO-MINOR_FIX_ALLOWED] expected_files 범위 내 작은 수정으로 자동 처리됩니다. Evidence: matched='uv   [AUTO-REAL_BUG_IN_SCOPE] expected_files 범위 내 실제 버그로 판단됩니다. 수동 검토가 필요합니다.default)rC   rD   pr_head_sha_prefix)r>   joinr   r   r   FILE_PATH_PATTERNfindallresearchescaper   r	   FORBIDDEN_PATH_INTRUSIONFALSE_POSITIVE_PATTERNHARDCODED_PATH_PATTERNgroupr   STYLE_HINT_PATTERNBUG_WORD_PATTERNr   splitlenstripr[   r"   r   (GEMINI_REAL_BUG_REQUIRES_SCOPE_EXPANSIONMINOR_FIX_PATTERNr   r   )threadpr_head_shafix_commitsrf   forbidden_pathsr    is_outdatedr:   r=   head_sha_prefixextracted_pathsfp
fp_matchedhardcoded_matchedmatchedmatched_textreason_kindcommit
commit_shaw
body_wordskeyword_matchr_   r`   in_expectedin_scoper\   rS   rX   s      `                      @@@r   classify_threadr      s   . 

4$I**\51Kzz*b)H<D3888x88"D *5k"1o"O "!**- !"&<H

 
	
 (//5O ! 	BTzTRZ299RYYr]D+I*')BB"'J2$ O**.|B4qB %;$S$S+/$)*, 	* (..t4J.55d;--G}}8B4Hr"!00( 6&&2^27 !#/4> 0DT!
 	
"   &/?/F/Ft/L$++D1"!,,&9@gmmob%IM !6=7==?2!
 	
   'ZZr*
ZZ	2.
zz'2. 8<zz|i!s177SaKbGcghGhaggn-i
i 
"3B
 
 Z(CZ^t-C   
'
 
 49 	
 M^&#%88"EXbjQSRSnhmEn o,,6<7GVWY !%&0 %@M'<+8>N	 /'T    
$
 
 ~&#%>>#QRVQW X&&*V+<^<LAO !7 _ _'+ %&4 0 %&& 
# .
'.
 +
 >'..t4G&#%77"*=D'--/")MRQ !%:Aw}}r %   //0 ("1
 i js   $"P8P8c                "    d|  d}ddddd| gS )uZ  gh api graphql resolveReviewThread mutation subprocess args를 반환.

    GraphQL mutation은 thread_id만 필요. owner/repo는 GitHub GraphQL API에서
    요구하지 않으므로 인자에서 제외.

    반환 예: ["gh", "api", "graphql", "-f",
              "query=mutation { resolveReviewThread(input: {threadId: \"...\"})" ...}]
    z2mutation { resolveReviewThread(input: {threadId: "z""}) { thread { id isResolved } } }ghapigraphql-fquery=r   )r    mutations     r   build_resolve_thread_argsr   x  s5     ?yk J. 	/  %DF8**=>>r   c                    | j                   r| j                   S | j                  j                  }dj                  d | j                  j                         D              }d| d| dS )uw   1~3 문장 dismiss 코멘트 생성.

    형식: "[AUTO-{verdict}] <분류 사유>. Evidence: <evidence 요약>."
    z, c              3  >   K   | ]  \  }}||dk7  r	| d|   y w)Nr8   =r   )r?   kvs      r   rA   z(build_dismiss_comment.<locals>.<genexpr>  s1      !q!=Q"W #Qqc
!s   z[AUTO-u"   ] 자동 분류 결과. Evidence: rF   )r$   r!   valuerk   r'   items)outcomer!   evidence_summarys      r   build_dismiss_commentr     ss    
 &&&oo##Gyy !&//557! 
 	 %&a	)r   c                   g }| D ]\  }|j                   j                  t        v r+t        |j                        }t
        j                  d|       d}|rd}dD ]f  }	 t        j                  |ddd      }|j                  dk(  rd} n9t
        j                  d|j                  ||j                  |j                         h |s t
        j                  d|j                         t!        |j                  |j                   ||j"                  xs t%        |      |j&                  i |j(                  d|r|ndi      }	|j+                  |	       L|j+                  |       _ |S # t        j                  $ r% t
        j                  d	|j                  |       Y Et        t        j                  f$ r-}t
        j                  d
|j                  ||       Y d}~d}~ww xY w)u;  5종 (FALSE_POSITIVE / STYLE_ONLY / OUTDATED / CODE_ALREADY_FIXED / MINOR_FIX_ALLOWED) 자동 처리.

    apply=False (dry-run): subprocess args 합성만, 실제 호출 X.
    apply=True: subprocess.run으로 호출. retry 1회, timeout=30s.

    반환: outcome.auto_resolved=True 업데이트된 리스트.
    zresolve args: %sTF)         capture_outputtexttimeoutr   z0resolve thread %s failed (attempt=%d, rc=%d): %sz(resolve thread %s timed out (attempt=%d)z3resolve thread %s subprocess error (attempt=%d): %sNu<   resolve thread %s failed after retry — auto_resolved=Falseapply_succeededrE   )r!   r   AUTO_RESOLVABLEr   r    loggerdebug
subprocessrun
returncodewarningstderrTimeoutExpirederrorOSErrorSubprocessErrorr   r$   r   r%   r'   append)
outcomesapplyresultr   argsr   attemptproceupdateds
             r   auto_resolve_threadsr     s    F <#??  O3,W->->?DLL+T2 #O"'% G)~~ +/!%$&	   ??a/.2O!N#--# OO KK> 'LLV)) *!++- ' 7 7 Y;PQX;Y ' 7 7dG,,d.?TY_cdG MM'"MM'"y<#| ME &44 F#--#
 $Z%?%?@ Q#--#	 s$   *E'	7E''4G#G#6"GG#c                b   | D cg c]!  }|j                   t        j                  k(  r|# }}| D cg c]  }|j                  s| }}| D cg c]  }|j                  s| }}t	        |      }t	        |      }|dkD  rMt        d |D              }|st        d |D              rt        j                  nt        j                  }d| d}	n9|dkD  rt        j                  }d| }	nt        j                  }dt	        |       }	|dk(  xr |dk(  }
t        ||dd|
|		      S c c}w c c}w c c}w )
u  contracts freeze ReviewGateStatus 생성.

    gemini_status:
      - blocking 있으면 GEMINI_SCOPE_EXPANSION 또는 GEMINI_REAL_BUG
      - 모두 auto_resolved → GEMINI_COMPLETED
      - 일부 unresolved → GEMINI_UNRESOLVED
    unresolved_threads = blocking + 자동처리 외 잔여
    review_gate_passed = blocking_count == 0 AND unresolved_count == 0
      (REAL_BUG_IN_SCOPE 잔여가 있으면 후속 wiring이 머지하지 않도록 차단)
    r   c              3  V   K   | ]!  }|j                   t        j                  k(   # y wrP   )r%   r	   rq   r?   os     r   rA   z+build_review_gate_status.<locals>.<genexpr>  s*      
 !7!P!PP
   ')c              3  V   K   | ]!  }|j                   t        j                  k(   # y wrP   )r%   r	   rz   r   s     r   rA   z+build_review_gate_status.<locals>.<genexpr>  s,      T
 !7!`!``T
r   zblocking_thread_count=u!   , REAL_BUG_SCOPE_EXPANSION 존재zunresolved_threads=zall auto_resolved=F)gemini_statusunresolved_threadsfallback_review_usedfallback_review_passedreview_gate_passedreason)r!   r   r   r#   rx   r[   r   GEMINI_SCOPE_EXPANSIONGEMINI_REAL_BUGGEMINI_UNRESOLVEDGEMINI_COMPLETEDr   )r   r   blockingr#   
unresolvedblocking_countr0   has_forbiddenr   r   r   s              r   build_review_gate_statusr     st    99>>> 	
H  !)<1AOOQ<M< 	
J  ]N: 

 
 @MPS T
T
 Q
;; ++ 	 *.)99Z[	A	$66&'7&89$55%c-&8%9: (1,F1AQ1F#+"$- E =s   &D"D'D'D,c           	        t        d | D              }t        d | D              }t        d | D              }t        d | D              }t        d | D              }| D cg c]4  }|j                  r&d|j                   d|j                  j                   6 }}|dkD  rt
        j                  }n&|dkD  rt
        j                  }nt
        j                  }t        |||||||	      S c c}w )
uc  contracts freeze GeminiTriageResult로 집계 변환.

    false_positive_count, style_only_count: 직접 매핑
    real_bug_small_count = REAL_BUG_IN_SCOPE + MINOR_FIX_ALLOWED
    scope_expansion_count = REAL_BUG_SCOPE_EXPANSION (forbidden 포함)
    unresolved_count = blocking + 잔여
    actions_taken = ["resolved:<thread_id>:<verdict>", ...]
    c              3  \   K   | ]$  }|j                   t        j                  k(  s!d  & ywr   N)r!   r   r   r   s     r   rA   z'build_triage_summary.<locals>.<genexpr>:  s&      qyyM,H,HH   ",,c              3  \   K   | ]$  }|j                   t        j                  k(  s!d  & ywr   )r!   r   r   r   s     r   rA   z'build_triage_summary.<locals>.<genexpr>=  s&      qyyM,D,DDr   c              3  x   K   | ]2  }|j                   t        j                  t        j                  fv rd  4 ywr   )r!   r   r   r   r   s     r   rA   z'build_triage_summary.<locals>.<genexpr>@  s5      9988-:Y:YZZ 	
s   8:c              3  \   K   | ]$  }|j                   t        j                  k(  s!d  & ywr   )r!   r   r   r   s     r   rA   z'build_triage_summary.<locals>.<genexpr>D  s&       qyyM,R,RR r   c              3  :   K   | ]  }|j                   rd   ywr   r#   r   s     r   rA   z'build_triage_summary.<locals>.<genexpr>G  s     Faoo1F   z	resolved::r   )statusfalse_positive_countstyle_only_countreal_bug_small_countscope_expansion_countr0   actions_taken)
sumr#   r    r!   r   r   r   r   r   r   )	r   r   r   r   r   r0   r   r   r   s	            r   build_triage_summaryr   1  s                   FhFF ?? AKK=!))//!23M  q 44	A	//..1)13)# s   9C(c                   | D cg c]!  }|j                   t        j                  k(  r|# }}|sy|d   }|j                  xs t        j
                  }t        |||d| d|j                   d|j                   j                   dg ddt        |      |j                  |j                   j                  |r|j                  ndd	
      S c c}w )u|   Critical #1/#3 escalation packet 생성. blocking 있으면 첫 case 기준 packet 반환.
    blocking 없으면 None.
    Nr   zPR #u)   에서 scope expansion 감지: thread_id=z
, verdict=uu   REAL_BUG_SCOPE_EXPANSION 또는 FORBIDDEN_PATH_INTRUSION이 존재하여 자동 머지를 진행할 수 없습니다.)!   thread를 수동 검토 후 closeu   scope 조정 후 PR 재제출u   회장 직접 승인r   )r2   first_thread_idfirst_verdictfirst_escalation)task_idr-   r%   r   why_auto_cannot_continuesafe_optionsrecommended_optionr'   )
r!   r   r   r%   r	   rz   r
   r    r   rx   )r   r   r-   r   r   firstr%   s          r   detect_scope_expansionr   e  s     99>>> 	
H  QKE++n/E/n/nO'9+ )EMM4G4G3HJ;

 ?%(]$"]]00)8%%d
% s   &C	c           
        |D cg c]  }|j                  dd      r| }	}g }
|	D ]#  }t        |||||      }|
j                  |       % t        |
|      }
t	        d |
D              }|
D cg c]"  }|j
                  t        j                  k(  s!|$ }}t        |      }|
D cg c]  }|j                  r| }}t        |      }|dk(  }t        |
      }t        |
      }t        |
||       }|7t        j                  d| ||j                  j                   |j"                         t%        | |
||||||	      S c c}w c c}w c c}w )
uk   orchestration. 모든 thread classify → auto_resolve → ReviewGateStatus 생성 → TriageReport 반환.
isResolvedF)r|   r}   r~   rf   r   )r   c              3  :   K   | ]  }|j                   sd   ywr   r   r   s     r   rA   ztriage_pr.<locals>.<genexpr>  s     EAQ__aEr   r   )r   r-   z=Critical escalation detected: pr=%s task=%s type=%s reason=%s)r-   r/   r0   r1   r2   r3   r4   r5   )r>   r   r   r   r   r!   r   r   rx   r#   r   r   r   r   r   r%   r   r   r+   )r-   r/   r}   r~   rf   r   r   r   tactive_threadsr   r|   r   r1   r   r   r2   r   r0   r3   r4   r5   escalation_packets                          r   	triage_prr     sr    ")KAlE0JaKNK +-H  !!##)+
 	 ! $HE:H EEE#[aqyyM4Z4Z'Z[H[M%=Q__!=J=:+q0O 2(; *(3N /xT]^$K--33$$	
 )/3'-%	 	U L& \=s"   E E 0"EE(E
:E
c                   | j                   dkD  rd}n| j                  dkD  rd}nd}| j                  D cg c]  }|j                  s|j                   }}||| j
                  | j                   | j                  dS c c}w )u<  merge_queue_executor가 기존 호출하는 dict 인터페이스로 변환.

    {
      "status": "critical_scope_expansion" | "auto_triage_candidate" | "completed",
      "unresolved": [thread_id, ...],
      "auto_resolved_count": int,
      "blocking_thread_count": int,
      "merge_readiness": bool,
    }
    r   critical_scope_expansionauto_triage_candidate	completed)r   r   r1   r2   r3   )r2   r0   r/   r#   r    r1   r3   )reportr   r   unresolved_idss       r   to_legacy_gemini_stater    s     ##a'+		 	 1	$(  	
N  $%99!'!=!=!11 s   A<c                   g }g }	 t        |       j                  d      }t        j                  d|      }|rt|j	                  d      j                         D ]R  }|j                         j                  d      j                         j                  d      }|sB|j                  |       T t        j                  d|      }|r|j	                  d      j                         D ]e  }|j                         j                  d      j                         j                  d      }d|v s|j                  d	      sU|j                  |       g ||fS # t        $ r#}t        j                  d
|       Y d}~||fS d}~ww xY w)ux   task md에서 expected_files / forbidden 파싱.

    allowed_resources 블록 또는 expected_files 블록 대상.
    utf-8encodingz'expected_files:\s*\n((?:\s+-\s+.+\n?)+)r   -"z*forbidden_actions:\s*\n((?:\s+-\s+.+\n?)+)/z.pyztask file parse failed: %sN)r   	read_textrn   ro   rt   
splitlinesry   lstripr   rc   	Exceptionr   r   )	task_fileexpected	forbiddencontentef_matchlinefa_matchexcs           r   _parse_expected_files_from_taskr    s_   
 HI:y/++W+=996
  q)446 *zz|**3/557==cBOOD)* 999
  q)446 +zz|**3/557==cB$;$--"6$$T*	+ Y  :3S99Y:s%   BE BE 9E 	E<E77E<a  
query($pr: Int!, $owner: String!, $repo: String!) {
  repository(owner: $owner, name: $repo) {
    pullRequest(number: $pr) {
      headRefOid
      reviewThreads(first: 100) {
        nodes {
          id
          isOutdated
          isResolved
          comments(first: 50) { nodes { body } }
        }
      }
      commits(last: 30) {
        nodes { commit { oid message } }
      }
    }
  }
}
c                    	 t        j                  g dddd      } | j
                  dk7  r3t	        d| j
                   d	| j                  j                                	 t        j                  | j                        }|d
   d   |d   fS # t        t         j                  f$ r}t	        d|       |d}~ww xY w# t        t        f$ r}t	        d|       |d}~ww xY w)uC   현재 git remote에서 owner/repo 추출. 실패 시 RuntimeError.)r   repoviewz--jsonz
owner,nameT   r   u   gh repo view 실패: Nr   zgh repo view failed (rc=): ownerloginnameu#   gh repo view 응답 파싱 실패: )r   r   r   r   RuntimeErrorr   r   ry   jsonloadsstdoutKeyError
ValueError)r   r  infos      r   _detect_owner_repor&  =  s    C~~:	
 !&t&7s4;;;L;L;N:OP
 	
Qzz$++&G}W%tF|33 Z//0 C23%89sBC j! Q@FGSPQs/   B  +B< B9%B44B9<CCCc                   t               \  }}ddddd|  dd| dd| dd	t         g}	 t        j                  |d
d
d      }|j                  dk7  r3t        d|j                   d|j                  j                                	 t        j                  |j                        }|d   d   d   }|j                  dd      xs d}|d   d   }	|	D 
cg c]l  }
|
d   |
j                  dd      |
j                  dd      |
j                  di       j                  dg       D cg c]  }d|j                  dd      i c}dn }}
}|d    d   }|D cg c]  }|d!   d"   |d!   d#   g d$ }}|||fS # t        t        j
                  f$ r}t        d|       |d}~ww xY wc c}w c c}}
w c c}w # t        t        f$ r}t        d%|       |d}~ww xY w)&u   실제 GitHub GraphQL로 PR reviewThreads + recent commits + headRefOid를 fetch.

    실패 시 RuntimeError를 발생 — caller가 보수적으로 처리해야 함 (clean 오판 방지).
    owner/repo는 `gh repo view`로 동적 추출.
    r   r   r   z-Fzpr=zowner=zrepo=r   r   Tr   r   u   gh api graphql 호출 실패: Nr   zgh api graphql failed (rc=r  data
repositorypullRequest
headRefOidr8   reviewThreadsnodesr7   r9   Fr   r:   r=   )r7   r9   r   r:   commitsr   oidrL   )rK   rL   rM   u%   gh api graphql 응답 파싱 실패: )r&  _FETCH_QUERYr   r   r   r   r  r   r   ry   r   r!  r"  r>   r#  r$  )r-   r  r  r   r   r  payloadpr_datahead_shathread_nodesr   r@   r/   commit_nodesr~   s                  r   _fetch_pr_threadsr6  S  sI    %&KE4eYI;ugdVn|n%DL~~d4dBO !((9T[[=N=N=P<QR
 	
S**T[[)&/,/>;;|R06B/8 "
  geeL%8eeL%8 UU:r266wC QUU62./	
 
 y)'2 "
 	 {5)X;y1
 
 X--E Z//0 L;C5ABKL$

 j! SB3%HIsRSsa   E5 AF5 AF*$F%?F*F5 F0.F5 5F"FF"%F**F5 5GGGc                    t        j                  d      } | j                  dt        dd       | j                  dddd	
       | j                  dddd
       | j                  dt        dd       | j                  dt        dd       | j                         }|j                  }g }g }|j                  rt        |j                        \  }}d}d}|j                  rp	 t        j                  t        |j                        j                  d            }|j                  dg       }|j                  dg       }	|j                  dd      }
n	 t'        |j(                        \  }}	}
t-        |j(                  ||
|	|||d      }|j.                  ||t1        |j2                        t1        |j4                        |j6                  |j8                  |j:                  |j<                  t?        |      d
}tA        t        jB                  |dd             |rtE        jF                  d        |j:                  d!kD  rtE        jF                  d       |j8                  d!kD  rtE        jF                  d        tE        jF                  d!       y # t        t         f$ r.}t"        j%                  d|       d}d| }g g d}
}	}Y d }~Yd }~ww xY w# t*        $ r4}t"        j%                  d|       d}t	        |      }g g d}
}	}Y d }~d }~ww xY w)"NuA   auto_gemini_triage — Gemini review thread 자동 분류/resolve)descriptionz--prTz	PR number)typerequiredhelpz	--dry-run
store_truezdry-run mode (default))actionri   r;  z--applyFu   실제 GraphQL 호출z--task-filer8   u   task md 파일 경로)r9  ri   r;  z	--fixtureu   JSON fixture 파일 경로r  r  r/   r~   r}   zfixture load failed: %szfixture load failed: zPR fetch failed: %s)r-   r/   r}   r~   rf   r   r   r   )
r-   fetch_failedfetch_error_reasonr4   r5   r1   r0   r2   r3   legacyr   )indentensure_asciir   r   )$argparseArgumentParseradd_argumentr,   r   
parse_argsr   r  r  fixturer   r!  r   r
  r>   r   r$  r   r   r6  prr  r   r-   r   r4   r5   r1   r0   r2   r3   r  printdumpssysexit)parserr   r   rf   r   r>  r?  fixture_datar/   r~   r}   r  r   outputs                 r   mainrP    s   $$WF S4kJ
L$Mef
	,Lcd
CBYZ
#r@\]DJJE !#N!#O~~*I$..*Y' L||		;::d4<<&8&B&BG&B&TUL"&&y"5G&**="=K&**="=K	;0A$''0J-G[+ ''%'	F %%$0$V%>%>? !6!67%99"33!'!=!=!11(0F 
$**VAE
:; ##a'"HHQKc $ 	;LL2C8L#8!>02B+[G		;  	;LL.4L!$S02B+[G		;s1   !A.J K K#KK	L)LL__main__)r|   r&   r}   r   r~   
list[dict]rf   	list[str]r   rS  returnr   )r    r   rT  rS  )r   r   rT  r   )F)r   r.   r   r"   rT  r.   )r   r.   rT  r   )r   r.   rT  r   )r   r.   r   r   r-   r,   rT  zOptional[EscalationPacket])Fr8   )r-   r,   r/   rR  r}   r   r~   rR  rf   rS  r   rS  r   r"   r   r   rT  r+   )r   r+   rT  r&   )r  r   rT  ztuple[list[str], list[str]])rT  ztuple[str, str])r-   r,   rT  z"tuple[list[dict], list[dict], str])rT  None)9__doc__
__future__r   rC  r   loggingrn   r   rK  dataclassesr   r   enumr   pathlibr   typingr   utils.automation_contractsr	   r
   r   r   r   	getLoggerr   r   compile
IGNORECASErr   ru   rv   rl   r{   rs   DOTALLCODE_QUOTE_PATTERN	frozensetr   r   r   r   r+   r   r   r   r   r   r   r   r   r  r  r0  r&  r6  rP  r   r   r   <module>rd     s  4 #    	  
 )     
		8	$ $hMM   RZZoMM  2::eMM  BJJ0 MM	  BJJ@MM  $CMM   RZZ8II    :C :    ' ' 'UUU U 	U
 U Ux?(2 J'JJ Jb6'66z-h,',, ,  	,t ??? ? 	?
 ? ? ? ? ?LJ!H,Q,4SnM` zF r   