
    i?              
       d   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Zej                  j                  dej                  j                  ej                  j                  e                   ddlZddlZdedej"                  fdZdededefdZded	edeedz  ef   fd
ZdededefdZdededz  fdZdee   dedeeef   fdZdedej"                  dedeeef   fdZdededeeef   fdZdedej"                  dedeeef   fdZded	ededeeef   fdZ ddZ!e"dk(  r e!        yy)u  
pre_push_guard.py — push 직전 4가지 안전 점검 (Guard MVP Phase 1)

4가지 검사:
  B-1 working tree clean 검사      (working_tree_modified ∪ untracked 기준)
  B-2 main...origin/main ahead/behind 검사
  B-3 task scope 일치 검사         (head_diff 기준)
  B-4 qc-result JSON vs 보고서 일치 검사

모두 PASS → exit 0 / 한 건이라도 FAIL → exit 1
    Ntask_idreturnc                     t        j                  |       }t        j                  d|z   dz   |z   dz   |z   dz   |z   dz         S )Nas  ^(memory/heartbeats/|memory/daily/|memory/logs/|memory/reports/|memory/capabilities/|memory/sessions/|memory/meetings/|memory/backups/|memory/screenshots/|memory/events/|logs/|whisper/|memory/whisper/|output/)|^bot-activity\.json$|^token-ledger\.json$|^memory/token-ledger\.json$|^memory/task-timers\.json|^memory/pipeline-status\.json$|^memory/preview-state\.json$|^memory/merge-log\.json$|^memory/bot_settings_sync\.json$|^memory/memory-check-log\.json$|^memory/canary-status\.json$|^\.heartbeat$|^memory/\.task-counter$|^config/constants\.json$|^scripts/gemini_rate_tracker\.json$|^tests/coverage-report\.txt$|^memory/tasks/z\.md$|^memory/tasks/z/|^memory/reports/zR(?:-[\w-]+)?\.md$|^memory/tasks/(?:task-|dispatch-).*\.md$|^memory/plans/tasks/(?!zr(?:/|$))[^/]+/.*$|(?:^|/)conftest\.py$|^teams/|^tests/test_(?!pre_push_guard|task_scope|qc_report_guard)[^/]*\.py$)reescapecompile)r   safe_ids     G/home/jay/workspace/.worktrees/task-2464-dev6/scripts/pre_push_guard.py_build_system_ignorer       sx    ii G::	 %	%(	 %	%(	   '!	' *$!	$& '.'	.&1P'	P     patternpathc                    | j                  d      r | dd }||k(  xs |j                  |dz         S | dk(  ryd| v rt        j                  || j                  dd            ryt        j
                  j                  |      }t        j                  dd	|       j                  dd	      }t        j                  ||      ryy
t        j                  ||       S )u-   fnmatch 기반 glob 매칭. ** 패턴 지원.z/**N/z**T*z\*\*/ F)	endswith
startswithfnmatchreplaceosr   basenamer   sub)r   r   prefixfilename
pat_simples        r
   
