
    ii                       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	Z	ddl
Z
ddlmZmZ ddlmZ ddlmZ dZ ej$                  dej&                  ej(                  z        Z ej$                  d	ej&                        Z ej$                  d
      ZdZd#dZd#dZd$dZ	 d%	 	 	 	 	 	 	 d&dZd'dZd(dZ	 	 	 	 	 	 	 	 d)dZ	 	 	 	 	 	 	 	 d)dZ 	 	 	 	 	 	 	 	 d*dZ!d+dZ"	 	 	 	 	 	 d,dZ#d-dZ$d.dZ%	 	 	 	 	 	 	 	 d/dZ&	 	 	 	 	 	 d(dZ'	 	 	 	 	 	 	 	 d*dZ(	 	 	 	 	 	 d(dZ)d0dZ*d1dZ+	 	 	 	 	 	 	 	 	 	 d2dZ,d3d Z-d4d5d!Z.e/d"k(  r e	j`                   e.              yy)6u8  taskctl_verify.py — taskctl 상태 전이 직전·직후의 11개 정합성 검사기.

task-2459 Phase 2-C 구현. spec: memory/specs/taskctl-verify-spec.md (v1.0).

핵심 정책:
  - read-only 진단기. git history mutation 금지 (rebase/cherry-pick/reset 0줄).
  - LLM API 호출 0줄. 외부 네트워크 호출 0줄.
  - FAIL 시 evidence 만 저장하고 exit ≠ 0 으로 보고. 자동 복구 절대 금지.

Exit code:
  0 = PASS  (모든 검사 PASS or N/A)
  1 = FAIL  (1건 이상 FAIL)
  2 = WARN  (FAIL 0건, WARN 1건 이상)
  3 = internal error
    )annotationsN)datetimetimezone)Path)Anyz1.0z-^\s*qc_verdict\s*[:\-]\s*(PASS|WARN|FAIL)\s*$z)##\s*QC\s+Verdict[^\n]*\n+([\s\S]{0,400})z\b(PASS|WARN|FAIL)\b)
handoff_idfrom_botto_bottask_id	timestampc                 f    t        j                  t        j                        j	                  d      S )N%Y-%m-%dT%H:%M:%SZr   nowr   utcstrftime     G/home/jay/workspace/.worktrees/task-2459-dev5/scripts/taskctl_verify.py_now_isor   9   s!    <<%../CDDr   c                 f    t        j                  t        j                        j	                  d      S )Nz%Y%m%dT%H%M%SZr   r   r   r   _now_compactr   =   s!    <<%../?@@r   c                   | j                   j                  dd       t        j                  t	        | j                         | j
                  dz   d      \  }}	 t        j                  |dd      5 }t        j                  ||d	d
       |j                  d       d d d        t        j                  |t	        |              y # 1 sw Y   )xY w# t        $ r' 	 t        j                  |        # t        $ r Y  w xY ww xY w)NT)parentsexist_ok.z.tmp)dirprefixsuffixwutf-8encodingF   )ensure_asciiindent
)parentmkdirtempfilemkstempstrnameosfdopenjsondumpwritereplace	ExceptionunlinkOSError)pathdatafdtmp_pathfs        r   _atomic_write_jsonr<   A   s    KKdT2##TYY_VLB
YYr31 	QIIdAE!<GGDM	 	

8SY'	 	  	IIh 	  		sH   C 2+C'C C
C 	DC10D1	C=:D<C==Dc                P    t        j                  dg| z   t        |      dd|      S )u4   git read-only 명령. mutation 명령 호출 금지.gitTcwdcapture_outputtexttimeout)
subprocessrunr,   )argsr@   rC   s      r   _git_runrG   S   s.     >>	$H r   c                    	 | j                         j                  }t        j                  |t
        j                        j                  d      S # t        $ r Y y w xY w)N)tzr   )statst_mtimer6   r   fromtimestampr   r   r   )r7   tss     r   _file_mtime_isorN   `   sU    YY[!! !!"6??   s   A 	AAc                    |dz  dz  |  dz  }t        |      |j                         rt        |      nd d}|j                         rd|d fS d|d| dfS )	N.taskslocksz.lock)	lock_path
