
    +j.                        d Z ddlZddlZddlZdZdZdZdZd Zd Z	d	 Z
d
 Zd Zd Zd Zd ZddededefdZedk(  rddlZddlZ eej.                        dk  r1 e ej2                  ddgddd              ej4                  d       ej.                  d   Z eej.                        dkD  rej.                  d   ndZ e ej2                   eee      dd             yy)uo  
file_touch_ratio_check.py - 보고서 수정 파일 vs 실제 git 변경 파일 비율 검증 verifier

보고서에서 "N개 파일 수정"이라 했는데 실제 git diff 에 해당 파일이 없으면 탐지.

CODE_ROOT recognition (task-2729+22, Option A replacement / fresh origin/main base)
-----------------------------------------------------------------------------------
보고서(report)는 canonical ``workspace_root`` 의 ``memory/reports`` 에서 읽되,
실제 git diff(evidence)는 환경변수로 지정된 **CODE_ROOT** 에서 계산한다.
report-root 와 diff/evidence-root 를 분리해, 워크트리에서 변경한 파일이
canonical diff 기준으로 ratio 0.00 false-negative 가 되는 문제를 제거한다.

설계 원칙(요구 verbatim):
1. CODE_ROOT 인식 우선순위: ``PROJECT_PATH`` → ``WORKTREE_PATH`` →
   ``QC_EVIDENCE_ROOT``. 값이 존재하고 유효한 git 작업 디렉토리일 때 채택하며,
   하나도 없으면 ``workspace_root`` 로 fallback(backward-compat).
2. top-level 정규화: 선택된 CODE_ROOT 가 repo **하위 디렉토리**여도
   ``git rev-parse --show-toplevel`` 로 최상위 루트로 정규화한다. git diff 는
   항상 top-level 상대경로를 돌려주므로, 보고서 경로 prefix strip 기준을
   top-level 과 맞춰 path mismatch 를 제거한다.
3. report-root / diff-root 분리: report 는 canonical ``workspace_root`` 에서
   read, diff 는 정규화된 CODE_ROOT 에서 계산.
4. HEAD~5 fail-safe: 가용 커밋이 5 미만 / shallow / ``HEAD~5`` 부재 시 가용
   범위만큼 bounded 비교하고, 루트 단일 커밋이면 git empty-tree 와 비교한다.
   미정의 crash 없이 graceful(SKIP) 처리.
5. 부수효과 없음: 본 verifier 는 report 를 read 만 하고 어떤 파일도 쓰지 않으며,
   외부 모듈(git_evidence/dispatch/callback)을 import/수정하지 않는다.
    N(4b825dc642cb6eb9a060e54bf8d69288fbee4904)PROJECT_PATHWORKTREE_PATHQC_EVIDENCE_ROOT      c                 Z    t        j                  dd|gt        |       z   ddt              S )u>   ``git -C <cwd> <args>`` 실행 헬퍼 (timeout 가드 포함).gitz-CT)capture_outputtexttimeout)
subprocessrunlist_GIT_TIMEOUT)argscwds     D/home/jay/workspace/teams/shared/verifiers/file_touch_ratio_check.py_gitr   0   s/    >>	cT$Z'	     c                     | rt         j                  j                  |       sy	 t        ddg|       j                  dk(  S # t
        j                  t        f$ r Y yw xY w)u   ``path`` 가 유효한 git 작업 디렉토리인지 확인.

    일반 repo(``.git`` 디렉토리)와 worktree 링크(``.git`` 일반 파일) 모두 허용.
    최종 판정은 ``git rev-parse --git-dir`` 성공 여부.
    F	rev-parse	--git-dirr   )ospathisdirr   
