
    ƿi7=                    f   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mZm	Z	 ddl
mZ ddlmZ  eddh      Z ed	      Z ej"                  d
ej$                        ZdZddZddZdddZdddZdddZd dZd!dZ	 d	 	 	 	 	 d"dZdd	 	 	 	 	 d#dZdd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d$dZy)%u6  utils/gemini_gate_validator.py — thread-aware Gemini gate 검증기.

task-2472 구현 4: body keyword grep만으로는 부족한 기존 Gemini gate 보강.
latestReviews + comments + threads + image badge 종합 판정.

fail-closed: 어느 gh API 호출이라도 실패 시 fetch_ok=False → 자동 FAIL.
    )annotationsN)datetimetimezone)Path)Optionalzgemini-code-assist[bot]zgemini-code-assistz5memory/orchestration-audit/gemini-gate-decision.jsonlz'!\[\s*(high|medium|critical|low)\s*\]\(a  
query ReviewThreads($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 50) {
        nodes {
          id
          isResolved
          comments(first: 50) {
            nodes {
              body
              author { login }
            }
          }
        }
      }
    }
  }
}
c                 f    t        j                  t        j                        j	                  d      S )Nz%Y-%m-%dT%H:%M:%SZ)r   nowr   utcstrftime     N/home/jay/workspace/.worktrees/task-2487-B-dev2/utils/gemini_gate_validator.py_now_isor   >   s!    <<%../CDDr   c                    t        j                  | dd      }t        j                  |j	                  d            j                         S )NTF)	sort_keysensure_asciiutf-8)jsondumpshashlibsha256encode	hexdigest)record
serializeds     r   _evidence_hash_strr   B   s7    FdGJ>>*++G45??AAr   c                \    | xs) t        t        j                  j                  dd            S )NWORKSPACE_ROOTz/home/jay/workspace)r   osenvironget	workspaces    r   _workspace_rootr$   G   s#    URZZ^^,<>STUUr   c                (   	 t        j                  dd| gdd|dd      }|j                  dk7  s|j                  j	                         sy	 dt        j                  |j                        fS # t
        j                  $ r Y yw xY w# t        $ r Y yw xY w)uL   gh api {endpoint} → (ok, parsed_json).

    실패 시 (False, None).
    ghapiTFcapture_outputtexttimeoutshellcheckr   FN)	
subprocessrun
returncodestdoutstripr   loadsJSONDecodeError	Exception)endpointr+   procs      r   _gh_api_jsonr9   K   s    
~~5(#
 ??at{{'8'8':	DKK000## 		 s0   AB  A, ,B?B BB 	BBc           
     f   	 t        j                  |      }t        j                  ddddd|  dd| gdd|dd	      }|j                  d
k7  s|j
                  j                         sy	 dt        j                  |j
                        fS # t         j                  $ r Y yw xY w# t        $ r Y yw xY w)uI   gh api graphql 호출 → (ok, data).

    실패 시 (False, None).
    r&   r'   graphqlz-fzquery=z
variables=TFr(   r   r.   )
r   r   r/   r0   r1   r2   r3   r4   r5   r6   )query	variablesr+   vars_strr8   s        r   _gh_graphqlr?   c   s    
::i(~~eYug&
8*-
  
 ??at{{'8'8':	DKK000## 		 s0   A&B$ * B B!B$  B!!B$ $	B0/B0c                ^    | j                  dd      }t        |      dk7  r| | fS |d   |d   fS )u-   'owner/name' 형식 파싱 → (owner, name)./      r   )splitlen)repopartss     r   _parse_reporH      s:    JJsAE
5zQTz8U1Xr   c                    ddddd}| s|S t         j                  |       D ]3  }|j                  d      j                         }||v s'||xx   dz  cc<   5 |S )u   image markdown alt-text 기반 severity 탐지.

    패턴: ![high](...) ![medium](...) ![critical](...) ![low](...)
    Returns {"high": N, "medium": N, "critical": N, "low": N}
    r   )highmediumcriticallowrB   )_IMAGE_SEVERITY_PATTERNfinditergrouplower)r*   countsmsevs       r   detect_image_severityrU      sf     '(1!ANF$--d3 ggaj &=3K1K Mr   c                   | t         j                  j                  dd      }g }t        d| d|  d      \  }}|s|j	                  d       g }t        d| d|  d      \  }}|s|j	                  d       g }t        d| d	|  d      \  }}|s|j	                  d
       g }t        |      \  }	}