lock_mtimePASSFAILzstart_lock: u+    부재 (start_task_guard 미실행 의심))r,   existsrN   )r   	workspacerR   details       r   check_start_lockrY   n   sx    H$w.G9E1BBI^4=4D4D4Foi0DF vt##
yk!LM r   c                   t        g d|      }|j                  dk7  r"dd d ||j                  j                         ddfS |j                  j                         }|dk(  rdd|rd|  d	| nd|  d
|ddfS |r#d|  d	| }|||d}||k(  rd|d fS d|d| d| fS d|  d	}|| dd d}|j                  |      rd|d fS d|d| d| dfS )N)z	rev-parsez--abbrev-refHEADr@   r   rU   )current_branchexpected_branchbot_hint	git_erroru"   branch_match: git rev-parse 실패r[   ztask/-z-*)r]   r^   r_   u"   branch_match: detached HEAD 상태rT   u   branch_match: 기대 u	   , 실제 z<bot>zbranch_match: u    가 u    prefix 와 불일치)rG   
returncodestderrstripstdout
startswith)r   botr@   rescurrentexpectedrX   r   s           r   check_branch_matchrk   }   sb    8c
BC
~~"&#' ZZ--/	 1	
 		
 jj G&"(/2eG9AcU+%y8K 1

 
	
 7)1SE*%'

 h64''#H:Ywi@
 	
 WIQF!$XU+F
 &!vt##6^G9E&AVWWWr   c                    |j                         }|r	d|  d| }nd|  d}t        |      |d}t        |      }|r |j                  |      s
| d|dz   v rd|d fS ||v rd|d fS d|d| dfS )	Nz.worktrees/ra   )r@   cwd_match_pattern/rT   rU   u   worktree_path: cwd 가 u    worktree 안이 아님)resolver,   endswith)r   rg   r@   cwd_resolvedmarkerrX   cwd_strs          r   check_worktree_pathrt      s     ;;=L
wiq.wiq)< #F ,G
F#&|w}'D64'' W64''
!&)@A r   c                T   |dz  dz  |  dz  |dz  dz  |  dz  g}t               }d }|D ]N  }|j                         r|j                         n|}||v r*|j                  |       |j                         sL|} n |rt	        |      nd |rt        |      nd d}|d|d fS d|d| fS )Nmemoryeventsz
.cancelled)cancelled_marker_pathcancelled_atrT   rU   u*   cancelled_check: cancel 마커 존재 — )setrV   ro   addr,   rN   )	r   rW   r@   
candidatesseenfoundprprX   s	            r   check_cancelledr      s     	Hx'WIZ*@@h!wiz$::J eDE HHJQYY[A:88:E 05U$27.TF }vt##
4UG< r   c                   t        ddg|       }|j                  dk7  r dg |j                  j                         ddfS |j                  j                         D cg c]  }|j                         s| }}g }|d d D ]&  }|j                  t        |      d	kD  r|d	d  n|       ( d
|i}|sd|d fS d|dt        |       dfS c c}w )Nstatusz--porcelainr\   r   rU   )dirty_filesr`   u$   dirty_tree: git status 실행 실패2      r   rT   zdirty_tree: z' untracked or modified file(s) detected)rG   rb   rc   rd   re   
splitlinesappendlen)r@   rh   lnlinesfilesrX   s         r   check_dirty_treer      s    
Hm,#
6C
~~SZZ-=-=-?@2
 	

 **//1@BRXXZR@E@ECRj 4s2w{RV34 U#Fvt##
s5zl"IJ  As   C1Cc                >   t        dd|  dg|      }|j                  dk7  r dg |j                  j                         dd fS t	        |j
                  j                         D ch c]#  }|j                         s|j                         % c}      }dd|id fS c c}w )	Ndiffz--name-onlyz..HEADr\   r   rT   )changed_paths_listr`   r   )rG   rb   rc   rd   sortedre   r   )baser@   rh   r   pathss        r   check_changed_pathsr     s     FMdV6?;
