
    Ri)o              	          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mZ ddlm	Z	 ej                  j                  dd      Zd Zded	edz  fd
Zded	ee   fdZded	e	fdZdee   d	eeee   f   fdZdee   ded	eeee   f   fdZdee   d	eeee   f   fdZdee   d	eeee   f   fdZded	ee	   fdZdee	   d	eeef   fdZded	edz  fdZded	edz  fdZdeded	eeef   fdZdededee   fdZded	eeee   f   fdZdee   ded	eeee   f   fd Z ded	eeee   f   fd!Z!ded	eeee   f   fd"Z"d# Z#e$d$k(  r e#        yy)%u   
g3_independent_verifier.py — .done 생성 전 독립 검증 스크립트
보고서에 명시된 핵심 변경사항이 실제 코드에 존재하는지 확인한다.

사용법:
    python3 g3_independent_verifier.py --task-id task-1883
    N)datetime)PathWORKSPACE_ROOTz/home/jay/workspacec                      t        j                  d      } | j                  ddd       | j                  ddd d	
       | j                         S )Nu+   .done 생성 전 독립 검증 스크립트)descriptionz	--task-idTu'   검증할 태스크 ID (예: task-1883))requiredhelpz	--team-idFuF   검증할 팀 ID (예: dev1-team). 지정 시 팀 범위 검증 수행)r   defaultr	   )argparseArgumentParseradd_argument
parse_args)parsers    P/home/jay/workspace/.worktrees/task-2117-dev1/scripts/g3_independent_verifier.pyr   r      sa    $$1^_F
6  
 U	       task_idreturnc                 ~    t        t              dz  dz  |  dz  }|j                         sy|j                  d      S )uB   보고서 파일을 읽어 내용을 반환한다. 없으면 None.memoryreports.mdNutf-8encoding)r   r   exists	read_text)r   report_paths     r   load_reportr   &   sD    ~&1I=7)3OK  ' 22r   report_contentc                    d| vrg S | j                  d      }| |d }t        j                  d|dd       }|r|d|j                         dz    }g }t        j                  d      }t        j                  d      }t        j                  dt        j
                        }d}t        j                  d	      }	|j                         D ]  }
|
j                         }
|j                  |
      s&|j                  |
      r8|
j                  d
      j                  d
      D cg c]  }|j                          }}t        |      dk  rh d}|d   j                         |v s$|d   j                         j                         dk(  r|d   j                         j                         dv r|d   j                         }|j                  |      s|	j                  |      s|d   j                         }|d   j                         }|d   j                         j                         }|j                  |      }|r|j                  d      nd}|j                  ||||d        |S c c}w )u  
    보고서에서 "수정 파일별 검증 상태" 테이블을 파싱한다.

    반환: [{"file_path": str, "description": str, "keyword": str | None, "status": str}, ...]
    테이블이 없으면 빈 리스트 반환 (Lv.2 이하 → SKIP 처리).
    u   수정 파일별 검증 상태Nz	\n#{2,}\s   z
^\|(.+)\|$z^\|[\s\-|]+\|$zgrep\s+"([^"]+)"\s+OK)scripts/teams/	projects/zworkers/utils/memory/tests/
dashboard/src/lib/config/prompts//z\.\w{1,5}(?::\d+)?$|   >       파일   파일 file   파일r   r3      >      상태status   )	file_pathr   keywordr6   )indexresearchstartcompile
IGNORECASE
splitlinesstripmatchsplitlenlower
startswithgroupappend)r   section_startsection_textnext_header_matchentriestable_row_reseparator_re
keyword_reKNOWN_PATH_PREFIXESFILE_EXTENSIONSlineccellsheader_keywordsfile_colr   grep_colr6   mr9   s                       r   parse_verification_tablerY   .   sL    (~=	 #(()IJM!-.1L		,QR0@A#$B%6%<%<%>%BCG ::m,L::/0L4bmmDJ
 jj!78O'') '