glob_matchr   B   s    "v~>#!>>$w??4s!;<77##D)VVHb'2::5"E
??8Z0??4))r   	workspacec                    t         j                  j                  |dd|  d      }t         j                  j                  |      rN	 t	        |d      5 }t        j                  |      }ddd       j                  di       }t        ||       }|dfS t         j                  j                  |dd
|  d      }t         j                  j                  |      sdd| fS 	 t	        |d      5 }|j                         }ddd       t              }|yt        ||       }|dfS # 1 sw Y   xY w# t        $ r}dd	| fcY d}~S d}~ww xY w# 1 sw Y   PxY w# t        $ r}dd| fcY d}~S d}~ww xY w)u   
    allowed_resources 해소.
    1) <workspace>/memory/capabilities/<task_id>.json 우선
    2) 없으면 <workspace>/memory/tasks/<task_id>.md ## allowed_resources YAML fallback
    반환: (allowed_resources_dict | None, 에러메시지|"")
    memorycapabilitiesz.jsonzutf-8)encodingNallowed_resourcesr   u#   capability snapshot 파싱 실패: tasksz.mdu3   capability snapshot 없음, task 파일도 없음: )Nu1   task 파일에 ## allowed_resources 블록 없음u   task 파일 파싱 실패: )r   r   joinexistsopenjsonloadget_substitute_placeholder	Exceptionread_parse_allowed_resources_yaml)	r   r   cap_pathfsnapare	task_path	task_texts	            r
   _resolve_allowed_resourcesr7   V   sr    ww||IxG9EARSH	ww~~h	Ch1 $Qyy|$-r2B(W5Br6M
 Y'gYc?KI77>>)$J9+VVV	7)g. 	!!I	!*95:L$R12v+$ $  	C>qcBBB	C	! 	!  721#6667sr   D/ D#))D/ E ,E=E E #D,(D/ /	E	8E>E	E	EE 	E2!E-'E2-E2r3   c                     dt         t           dt         t           ffd}t        |       }d|v r ||d         |d<   d|v r ||d         |d<   |S )uN   paths/forbidden_paths의 'task-XXXX' placeholder → 실제 task_id로 치환.itemsr   c                 N    | D cg c]  }|j                  d       c}S c c}w )Nz	task-XXXX)r   )r9   itemr   s     r
   _subz%_substitute_placeholder.<locals>._sub|   s"    ?DEt['2EEEs   "pathsforbidden_paths)liststrdict)r3   r   r<   results    `  r
   r,   r,   z   si    FDI F$s) F "XF&vg/wF"$(0A)B$C !Mr   textc                 $   t        j                  d| t         j                  t         j                  z        }|sy|j	                  d      }i }d}|j                         D ]  }|j                         }|j                  d      rd}g ||<   ,|j                  d      rd}g ||<   E|j                  d      r9|d	v r5||   j                  |d
d j                         j                  d             |s|j                  d      rd|v sd} |r|S dS )uQ   ## allowed_resources 섹션에서 YAML 블록 파싱 (표준 라이브러리만).z2^##\s+allowed_resources\s*\n```(?:yaml)?\n(.*?)```N   zpaths:r=   zforbidden_paths:r>   z- )r=   r>      "-:)	r   search	MULTILINEDOTALLgroup
splitlinesstripr   append)rC   m	yaml_textrB   current_keylinestrippeds          r
   r/   r/      s    			=bllRYY&	A 
I F"K$$& ::<x(!K"$F;  !34+K"$F;  &;:V+V;&&x|'9'9';'A'A#'FGh11#63(?K 6%%r   argscwdc                     dd|g| z   }t        j                  |ddd      }|j                  |j                  j	                         fS )u1   git 실행 → (returncode, stdout). shell=False.gitz-CTF)capture_outputrC   check)
subprocessrun
returncodestdoutrO   )rV   rW   cmdrs       r
   _run_gitrb      sA    $
t
#Cs4d%HA<<)))r   	diff_setsignore_patternr$   c                    dt         dt         fd}t        | j                  dg             t        | j                  dg             z  D ch c]
  } ||       }}|D cg c]  }|j                  |      r| }}|sy|j                  dg       }|j                  dg       }g }	|D ]Q  t	        fd	|D              r|	j                   d
       ,t	        fd|D              rA|	j                         S |	rHdj                  |	dd       }
t        |	      dkD  r|
dt        |	       dz  }
ddt        |	       d|
 dfS ddt        |       dfS c c}w c c}w )u   
    working_tree_modified ∪ untracked 집합에서
    system-ignore 적용 후 task scope 밖 파일이 있으면 FAIL.
    pr   c                 l    | j                  d      r"| j                  d      r| j                  d      S | S )NrG   )r   r   rO   )rf   s    r
   _strip_quotesz,check_b1_working_tree.<locals>._strip_quotes   s*     ||C0QZZ_qwws|K!Kr   working_tree_modified	untracked)Tu   변경 없음 (clean)r=   r>   c              3   6   K   | ]  }t        |        y wNr   .0fpr   s     r
   	<genexpr>z(check_b1_working_tree.<locals>.<genexpr>        >z"d#>   z (forbidden)c              3   6   K   | ]  }t        |        y wrl   rm   ro   apr   s     r
   rq   z(check_b1_working_tree.<locals>.<genexpr>        @B:b$'@rs   , N    ... (   건)Fu   task scope 밖 변경    건: u/    — git stash push -u 로 격리 후 재시도Tu   system-ignore 후    건 모두 scope 내)r@   setr+   rJ   anyrP   r&   len)rc   rd   r$   rh   rf   dirtyafter_ignoreallowed_pathsr>   out_of_scopedetailr   s              @r
   check_b1_working_treer      s   L L L 	5r:;)--R012 	aE   %E!N,A,A!,DAELE,  144WbAM!2!6!67H"!MO L &>o>>4& 56@-@@%& <+,|q s<0166F$S%6$7uVH=>
 	

 %c,&7%88LMMMK Fs   E E7Ebase_refc                    t        ddd|  dg|      \  }}|dk7  rdd| dfS |j                         }t        |      d	k7  rdd
|dfS t        |d         t        |d         }}|dkD  rdd| d| dfS |dk(  r|dk(  rydd| d| dfS )uU   
    git rev-list --left-right --count <base_ref>...HEAD
    behind > 0 → FAIL
    zrev-listz--left-rightz--countz...HEADr   Tu   rev-list 실패 (rc=u-   ) — 로컬 전용 환경으로 간주, PASSrF   u   rev-list 출력 파싱 실패 (u   ) — PASS (관대)rE   Fzbehind=z ahead=u%    — rebase 권장: git pull --rebase)Tu#   ahead=0 behind=0 — push 불필요zahead=z behind=u    — push 가능)rb   splitr   int)r   rW   rcoutpartsbehindaheads          r
   check_b2_ahead_behindr      s    
 	^Y8*G0DEsGB 
Qw+B4/\]]]IIKE
5zQ6sg=PQQQaM3uQx=EFzxwug5Z[[[zfk:6%0@AAAr   c                 N  	 | j                  dg       }|j                  dg       }|j                  dg       }|syg }g }|D ]`  	t        	fd|D              r|j                  	       )|j                  	      r;t        	fd|D              rP|j                  	       b |r'dj	                  |dd	       }d
dt        |       d| fS |rGdj	                  |dd	       }t        |      d	kD  r|dt        |       dz  }d
dt        |       d| fS ddt        |       dfS )u   
    head_diff 기준 (push될 커밋).
    forbidden 1건이라도 → FAIL.
    system-ignore 후 allowed에 모두 매치되어야 PASS.
    	head_diffr=   r>   )Tu)   head_diff 없음 (커밋 없음) — PASSc              3   6   K   | ]  }t        |        y wrl   rm   rn   s     r
   rq   z&check_b3_task_scope.<locals>.<genexpr>  rr   rs   c              3   6   K   | ]  }t        |        y wrl   rm   ru   s     r
   rq   z&check_b3_task_scope.<locals>.<genexpr>  rw   rs   rx   Nry   Fu   forbidden_paths 침범 r|   rz   r{   u   task scope 밖 파일 Tz
head_diff r}   )r+   r   rP   rJ   r&   r   )
rc   rd   r$   r   r   r>   forbidden_hitsr   r   r   s
            @r
   check_b3_task_scoper     sU    %==b9I044WbAM!2!6!67H"!MO@ "N L 