t        t        |	|
| d      \  }}g }|rt        |t              r	 |d   d   d   d   d   }|D ]y  }d}|j                  di       j                  d      r|d   d   d   j                  dd      xs d}|j	                  |j                  dd      |j                  dd      |d       { n|j	                  d       |xr |xr |xr |xr | }t        |t              r|ng t        |t              r|ng |t        |t              r|ng ||dS # t        t        f$ r |j	                  d       Y tw xY w)u\  gh API로 PR review 데이터 종합 수집.

    수집 항목:
    - latestReviews: /repos/{repo}/pulls/{pr}/reviews
    - reviewComments: /repos/{repo}/pulls/{pr}/comments
    - reviewThreads: GraphQL (isResolved + comments[0].body)
    - issueComments: /repos/{repo}/issues/{pr}/comments

    fail-closed: 어느 호출이라도 실패 시 fetch_ok=False.

    Returns
    -------
    dict
        {
            "reviews": [...],
            "comments": [...],
            "threads": [...],
            "issue_comments": [...],
            "fetch_ok": bool,
            "errors": [...]
        }
    GH_REPOzJeon-Jonghyuk/dev_workspacezrepos/z/pulls/z/reviewszreviews fetch failedz	/commentszcomments fetch failedz/issues/zissue_comments fetch failed)ownernamenumberdata
repositorypullRequestreviewThreadsnodes commentsr   bodyid
isResolvedF)rc   rd   rb   z"reviewThreads GraphQL parse failedz"reviewThreads GraphQL fetch failed)reviewsra   threadsissue_commentsfetch_okerrors)r   r    r!   r9   appendrH   r?   _REVIEW_THREADS_QUERY
isinstancedictKeyError	TypeErrorlist)	pr_numberrF   ri   ok1re   ok2ra   ok3rg   rX   rY   ok4gql_datarf   r_   noderb   rh   s                     r   fetch_pr_review_datarx      s;   6 |zz~~i)FGF  &gi[ IJLC,- !6$wyk!KLMC-. 'vXi[	'RSC34 d#KE4;MC G
z(D)	@ .}=oNwW   
88J+//8
+G4Q7;;FBGM2D"hhtR0&*hh|U&C $	
 	:; 9s9s9s96zH )$77R *8T :H,6~t,L.RT  )$ 	@MM>?	@s   BG  G32G3T)block_unresolved_mediumc               Z   | j                  dd      s!dd| j                  dg        ddddddg ddS | j                  d	g       }t        d
 |D              }|sddddddddg ddS g }|D ]8  }t        |t              s|j	                  |j                  dd      xs d       : | j                  dg       D ]8  }t        |t              s|j	                  |j                  dd      xs d       : | j                  dg       D ]8  }t        |t              s|j	                  |j                  dd      xs d       : dj                  |      }t        j                  dt        j                        }	ddddd}
|	j                  |      D ]J  }|j                  d      xs |j                  d      xs dj                         }||
v s>|
|xx   dz  cc<   L t        |      }|d   |d   z   }i |
d|i}|
d   dkD  s|
d   dkD  rdd|
d    d|
d    |g ddS | j                  dg       }g }|D ]  }t        |t              s|j                  dd      r'|j                  dd      xs d}ddddd}d}d }t        j                  d!|t        j                        D ]U  }|j                  d      xs |j                  d      xs dj                         }|j                  |d       |kD  sO||   }|}W |d"v s|j	                  |j                  d#d      ||d$d% d&        |D cg c]  }|d'   d(v s| }}|rdd)t        |       d*||ddS |D cg c]  }|d'   d+k(  s| }}|r,|rdd,t        |       d-||ddS d.d,t        |       d/||ddS d0d1|g ddS c c}w c c}w )2u  Gemini gate 종합 판정.

    판정 규칙:
    1. reviews 중 Gemini bot 작성 review 0개 → FAIL ("Gemini review 부재")
    2. reviewThreads 중 unresolved && severity in (medium,high,critical) → FAIL
    3. body/comment 텍스트 또는 image badge에 high/critical → FAIL
    4. medium unresolved thread → block_unresolved_medium=True 시 FAIL,
       False 시 PASS_WITH_MEDIUM

    Returns
    -------
    dict
        {
            "verdict": "PASS" | "FAIL" | "BLOCKED" | "RECOVERABLE_BLOCKED",
            "reason": str,
            "severity_counts": {"high": N, "medium": N, "low": N, "critical": N, "image_high": N},
            "unresolved_threads": [{"id":..., "severity":..., "body":...}, ...],
            "gemini_review_present": bool,
        }
    rh   FFAILu   PR 데이터 fetch 실패: ri   r   )rJ   rK   rM   rL   