zz|!!$'d# %)JJsO$9$9#$>?q??u:> C8>>.%(..2B2H2H2Jh2V8>>!!#';;8>>#""#67@V@VW_@`Ahnn&8>>#q!'') h' !!''!*t%*" 		
A'
R NC @s   I(raw_pathc                     t        j                  d      }|j                  |       }|r|j                  d      } t	        |       }|j                         st	        t              |z  }|S )u   
    파일경로:라인번호 형식에서 순수 경로를 추출하고
    상대 경로면 WORKSPACE_ROOT 기준으로 절대 경로로 변환한다.
    ^(.+):(\d+)$r!   )r;   r>   rB   rG   r   is_absoluter   )rZ   colon_line_rerX   ps       r   resolve_file_pathr`   }   sX     JJ/MH%A771:XA==? 1$Hr   rL   c           
          | D cg c]  }|j                  d      dk(  s| }}|sdg fS |D cg c]$  }|j                  d|j                  dd            & }}dt        |       d| }d	|gfS c c}w c c}w )
uS   
    planned 상태 항목이 존재하면 FAIL.
    반환: (status, details)
    r6   plannedPASSr8   r   unknownu   planned 항목 u   건 발견: FAIL)getrD   )rL   erb   namesdetails        r   check_planned_itemsrj      s    
 "BQQUU8_	%AqBGBrzJQRQQUU;mY ?@RERs7|nL@FF8 C Ss   A0A0)A5team_idc           
      j   d}g }| D ]  }|d   }t        j                  d      }|j                  |      }|r|j                  d      n|j	                  d      rNt
        j                  d      dz   }j	                  |      rt        |      d n|j                  d| d       t        fd	|D              rj	                  d
      sʉj                  d      }	t        |	      dk\  r7|	d   }
|j                  dd      }|
|k7  r|j                  d| d|
 d| d       " |rd|fS dg fS )uj  
    verified 파일 경로가 해당 팀 범위 내인지 검증한다.
    범위 규칙:
      - teams/{team}/ 하위 파일은 해당 팀만 수정 가능
      - teams/shared/, scripts/, utils/, memory/, tests/, config/, prompts/ 등 공용 파일은 모든 팀 허용
    범위 밖 파일 발견 시 WARN (FAIL 아님).
    반환: (status, warnings)
    )zteams/shared/r"   r%   r&   r'   r+   r,   r(   r)   r*   r$   r8   r\   r!   r-   Nzteam_scope: z is outside WORKSPACE_ROOTc              3   @   K   | ]  }j                  |        y w)N)rF   ).0prefix
clean_paths     r   	<genexpr>z#check_team_scope.<locals>.<genexpr>   s     Kz$$V,Ks   r#   r7   z-team z belongs to team 'z', not ''WARNrc   )r;   r>   rB   rG   rF   r   rstriprD   rH   anyrC   replace)rL   rk   SHARED_PREFIXESwarningsentryrZ   r^   rX   ws_rootparts	file_team
team_shortrp   s               @r   check_team_scoper      sZ   O H %

?3)#$QWWQZ(
   %$++C036G$$W-'G6
 ,xj8R ST K?KK   *$$S)E5zQ!!H	$__Wb9

*OOl8*<NykYablammn$op?F x2:r   c                     g }| D ]>  }t        |d         }|j                         r"|j                  t        |d                @ |rdnd}||fS )um   
    각 항목의 파일이 실제로 존재하는지 확인한다.
    반환: (status, missing_files)
    r8   re   rc   )r`   r   rH   str)rL   missingrz   pathr6   s        r   check_file_existencer      s[    
 G 4 {!34{{}NN3u[1234
 VFF7?r   c           
      F   g }| D ]  }|d   dk7  r|d   }|st        |d         }|j                         s3	 t        j                  dd|t	        |      gddd	      }|j
                  j                         }|j                         rt        |      nd
}|d
k(  r|j                  d| d|d            |rdnd}	|	|fS # t        j                  $ r |j                  d| d|d           Y t        $ r(}|j                  d| d|d    d|        Y d}~d}~ww xY w)u   
    verified 상태인 항목에 대해 grep -c "키워드" 파일경로를 실행한다.
    0건이면 FAIL.
    반환: (status, failed_items)
    r6   verifiedr9   r8   grepz-cT   capture_outputtexttimeoutr   zgrep_verification: 'z' not found in z,grep_verification: timeout while searching 'z' in z$grep_verification: error searching 'z: Nre   rc   )r`   r   