returncoder   TimeoutExpiredOSError)r   s    r   _is_git_worktreer    :   sX     rww}}T*[+.5@@AEE%%w/ s   ? AAc                     	 t        ddg|       }|j                  dk(  r4|j                  j                         r|j                  j                         S | S # t        j
                  t        f$ r Y | S w xY w)u`   ``path`` 를 git 최상위 루트로 정규화. 실패 시 입력값 그대로 반환(graceful).r   z--show-toplevelr   )r   r   stdoutstripr   r   r   )r   outs     r   _to_toplevelr%   H   sr    K!23T:>>Q3::#3#3#5::##%% K %%w/ Ks   AA A21A2c                     t         D ]M  }t        j                  j                  |d      j	                         }|s4t        |      s@t        |      |fc S  t        |       dfS )u   env 우선순위로 diff 계산용 CODE_ROOT 를 고른 뒤 top-level 정규화.

    Returns:
        (code_root, source_label)
        source_label: 채택된 env 변수명 또는 ``"workspace_root"``.
     workspace_root)_CODE_ROOT_ENV_PRIORITYr   environgetr#   r    r%   )r(   env_name	candidates      r   _select_code_rootr.   S   s^     , 7JJNN8R0668	))4 +X667
 (*:;;r   c                     	 t        g d|       }|j                  dk(  r't        |j                  j	                         xs d      S 	 y# t
        j                  t        t        f$ r Y yw xY w)uH   HEAD 에서 도달 가능한 커밋 수. 빈 repo/비-repo/오류 시 0.)zrev-listz--countHEADr   0)	r   r   intr"   r#   r   r   r   
ValueError)	code_rootr$   s     r   _reachable_commit_countr5   b   sk    2I>>>Qszz'')0S11   %%w
; s   AA A)(A)c                 x    t        |       }|dk  rd|fS t        t        |dz
        }|dk(  rt        |fS d| |fS )ut  HEAD~5 fail-safe: 가용 커밋 수에 맞춰 bounded base rev 를 결정.

    - 커밋 ≥ 6 : ``HEAD~5`` (기존 동작)
    - 2..5     : ``HEAD~(count-1)`` (가용 범위만큼만)
    - 1        : empty-tree (초기 커밋 전체를 변경분으로)
    - 0        : ``None`` (호출부에서 graceful SKIP)

    Returns:
        (base_rev_or_none, commit_count)
    r   N   zHEAD~)r5   min_MAX_LOOKBACK_EMPTY_TREE_SHA)r4   countlookbacks      r   _pick_base_revr=   m   sS     $I.Eze}=%!),H1}''H:&&r   c                    t        |       d   }|t               ddfS 	 t        dd|g|       }|j                  dk7  r(t               |d|j                  j                          fS |j                  j                         D ch c]#  }|j                         s|j                         % }}||dfS c c}w # t        j                  t        f$ r0}t               |dt        |      j                   d	| fcY d}~S d}~ww xY w)
u   정규화된 CODE_ROOT 에서 변경 파일(top-level 상대경로) 집합 계산.

    base_rev → working tree 비교 (커밋된 변경 + 미커밋 변경 포함).

    Returns:
        (changed_set, base_label, error_or_none)
    r   Nz	no-commitu2   비교 가능한 커밋 없음 (빈 repo/비-repo)diffz--name-onlyu   git diff 실패: u   git diff 예외: : )r=   setr   r   stderrr#   r"   
splitlinesr   r   r   type__name__)r4   base_revr$   lnchangedes         r   _changed_filesrJ      s     i(+H{$XYYNFM84i@>>QE8'89I9I9K8L%MNN(+

(=(=(?N"288:288:NN4(( O%%w/ Nx#4T!W5E5E4Fb!LMMNs<   AB5 %B5 B0B0)B5 0B5 5C>%C93C>9C>c                    t        |D ch c](  }|s|j                  d      s|j                  d      * c}t        d      }|sg S dj                  d |D              }t	        j
                  d|z   dz   t        j                        }t               }g }|j                  |       D ]<  }|j                         }|s||vs|j                  |       |j                  |       > |S c c}w )u  보고서에서 수정 파일 경로를 top-level 상대경로로 추출.

    지원 패턴:
    - 테이블 행: ``| /abs/workspace/... |``
    - 목록 항목: ``- /abs/workspace/...``

    ``base_roots`` 의 각 prefix(report-root, 정규화된 CODE_ROOT 등)를 strip
    후보로 쓴다. 가장 긴 prefix 를 먼저 매칭해, 보고서가 canonical 경로를 쓰든
    워크트리 경로를 쓰든 동일한 top-level 상대경로로 정규화한다.
    /T)keyreverse|c              3   F   K   | ]  }t        j                  |        y w)N)reescape).0bs     r   	<genexpr>z*_extract_reported_files.<locals>.<genexpr>   s     7A299Q<7s   !z(?:^\|[ \t]*|^-[ \t]+)(?:z)/([^\s|]+))sortedrstriplenjoinrQ   compile	MULTILINErA   findallr#   addappend)	report_content
base_rootsrbasesalternationpatternseenorderedms	            r   _extract_reported_filesrh      s      *B1aAHHSM#BE
 	((777Kjj${2^C
G 5DG__^, GGI$HHQKNN1	
 N% 	Cs   C)C)C)/home/jay/workspacetask_idr(   returnc           
         t         j                  j                  |dd|  d      }	 t        |dd      5 }|j	                         }ddd       t        |      \  }}t        ||g      }|sddgd
S 	 t        ddg|      }	|	j                  dk7  r	dd| gd
S 	 t        |      \  }
}}|dd| d| d| dd| gd
S t!        |      }||
z  }|rt#        |      t#        |      z  nd}d| d| dd| d| dt#        |       d| dt#        |
       dt#        |       d |d!g}|dk(  r3t%        ||
z
        dd" D ]  }|j'                  d#|         d$|d%gz   d
S |d&k  r3t%        ||
z
        dd" D ]  }|j'                  d#|         d'|d(gz   d
S d)|d
S # 1 sw Y   ]xY w# t
        $ r dd	| gd
cY S t        $ r)}ddt        |      j                   d| gd
cY d}~S d}~ww xY w# t
        $ r	 ddgd
cY S t        j                  t        f$ r)}ddt        |      j                   d| gd
cY d}~S d}~ww xY w)*u  보고서의 수정 파일 목록과 실제 git diff 를 대조해 File-Touch Ratio 계산.

    report 는 canonical ``workspace_root`` 에서 읽고, diff 는 env 로 결정된
    (top-level 정규화된) CODE_ROOT 에서 계산한다.

    Args:
        task_id: 검사 대상 task ID
        workspace_root: canonical report 루트 (기본값 유지 — backward-compat)

    Returns:
        {"status": "PASS"|"FAIL"|"WARN"|"SKIP", "details": [...]}
    memoryreportsz.mdra   zutf-8)encodingNSKIPu   보고서 파일 없음: statusdetailsu    보고서 파일 읽기 실패: r@   u(   보고서에 수정 파일 섹션 없음r   r   r   u   git repo 아님: u"   git 명령어를 찾을 수 없음u   git 확인 실패: u$   git diff 계산 불가 (fail-safe): zCODE_ROOT: z	 (source=)zbase: g        zreport-root: zdiff base: u   보고서 파일 수: u#   실제 변경 파일 수 (git diff z): u   교집합: zFile-Touch Ratio: z.2f
   u   미변경: FAILu+   보고서에 명시된 파일 변경 없음g      ?WARNu7   보고서 파일의 50% 이상이 실제 변경 안 됨PASS)r   r   rY   openreadFileNotFoundErrorr   rD   rE   r.   rh   r   r   r   r   rJ   rA   rX   rV   r^   )rj   r(   report_pathfr_   rI   r4   code_root_sourcereported_filescheckgit_changed_files
base_label
diff_errorreported_setintersectionratiors   s                    r   verifyr      s5    '',,~xwisOTK	
+sW5 	&VVXN	& #4N"CI -N3N  .X-YZZ

k;/;q $4Ei[2Q1RSS ! 1?y0I-z:6zlCi[	2B1C1E%
 	
 ~&L"33L5ACL 11sE i[	*:);1=
'(
j\"
 \!2 34
-j\SAR=S<TU
c,'()
U3K(G z'889#2> 	.ANN[,-	. W8e7f-fggs{'889#2> 	.ANN[,-	. "[!\\
 	
 11O	& 	& Z 0I+.W-XYY 
:47;K;K:LBqcRS
 	

*  U .R-STT%%w/ 
-d1g.>.>-?r!EF
 	

s^   F. F!F. 3%G6 !F+&F. .G3G3
G.(G3.G36III=II__main__   rp   z;Usage: file_touch_ratio_check.py <task_id> [workspace_root]rq   F)ensure_asciiindentr7   )ri   )__doc__r   rQ   r   r:   r)   r9   r   r   r    r%   r.   r5   r=   rJ   rh   strdictr   rE   jsonsysrX   argvprintdumpsexit_task_id_workspace_root r   r   <module>r      s'  : 
 	  = P   <'(N*!HW2C W2 W2 W2t z
388}qDJJ$ ]^ #		
 	xx{H%(]Q%6chhqk<QO	*$**VHo6UST
UV' r   