EC
~~ #%CJJ4D4D4FG
 	

 )>)>)@O2BHHJBHHJOPE(%0$66 Ps   $B:Bc                   d}d}d}d}g }d}| j                         D ]d  }|j                  d      }|j                         }	|	j                  d      r| }|sd}d}d}B|sEt	        |      t	        |j                  d            z
  }
|s|	j                  d      rd}d}|
}|	dk(  s|	j                  d	      r||
|k  r|	j                  d
      rd}d}|	j                  d      rd}|s|	j                  d      ro|	dd j                         }|j                  d      r|j                  d      s"|j                  d      r|j                  d      r|dd }|j                  |       Pd}|	j                  d
      seg |sy|S )u]  task md 에서 `allowed_resources.paths` 리스트 추출 (yaml lib 미사용).

    Returns:
        - ``None`` : ``allowed_resources`` 블록 자체가 없음 (구버전 task md 호환)
        - ``[]``   : 블록은 있으나 ``paths`` 리스트가 비어있음 (스펙 위반 — FAIL 처리)
        - ``[...]``: 추출된 패턴 리스트
    FNr'   z``` zallowed_resources:T #:zpaths:z- r$   "'   )r   rstriprd   rf   r   lstriprp   r   )task_mdin_yaml
in_allowedin_pathsseen_allowedr   yaml_indentrawlinestrippedleadingvals               r   _extract_allowed_pathsr     s    GJHLE"K!!# 8zz$::<u%!kG"
 " d)c$++c"233""#78!
#% r>X005 "w+'=(BSBSTWBXJHx(H""4(qrl((*NN3'CLL,=NN3'CLL,=a)CS! !$$S)q8r Lr   c                   || k(  ryt        j                  | |      ryd|v rg }d}|t        |      k  r|||dz    dk(  r7|j                  d       |dz  }|t        |      k  r||   dk(  r|dz  }d|d	<   n||   d
k(  r|j                  d       |dz  }nl||   dk(  r|j                  d       |dz  }nM||   dv r-|j                  t	        j
                  ||                |dz  }n|j                  ||          |dz  }|t        |      k  rddj                  |      z   dz   }	 t	        j                  ||       duS y# t        j                  $ r Y yw xY w)u   `**` 지원하는 glob 매칭.Tz**r   r$   z.*rn   r   z(?:.*/)?r   *z[^/]*?z[^/]z
.+()^$|{}\^r   $NF)	fnmatchfnmatchcaser   r   reescapejoinmatcherror)r7   patternregex_partsiregexs        r   _glob_matchr   c  s    $4)w "$#g,q1q5!T)""4(Qs7|#
c(9FA '1KOs"""7+Qs"""6*Q},""299WQZ#89Q""71:.Q+ #g,, bggk**S0	88E4(44  xx 		s   )E EEc                  	 |dz  dz  |  dz  }g g t        |      d}|j                         sd|d| fS 	 |j                  d      }t	        |      }|d|dfS ||d<   |sd|dfS g }|D ](  	t        	fd|D              r|j                  	       * ||d<   |sd|d fS d|dt        |       dfS # t        $ r!}di |d	t        |      id
| fcY d }~S d }~ww xY w)Nrv   tasks.md)allowed_patternsscope_violationstask_md_pathrU   u!   scope_matrix: task md 부재 — r!   r"   
read_erroru(   scope_matrix: task md 읽기 실패 — WARNuA   scope_matrix: allowed_resources 블록 부재 (구버전 task md)r   u3   scope_matrix: allowed_resources.paths 빈 리스트c              3  6   K   | ]  }t        |        y wN)r   ).0patr   s     r   	<genexpr>z%check_scope_matrix.<locals>.<genexpr>  s     ;3;q#&;s   r   rT   zscope_matrix: z" path(s) outside allowed_resources)r,   rV   	read_textr6   r   anyr   r   )
r   rW   changed_pathsr   rX   rB   excpatterns
violationsr   s
            @r   check_scope_matrixr     sm    ("W,'#>GGF
 >> /y9
 	

  ' 2 &d+H O
 	
 "*F A
 	
 J !;(;;a ! ",Fvt##
Z))KL E  
.v.|SX.6se<
 	