subprocessrunr   stdoutrA   isdigitintrH   TimeoutExpired	Exception)
rL   failedrz   r9   r8   result	count_strcountrg   r6   s
             r   run_grep_verificationr      s_    F j?j(	"%eK&89	!	j^^wI7#	F ++-I&/&7&7&9C	NqEz 4WI_US^M_L`ab-j8 V6F6> (( 	mMMH	QVW\]hWiVjkl 	jMM@	uU`OaNbbdefdghii	js   A<C-D 0D 8DD c                     t        j                  d|       }|sg S |j                  d      }t        t              dz  }|j                         sg S d| d}t        |j                  |            }|S )u   
    태스크 ID에서 숫자 키를 추출하여 관련 테스트 파일을 탐색한다.
    패턴: tests/test_*{key}*.py
    z(\d+)r!   testsztest_*z*.py)r;   r<   rG   r   r   r   listglob)r   	key_matchkey	tests_dirpatternfounds         r   find_test_filesr   
  sp     		(G,I	
//!
C^$w.I	se4 G()ELr   
test_filesc                    | syg d| D cg c]  }t        |       c}z   ddgz   }	 t        j                  |dddt              }|j                  dk7  r@|j
                  |j                  z   j                         }t        |      d	kD  r|d
d n|}d|fS yc c}w # t        j                  $ r Y yt        $ r}dd| fcY d}~S d}~ww xY w)u>   
    pytest를 실행한다.
    반환: (status, reason)
    )SKIPzno test files foundpython3z-mpytest
--tb=short-qTr   r   r   r   cwdr   i  iNre   )rc   rr   )re   zpytest timeout (30s)zpytest error: )r   r   r   r   
returncoder   stderrrA   rD   r   r   )r   fcmdr   outputreasonrg   s          r   
run_pytestr     s    
 ,
%(DAQ(D
DVZG[
[C,
 !mmfmm3::<F&)&kC&7VDE]VF6>! )E $$ .- ,s+++,s)   BA,B C	0C	8C>C	C	c                     t        j                  d|       }t        j                  d|       }|syt        |j                  d            |rt        |j                  d            dS ddS )u   
    보고서에서 'N passed, M failed' 패턴을 추출.
    반환: {"passed": int, "failed": int} 또는 None (패턴 없으면)
    (\d+)\s+passed(\d+)\s+failedNr!   r   passedr   r;   r<   r   rG   )r   passed_matchfailed_matchs      r   parse_report_test_resultsr   :  sm     99.?L99.?L l((+,0<#l((+, BC r   r   c                     t        j                  d|       }t        j                  d|       }|s|sy|rt        |j                  d            nd|rt        |j                  d            dS ddS )u0   pytest 출력에서 passed/failed 수를 추출.r   r   Nr!   r   r   r   )r   r   r   s      r   parse_actual_pytest_outputr   L  sq    99.7L99.7L 1=#l((+,!0<#l((+, BC r   c                 x   t        |       }|dddifS t        |      }|sdddifS g d|D cg c]  }t        |       c}z   ddgz   }	 t        j                  |d	d	d
t
              }|j                  |j                  z   }t        |      }	|	dddifS |d   }
|	d   }|d   }|	d   }g }|
|k7  r|j                  d|
 d|        ||k7  r|j                  d| d|        |rddj                  |       }d||	|dfS d||	dfS c c}w # t        j                  t        f$ r}ddd| ifcY d}~S d}~ww xY w)uu   
    보고서의 테스트 결과와 실제 pytest 실행 결과를 비교.
    반환: (status, check_details)
    Nr   r   u+   보고서에 테스트 결과 패턴 없음u   관련 테스트 파일 없음r   r   r   T<   r   u   pytest 실행 실패: u   pytest 출력 파싱 실패r   r   u   failed: 보고서=u	   , 실제=u   passed: 보고서=u&   보고서 테스트 결과 불일치: z; re   )reportactualri   rc   )r   r   )r   r   r   r   r   r   r   r   r   r   r   rH   join)r   r   report_resultsr   r   r   r   actual_outputrg   actual_resultsreport_failedactual_failedreport_passedactual_passed