image_high)verdictreasonseverity_countsunresolved_threadsgemini_review_presentre   c              3     K   | ]@  }t        |t              r.|j                  d i       xs i j                  dd      t        v  B yw)userloginr`   N)rl   rm   r!   GEMINI_BOT_AUTHORS).0rs     r   	<genexpr>z evaluate_gate.<locals>.<genexpr>  sE       a 
vr		 b%%gr26HH s   AAu;   Gemini review 부재: gemini-code-assist[bot] 리뷰 없음rb   r`   ra   rg   
za(?:severity|priority)\s*[:=]\s*(high|medium|low|critical)|!\[\s*(high|medium|low|critical)\s*\]\()rJ   rK   rM   rL   rB   rC   rJ   rL   r|   u$   high/critical severity 발견: high=z, critical=Trf   rd      )rM   rK   rJ   rL   rM   za(?:severity|priority)\s*[:=]\s*(high|medium|critical|low)|!\[\s*(high|medium|critical|low)\s*\]\()rK   rJ   rL   rc   N   )rc   severityrb   r   )rJ   rL   z unresolved high/critical thread u   건rK   zunresolved medium thread u"   건 (block_unresolved_medium=True)RECOVERABLE_BLOCKEDu;   건 (block_unresolved_medium=False → RECOVERABLE_BLOCKED)PASSuY   Gemini gate 통과: review 존재, unresolved medium+ thread 없음, high/critical 없음)r!   anyrl   rm   rj   joinrecompile
IGNORECASErO   rP   rQ   rU   rE   )pr_datary   re   r   	all_textsr   ciccombinedsev_pattern
sev_countsrS   rT   img_sevr|   r   rf   r   trb   severity_rank
thread_sev	best_rankhithigh_crit_unresolvedmedium_unresolveds                             r   evaluate_gater      s   4 ;;z5)3GKK"4M3NO()Qqa_`a"$%*
 	
 kk)R(G     !S()Qqa_`a"$%*
 	
 I 6aQUU62.4"56 [[R( 6aQUU62.4"56 kk*B/ 7b$RVVFB/5267 yy#H **	3
K
 +,qPQ!RJ!!(+ !wwqz-QWWQZ-2446*sOq O! $H-G7:#66J>>\:>O &AJ!7!!;6z&7I6J K&z235  /"$%)	
 		
 kk)R(G%' aquu\4'@55$*D %&A1MMJI[[;	 	% wwqz8QWWQZ85??A $$S"-	9 -c 2I!$J	% ;;"))55r?
DQURUJW)4 &::N)N  8=Q9R8SSVW."4%)
 	
 &:()B  "!5c:K6L5MMop#2&8)-  15c:K6L5M  NI  J#2&8)-  m* !% Es   +N#8N#N('N(r"   c        	   
     8   t               }	| ||||||||	d	}
t        |
      }i |
d|i}t        |      }|t        z  }|j                  j                  dd       t        j                  |d      dz   }t        j                  t        |      t        j                  t        j                  z  t        j                  z  d      }	 t        j                  ||j                  d	             t        j                   |       |S # t        j                   |       w xY w)
u  Gemini gate 판정 결과 audit 기록.

    memory/orchestration-audit/gemini-gate-decision.jsonl 에 line append.
    필수 필드 10개: task_id, pr_number, verdict, severity_counts,
    unresolved_threads, gemini_review_present, actor, reason, timestamp, evidence_hash
    )	task_idrq   r}   r   r   r   actorr~   	timestampevidence_hashT)parentsexist_okF)r   r   i  r   )r   r   r$   AUDIT_JSONL_RELparentmkdirr   r   r   openstrO_WRONLYO_APPENDO_CREATwriter   close)r   rq   r}   r   r   r   r   r~   r#   r   base_recordev_hashr   	work_roottargetlinefds                    r   record_gate_decisionr     s    $ 
I*0!6
K !-G66_g6F	*I(F
MMt4::f51D8D	VbkkBKK7"**De	LB
T[[)*
M 	s   %D D)returnr   )r   rm   r   r   )N)r#   Optional[Path]r   r   )   )r7   r   r+   intr   tuple[bool, object])r<   r   r=   rm   r+   r   r   r   )rF   r   r   ztuple[str, str])r*   r   r   rm   )rq   r   rF   z
str | Noner   rm   )r   rm   ry   boolr   rm   )r   r   rq   r   r}   r   r   rm   r   rp   r   r   r   r   r~   r   r#   r   r   r   )__doc__
__future__r   r   r   r   r   r/   r   r   pathlibr   typingr   	frozensetr   r   r   r   rN   rk   r   r   r$   r9   r?   rH   rU   rx   r   r   r   r   r   <module>r      sS   #   	 	  '    9;OPQ  NO %"**.MM  4EB
V0:$ WW
W 
Wz %)ff "f 
	ff !%,, , 	,
 , ,  , , , , 
,r   