s   B5 5	C>CCCc                v   |dz  dz  |  dz  }|j                         rt        |      nd d g d}|j                         sd|d fS 	 t        j                  |j	                  d            }t        D cg c]  }|j                  |      r| }}|r ddj                  |       g|d	<   d
|d| fS |d   | k7  rd|d    d|  g|d	<   d
|d|d    d|  fS |j                  d      }|sdg|d	<   d
|dfS ||d<   d|d fS # t
        t        j                  f$ r}d| g|d	<   d
|d| fcY d }~S d }~ww xY wc c}w )Nrv   handoffs.json)handoff_pathprevious_bothandoff_schema_errorsN/Ar!   r"   zparse: r   rU   u&   handoff_chain: JSON 파싱 실패 — z	missing: ,u(   handoff_chain: 필수 필드 누락 — r   ztask_id mismatch: handoff=z arg=u,   handoff_chain: task_id mismatch — handoff=r	   zfrom_bot emptyu3   handoff_chain: from_bot(previous_bot) 추적 불가r   rT   )
rV   r,   r0   loadsr   r6   JSONDecodeErrorHANDOFF_REQUIRED_FIELDSgetr   )	r   rW   r   rX   r8   r   r;   missingr   s	            r   check_handoff_chainr     s    x'*4'%7HHL-9-@-@-BL)!#F
  fd""
zz,00'0BC 2EQ!qEGE-6sxx7H6I+J*K&'6wi@
 	
 I'!(i(9wiH+
&' 	?+5	;	
 	
 88J'L+;*<&'A
 	
 *F>64W T))* 
-4SE?*;&'4SE:
 	

 Fs*   %C? 3D6
D6?D3D.(D3.D3c                P   |dz  dz  }|j                         s	dd d dddfS t        j                  t        |      | ddd	t        |      d
t        |      g	}	 t	        j
                  |t        |      ddd      }|j                  d |j                  r|j                  dd  ndd}|j                  dk(  rd|d fS |j                  dk(  rd|dfS d|d|j                   dfS # t        j                  $ r dd d dddfcY S t        $ r}dd d t        |      dd| fcY d }~S d }~ww xY w)Nscriptszmixed_commit_detector.pyrU   u   mixed_commit_detector.py 부재)mixed_commit_detector_exitmixed_commit_evidence_pathnoteuF   mixed_commit: mixed_commit_detector.py 부재 — 안전 검증 불가--json--quiet--workspacez	--git-dirT<   r?   rC   )r   r   r   u2   mixed_commit: detector timeout — 안전 측 FAILu)   mixed_commit: detector 실행 실패 — ir   )r   r   stdout_tailr   rT   r   u.   mixed_commit: detector 가 alien commit 감지z,mixed_commit: detector internal_error (exit u   ) — 안전 측 FAIL)
rV   sys
executabler,   rD   rE   TimeoutExpiredr6   rb   re   )r   rW   r@   detectorcmdrh   r   rX   s           r   check_mixed_commitr     s    9$'AAH?? .2.29
 U
 	
 	HICC
nnC
< '*nn&*,/JJszz$%(BF ~~vt##
~~vOOO 	:3>>:J K 	 C $$ 

 .2.2"
 A
 	
  	
.2.2S
 8u=
 	
	
s$   #C   D%>D%D D% D%c                   |dz  dz  |  dz  }|j                         rt        |      nd d d}|j                         sd|d fS 	 |j                  d      }dj	                  |j                         d d       }d }t        j                  |      }|r|j                  d      j                         }|\t        j                  |      }	|	rEt        j                  |	j                  d            }
|
r|
j                  d      j                         }||d<   |d|dfS |dv rd|d fS |dk(  rd|dfS d|d| fS # t        $ r!}di |d	t        |      id
| fcY d }~S d }~ww xY w)Nrv   reportsr   )qc_report_pathqc_verdict_valuer   r!   r"   r   r   u#   qc_report_guard: 읽기 실패 — r'   r   r   r   uM   qc_report_guard: 보고서는 존재하지만 qc_verdict 라인 파싱 실패)rT   r   rT   rU   z qc_report_guard: qc_verdict=FAILu(   qc_report_guard: 알 수 없는 verdict )rV   r,   r   r6   r   r   QC_VERDICT_REsearchgroupupperQC_SECTION_REQC_KEYWORD_RE)r   rW   report_pathrX   rB   r   headverdictmseckws              r   check_qc_report_guardr   W  s    h&2y_DK.9.@.@.B#k* F fd""
$$g$6 99T__&s+,DGT"A''!*""$""4(%%ciil3B((1+++-!(F[
 	

 ""vt##&vAAA