mismatches
detail_msgs                   r   cross_verify_test_resultsr   Z  s   
 /~>N"OPPP !)J"BCCC
%(DAQ(D
DVZG[
[C@DtRUcd5 0>N"?@@@ #8,M"8,M"8,M"8,MJ%.}oY}oVW%.}oY}oVW=dii
>S=TU
$$ 
 
 	
     ? )E %%y1 @$:1#">???@s#   D7D D9&D4.D94D9	timestampfail_reasonsc                     t        t              dz  dz  }|j                  dd       ||  dz  }| ||d}|j                  t	        j
                  |dd	      d
       y)uQ   FAIL 시 memory/events/{task_id}.g3-fail 파일에 차단 사유를 기록한다.r   eventsT)parentsexist_okz.g3-fail)r   r   r   Fr7   ensure_asciiindentr   r   N)r   r   mkdir
write_textjsondumps)r   r   r   
events_dir	fail_pathdatas         r   write_fail_filer     sm    n%08;JTD1y11I$D
 DuQGRYZr   c                    g }ddgddgddgddgd	}t               }|j                         D ]5  \  }}|D ]+  }t        j                  ||       s|j	                  |        5 7 t        |      d
k  r*|j                  dt        |       dt        |       d       t        |       }|dk  r|j                  d| d       |rdnd}||fS )ut   
    보고서 최소 품질 게이트 (V-5).
    SCQA 패턴 2개 이상 존재 + 200자 이상 분량 확인.
    z\*\*S\*\*\s*:u   #+\s*S\s*[-–—]z\*\*C\*\*\s*:u   #+\s*C\s*[-–—]z\*\*Q\*\*\s*:u   #+\s*Q\s*[-–—]z\*\*A\*\*\s*:u   #+\s*A\s*[-–—])SCQAr7   u   SCQA 패턴 부족: u(   개 발견 (최소 2개 필요, 발견: )   u   보고서 분량 부족: u   자 (최소 200자 필요)re   rc   )setitemsr;   r<   addrD   rH   sorted)	r   issuesscqa_patterns
found_scqaletterpatternsr   content_lengthr6   s	            r   check_report_qualityr     s    
 F  56 56 56 56	M J)//1  	Gyy.1v&	 :,S_,==eflmwfxeyyz{| (N1.1AA[\]V6F6>r   c                 d   t        d | D              }|dk  rdg fS 	 t        j                  dddd| gddd	t        
      }t	        |j
                  j                         j                         D cg c]  }|s|	 c}      }|dk  rdd| d| dgfS 	 dg fS c c}w # t        $ r dg fcY S w xY w)u;   수정 파일 5개 이상인데 커밋 1개 이하면 WARN.c              3   J   K   | ]  }|j                  d       dk(  sd  yw)r6   r   r!   N)rf   rn   rg   s     r   rq   z&check_micro_commits.<locals>.<genexpr>  s     MqquuX*/LMs   ##   rc   gitlogz	--onelinez--grep=T
   r   r!   rt   zmicro_commit: u   개 파일 수정, 커밋 u+   개 (5개+ 파일 시 다중 커밋 권장))	sumr   r   r   rD   r   rA   r@   r   )rL   r   verified_countr   rR   commit_counts         r   check_micro_commitsr     s    MGMMNrz