&>o>>!!$'  &@-@@%
& >"1-./N0C/DE&RRR<+,|q s<0166F.s</@.AvhOOO:c)n--ABBBr   strictc                     t        j                  | |      }|d   dk(  r|ryt        dt        j                         y|d   sd	j                  |d
   dd       }d|fS dd|d    d|d    dfS )uv   
    qc_report_guard.check() 호출.
    qc-result 없으면: strict=True → FAIL, 기본 → WARN(rc=0 유지).
    )r   r   json_verdictMISSING)Fu/   qc-result 파일 없음 (--strict 모드: FAIL)uT   [pre-push-guard] WARN: qc-result 없음 — --strict 미사용이므로 통과 (B-4)file)Tu8   qc-result 없음 — WARN (rc=0 유지, --strict 없음)okz; 
violationsN   FTzJSON=z
 / report=report_verdictu    — 일치)qc_report_guardr[   printsysstderrr&   )r   r   r   rB   r   s        r
   check_b4_qc_reportr   1  s     ""7iHFn*Kb	
 P$<6,/34f}
~&'z&9I2J1K;W r   c                  p   t        j                  d      } | j                  ddd       | j                  ddd	
       | j                  ddd
       | j                  ddd
       | j                  ddd       | j                         }|j                  }|j
                  }|j                  }|j                  }t        d| t        j                         t        ||      \  }}|3t        d| t        j                         t        j                  d       t        |      }t        j                  ||      \  }	}