27)< E  
.v.|SX.1#7
 	

s   D6 6	E ?EE E c                d   | dz  dz  }d ddd}|j                         sd|d fS 	 t        j                  dt        |      gt        |      ddd	      }|j                  |d<   dj                  |j                  j                         dd        |d<   dj                  |j                  j                         dd        |d<   |j                  dk(  rd|d fS d
|d|j                   fS # t        j                  $ r d
i |ddidfcY S t
        $ r!}d
i |dt        |      id| fcY d }~S d }~ww xY w)Nr   zguard.shr   )guard_sh_exitguard_sh_stdout_tailguard_sh_stderr_tailr   bashTr   r?   rU   r   rC   u   guard_sh: timeout 60s 초과u   guard_sh: 실행 실패 — r  r'   ir  r  r   rT   zguard_sh: exit )rV   rD   rE   r,   r   r6   rb   r   re   r   rc   )rW   r@   guardrX   rh   r   s         r   check_guard_shr    su   	!J.E " "F
 <<>fd""
nnSZ C
( "nnF?%)YY

%&F!" &*YY

%&F!" ~~vt##
#..)* - $$ 
*v*w	**
 	

  
)v)wC)*3%0
 	

s#   .C& &D/D/D*$D/*D/c                    t        d | j                         D              ryt        d | j                         D              ryy)Nc              3  &   K   | ]	  }|d k(    yw)rU   Nr   r   vs     r   r   z_overall.<locals>.<genexpr>       
111;
1   )rU   r   c              3  &   K   | ]	  }|d k(    yw)r   Nr   r	  s     r   r   z_overall.<locals>.<genexpr>  r  r  )r   r$   )rT   r   )r   values)resultss    r   _overallr    s5    

1 0
11

1 0
11r   c                >   i i g dfd}t        | |      \  }}} |d|||       t        | ||      \  }}} |d|||       t        | ||      \  }}} |d|||       t        | ||      \  }}} |d|||       t	        |      \  }}} |d|||       t        d|      \  }}} |d|||       j                  d	g       }t        | ||      \  }}} |d
|||       t        | |      \  }}} |d|||       t        | ||      \  }}} |d|||       t        | |      \  }}} |d|||       t        ||      \  }}} |d|||       t              \  }	}
d<   | t               t        t        |j!                               |t#              |	|
d
}|S )Nc                ~    || <   |j                         D ]
  \  }}||<    |r|dk(  rj                  |       y y y )NrU   )itemsr   )	r-   r   rX   reasonkr
  detailsfail_reasonsr  s	         r   _recordzrun_all_checks.<locals>._record  sO    LLN 	DAqGAJ	 f&' '6r   