E;''(;<dB

 V]]-@-@-B-M-M-OXTSWDXY1n^,<<VWcVd  eP  Q  R  R  R 
 2: Y  rzs*   AB 1B9B=B B B/.B/c                    t        j                  d| t         j                        }|sddgfS |j                  d      xs |j                  d      }t        j                  d|       }|sddgfS |j                  d      |j                  d      j	                  d      }}t        j                  d	| t         j                        }|rt        |j                  d            nd
}	 t        j                  ddd| d| d| dgddd      }|j                  dk7  r	dd| dgfS t        j                  |j                        }	t        j                  dt         j                        }
d}|	D ]  }|j!                  di       j!                  dd      }d|j#                         vr8|j!                  dd      }|j#                         }d|v s&d|v s"d|v sd |v s|
j                  |      s	d!|v sd"|v s|dz  } d| d#| d$g}|!||k7  r|j%                  d%| d&| d$       d'|fS ||dkD  r|j%                  d(| d)       d'|fS d*|fS # t        j                  t        j                  t        f$ r ddgfcY S w xY w)+uO   보고서의 Gemini 리뷰 결과와 실제 PR 코멘트를 교차검증한다.zPR\s*#(\d+)|/pulls?/(\d+)r   u'   보고서에서 PR 번호 추출 실패r!   r7   z github\.com/([^/\s]+)/([^/\s#]+)u)   보고서에서 repo 정보 추출 실패r-   u   High\s+(\d+)\s*건Nghapizrepos/z/pulls/z	/commentsTr   r   r   zPR #u    코멘트 조회 실패u   PR 코멘트 조회 중 오류z&!\[(security-critical|critical|high)\]userloginrr   geminibodyzseverity: highzseverity: criticalHIGHCRITICALzhigh-priority.svgzcritical.svgu   : 실제 Gemini High=u   건u   불일치: 보고서 High=u   건, 실제 High=re   u0   보고서에 High 건수 미명시, 실제 High=u
   건 발견rc   )r;   r<   r?   rG   ru   r   r   r   r   r   loadsr   r   JSONDecodeErrorr   r>   rf   rE   rH   )r   pr_match	pr_number
repo_matchownerrepo
high_matchreport_highr   comments_high_reactual_highrS   r   r  
body_lowerdetailss                    r   check_gemini_reviewr    s    yy5~r}}UHABBBq!6X^^A%6I >OJCDDD""1%z'7'7':'A'A#'F4E 0."--PJ.8#j&&q)*dK	:5F5'4&	{)LMdB
 !d9+-EFGGG::fmm,
 zzCR]]SHK uuVR $$Wb14::<'uuVR ZZ\

*#z1~T!t$"j0+1K" i[ 5k]#FGG;+#=3K=@QR]Q^^abcw{QI+V`abw7?C %%t';';YG :8999:s   #=I !I -I43I4c                     t        t              dz  dz  dz  | z  dz  }|j                         sddgfS |j                  d      }t	        t        j                  d	|t
        j                              }|sd
dgfS dg fS )uJ   context-notes.md에 3 Step Why 기록이 있는지 확인. 없으면 WARN.r   planstaskszcontext-notes.mdr   uE   context-notes.md 파일 없음 (Lv.2 이하 또는 3문서 미생성)r   r   z1st\s+Why|2nd\s+Why|3rd\s+Whyrt   um   three_step_why: context-notes.md에 '1st Why/2nd Why/3rd Why' 패턴 없음. 3 Step Why 자문 기록 필요.rc   )r   r   r   r   boolr;   r<   r?   )r   context_pathcontenthas_whys       r   check_three_step_whyr    s    '(2W<wFPSeeL _```$$g$6G299=wVWG  H  I  I  	I2:r   c                  >   t               } | j                  }t        j                         j	                  d      }g }t        |      }|f|d|ddd| ddddiddiddid	d| dgd
}t        t        j                  |dd             t        |||d          t        j                  d       t        |      }|st        |      \  }}|dk(  r^|d|ddddd|dddiddiddid|d
}t        t        j                  |dd             t        |||       t        j                  d       |d|dddddg dddiddiddidg d
}t        t        j                  |dd             t        j                  d       dt        |      d}	t        |      \  }
}|
t        |D cg c]  }|j!                  d      dk(  s| c}      |d}|
dk(  r|j#                  |       ddd}| j$                  r)t'        || j$                        \  }}|| j$                  |d}t)        |      \  }}|t        |      |d}|dk(  r|D ]  }|j+                  d| d        t-        |      \  }}|t/        d |D              |d }|dk(  r|j#                  |       t1        |      }t3        |      \  }}d|i}|dk(  r||d!<   n!|dk(  r||d!<   |j+                  d"|d d#         t5        ||      \  }}d|i}|j7                  |       |dk(  r$|j+                  d$|j!                  d%d&              t9        ||      \  }} || d'}!dd(d}"|rEd)|j;                         v r3t=        |      \  }}#||#d*}"|dk(  r|#D ]  }$|j+                  d+|$         t?        |      \  }%}&|%|&d'}'|rdnd}(| |&z   })||(||	|||||||"|!|'d,
||)d-}t        t        j                  |dd             |(dk(  r"t        |||       t        j                  d       t        j                  d       y c c}w ).Nz%Y-%m-%dT%H:%M:%Sre   r   z!report not found: memory/reports/r   )r6   entries_founderrorr6   r   )report_parsefile_existencegrep_verificationpytest_execution)r   overallr   checksr   Fr7   r   r   r!   u/   no '수정 파일별 검증 상태' table found)r6   r  r   )r6   r   )r  report_qualityr   r!  r"  rc   u?   no '수정 파일별 검증 상태' table found (Lv.2 or below))r6   r  rb   )r6   planned_countr  zno --team-id specified)r6   r   )r6   rk   ry   )r6   checkedr   zfile_existence: z
 not foundc              3   >   K   | ]  }|d    dk(  s|d   sd  yw)r6   r   r9   r!   N r   s     r   rq   zmain.<locals>.<genexpr>  s!     XQQx[J-F1Y<qXs   )r6   r'  r   r   zpytest_execution: r   zcross_verify: ri   mismatch)r6   ry   zno gemini review info in reportr  )r6   r  zgemini_review: )