|
rt        d|
 t        j                         t!        |	||      \  }}|rdnd}t        d| d| t        j                         t#        ||      \  }}|rdnd}t        d| d| t        j                         t%        |	||      \  }}|rdnd}t        d| d| t        j                         t'        |||j(                        \  }}|rdnd}t        d| d| t        j                         |xr
 |xr |xr |}|r1t        d t        j                         t        j                  d!       y t        d"t        j                         t        j                  d       y )#NuK   pre_push_guard.py — push 직전 4가지 안전 점검 (Guard MVP Phase 1))descriptionz	--task-idTu   task ID (예: task-2434))requiredhelpz
--base-shazorigin/mainu'   비교 기준 ref (기본: origin/main))defaultr   z--cwdz/home/jay/workspaceu2   git 저장소 루트 (기본: /home/jay/workspace)z--workspaceu?   capability/event 검색용 루트 (기본: /home/jay/workspace)z--strict
store_trueu5   qc-result 누락 시 FAIL (기본: WARN으로 통과))actionr   z[pre-push-guard] task=r   z[pre-push-guard] ERROR: rE   u2   [pre-push-guard] WARN: diff 수집 일부 실패: PASSFAILz  B-1 working tree clean    : u    — z  B-2 ahead/behind          : u      B-3 task scope 일치       : u#     B-4 보고서/qc-result 일치 : z%[pre-push-guard] OVERALL: PASS (rc=0)r   z%[pre-push-guard] OVERALL: FAIL (rc=1))argparseArgumentParseradd_argument
parse_argsr   rW   r   base_shar   r   r   r7   exitr   _task_scopeget_diff_setsr   r   r   r   r   )parserrV   r   rW   r   r   r$   res_errrd   rc   diff_errb1_pass	b1_detail_statusb2_pass	b2_detailb3_pass	b3_detailb4_pass	b4_detailoverall_passs                        r
   mainr   Q  s   $$aF d9ST
mF  H
)>Q  S
/D^  `

<T  VDllG
((CI}}H	"7)
,3::> "<GY!Ow (	2D *'2N &33HcBIxB8*MTWT^T^_ />#4GY  fVG	*7)5
D3::V /x=GYfVG	*7)5
D3::V -YHYZGYfVG	,WIU9+
FSZZX ,GYLGYfVG	/yi[
IPSPZPZ[ >w>7>wL5SZZH5SZZHr   __main__)r   N)#__doc__r   r   r)   r   r   r\   r   r   insertdirnameabspath__file__r   
task_scoper   r@   Patternr   boolr   tuplerA   r7   r,   r/   r?   r   rb   r   r   r   r   r   __name__ r   r
   <module>r      s  
    	 	  
 277??277??8#<= >  !# "** D* *3 *4 *(!7 !7 !7dTkSVFV@W !7H
 
s 
t 
& &t &>*49 *3 *5c? *3N3NJJ3N 3N 49	3NpBC Bc BeD#I6F B6*C*CJJ*C *C 49	*C^  49	@BJ zF r   