start_lockbranch_matchworktree_pathcancelled_check
dirty_treezorigin/mainr   r   scope_matrixhandoff_chainmixed_commitqc_report_guardguard_shr  )
r   verified_atverifier_versionr@   r_   r  r  r  overall	exit_code)
r-   r,   r   r,   rX   dictr  
str | NonereturnNone)rY   rk   rt   r   r   r   r   r   r   r   r   r  r  r   VERIFIER_VERSIONr,   ro   list)r   rg   rW   r@   r  sdrchangedr%  r&  payloadr  r  r  s               @@@r   run_all_checksr2    s    !G G L( w	2GAq!L!Q" #s3GAq!NAq!$!'34GAq!OQ1%gy#6GAq!q!Q's#GAq!L!Q"!-5GAq!OQ1%kk.3G )W=GAq!NAq!$!'95GAq!OQ1% )S9GAq!NAq!$#GY7GAq!q!Q'Y,GAq!J1a !'*GY*GN z,3;;=!\*G Nr   c                V    t               }|dz  dz  | z  d| dz  }t        ||       |S )NrP   evidencezverify-r   )r   r<   )r   rW   r1  rM   r7   s        r   write_evidence_filer5    s:    	Bx*,w672$e9LLDtW%Kr   c           	        t        j                  d      }|j                  dd       |j                  dd d       |j                  d	t        j                  j                  d
      xs t        j                         d       |j                  dddd       |j                  ddd       |j                  |       }t        |j                        j                         }t        t        j                               j                         }|j                         st        d| t        j                         y	 t        |j                   |j"                  ||      }|j*                  r:|j,                  s t        t/        j0                  |d             t3        |d         S 	 t5        |j                   ||      }|j,                  sL|d   |d   |d   |d   t9        |j;                  |            d}t        t/        j0                  |d             t3        |d         S # t$        j&                  $ r(}t        d| t        j                         Y d }~yd }~wt(        $ r(}t        d| t        j                         Y d }~yd }~ww xY w# t6        $ rT}t        d| t        j                         |j,                  s t        t/        j0                  |d             Y d }~yd }~ww xY w) Nz7Run 11 task verification checks and emit evidence JSON.)descriptionr   u&   검증 대상 task id (예: task-2459))helpz--botu-   bot 이름 (branch/worktree 정확도 향상))defaultr8  r   WORKSPACE_ROOTu7   워크스페이스 루트 (기본 cwd / WORKSPACE_ROOT)r   
store_true	json_onlyu?   dry-run: stdout 에 전체 evidence JSON, 파일 저장 안 함)actiondestr8  r   u   정상 stdout 메시지 억제)r=  r8  z&[taskctl-verify] workspace not found: )filer   z%[taskctl-verify] subprocess timeout: z![taskctl-verify] internal error: F)r%   r&  z+[taskctl-verify] failed to write evidence: r%  r  )r   r%  r&  r  r4  )argparseArgumentParseradd_argumentr.   environr   getcwd
parse_argsr   rW   ro   rV   printr   rc   r2  r   rg   rD   r   r4   r<  quietr0   dumpsintr5  r6   r,   relative_to)	argvparserrF   rW   r@   r1  r   evidence_pathsummarys	            r   mainrO    s   $$MF 	(PQ
4cd


/0?BIIKF  
 N	   ,-M   T"DT^^$,,.I
ryy{

#
#
%C4YK@	
  txxCH ~~zz$**W59:7;'((
+DLL)WM ::y)y) -y)M55i@A
 	djju56w{#$$G $$ 5cU;#**M 1#7cjjI  9#?	

 zz$**W59:s=   ="H# &J #J6IJ%JJ	K-A
K((K-__main__)r)  r,   )r7   r   r8   r'  r)  r*  )   )rF   	list[str]r@   r   rC   rI  r)  zsubprocess.CompletedProcess)r7   r   r)  r(  )r   r,   rW   r   r)  tuple[str, dict, str | None])r   r,   rg   r(  r@   r   r)  rS  )r   r,   rW   r   r@   r   r)  rS  )r@   r   r)  rS  )r   r,   r@   r   r)  rS  )r   r,   r)  list[str] | None)r7   r,   r   r,   r)  bool)r   r,   rW   r   r   rR  r)  rS  )rW   r   r@   r   r)  rS  )r  zdict[str, str]r)  ztuple[str, int])
r   r,   rg   r(  rW   r   r@   r   r)  r'  )r   r,   rW   r   r1  r'  r)  r   r   )rK  rT  r)  rI  )1__doc__
__future__r   r@  r   r0   r.   r   rD   r   r*   r   r   pathlibr   typingr   r+  compile
IGNORECASE	MULTILINEr   r   r   r   r   r   r<   rG   rN   rY   rk   rt   r   r   r   r   r   r   r   r   r   r  r  r2  r5  rO  __name__exitr   r   r   <module>r_     sr   #    	 	  
  '    

4MMBLL 
 

0MM 

23 EA& 02



),
 
4X4X!4X(,4X!4Xn!(,!8!(,!<07
77!7K\*Z99!92;9!9x9 9 !9 !9 xLL!L(,L!L^33!3!3l+dBB!B.2B9=B	BJE%P zCHHTV r   