r  planned_check
team_scoper   r!  r"  cross_verifygemini_reviewmicro_committhree_step_why)r   r#  r   r$  r   ry   ) r   r   r   nowstrftimer   printr   r   r   sysexitrY   r   rD   rj   rf   extendrk   r   r   rH   r   r   r   r   r   updater   rE   r  r  )*argsr   r   r   r   r   rL   	rq_status	rq_issuesreport_parse_check	pc_status
pc_detailsrg   r+  team_scope_check	ts_statusts_warnings	fe_statusmissing_filesfile_existence_checkr   	gv_statusgrep_failedgrep_verification_checkr   	pt_status	pt_reasonpytest_check	cv_status
cv_detailscross_verify_check	mc_statusmc_warningsmicro_commit_checkgemini_check
gv_detailsdtswhy_statustswhy_warningsthree_step_why_checkr#  all_warningss*                                             r   mainrW    s   <DllG''(;<I L
 !)N" %%&@	M!
 $,V"4&.%7%-v$6	  A	MN
  	djjeA>?F>,BC&~6G 3NC	9"!& #))*"S% #)"+' (0&8*2F);)16(: !*%F( $**V%BCGY	:HHQK " %%&_! % # $,V"4&.%7%-v$6 %
( 	djjeA>?$*S\J
 08IzQAAEE(Oy4PaQRM
 FJ'
 )/:RS||!1'4<<!H	;||#
  4G<I}w< 
 F 	BA"21#Z @A	B 37;I{X'XX
 FK(
 !)J%j1Iy"I.LF!*X	f	!*X04C0ABC 6ngNIz"I.j)FnZ^^Hj-Q,RST
 1'BI{$-;G
 %+6WXL(n&:&:&<< 3N C	:"+
C ;##oaS$9:; $8#@ L.&2O
 %f&G/L .**2!8 ,.).2
 % #F( 
$**V%
:;&L9HHQKu Rs   $P
>P
__main__)%__doc__r   r   osr;   r   r4  r   pathlibr   environrf   r   r   r   r   r   dictrY   r`   tuplerj   r   r   r   r   r   r   r   r   r   r   r   r  r  rW  __name__r)  r   r   <module>r`     so     	 	  
   02GH 3 3t 3LS LT$Z L^  "
d 
c49n0E 
6d4j 63 65d3i;P 6r$t* sDI~1F $4: $%T#Y2G $NS T$Z (,4: ,%S/ ,8c dTk $s td{ 0c 0C 0E#t)DT 0f[S [S [S	 [ sDI~1F Dd c eCcN>S *< <c49n0E <~# %T#Y*? Xv zF r   