
    9bjN              
       8   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                  ej                  j                  e            Zej                  j                  de       ej                  j                  e      Zeej                  vrej                  j                  de       ddlZddlZddlmZ dededefd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fdZdededz  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)Pathtask_id	workspacereturnc                 R   |dz  dz  |  dz  }d}|j                         r4	 t        j                  |j                               j	                  dd      }t        j                         }d|v xs" dt        j                  j	                  dd      v }|r|r|S y# t
        $ r d}Y Pw xY w)	u   lock_sha..HEAD 기준 diff base 결정 (PR sub-task worktree 오판 방지).

    worktree 컨텍스트 + lock 파일에 lock_sha 존재 시 lock_sha 반환,
    그 외에는 origin/main fallback.
    z.taskslocksz.lock lock_shaz/.worktrees/GIT_DIRorigin/main)	existsjsonloads	read_textget	Exceptionosgetcwdenviron)r   r   	lock_filer
   cwdis_worktrees         G/home/jay/workspace/.worktrees/task-2696-dev7/scripts/pre_push_guard.py_resolve_diff_baser   %   s     H$w.G9E1BBIH	zz)"5"5"78<<ZLH ))+C C'Z>RZZ^^IWY=Z+ZKx  	H	s   3B B&%B&c                     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     r   _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\*\*/r	   F)	endswith
startswithfnmatchreplacer   r#   basenamer   sub)r"   r#   prefixfilename
pat_simples        r   
glob_matchr1   _   s    "v~>#!>>$w??4s!;<77##D)VVHb'2::5"E
??8Z0??4))r!   c                    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#   joinr   openr   loadr   _substitute_placeholderr   read_parse_allowed_resources_yaml)	r   r   cap_pathfsnapare	task_path	task_texts	            r   _resolve_allowed_resourcesrE   s   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rA   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+   )rG   itemr   s     r   _subz%_substitute_placeholder.<locals>._sub   s"    ?DEt['2EEEs   "pathsforbidden_paths)liststrdict)rA   r   rJ   results    `  r   r;   r;      si    FDI F$s) F "XF&vg/wF"$(0A)B$C !Mr!   valuec                 T    d| v r#| j                  dd      d   j                         } | S )u   YAML list item 의 inline ``# 주석`` 제거 (single split, no quote escape).

    예) ``"path/to/file"  # 주석`` -> ``path/to/file``.
    본 task 에서 path 안에 ``#`` 사용은 없으므로 단순 split 으로 충분.
    #   r   )splitrstrip)rQ   s    r   _strip_yaml_inline_commentrW      s.     e|C#A&--/Lr!   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      rh|d	v rd|d
d j                         }t        |      }|j                         j                  d      j                  d      }|s||   j                  |       |s|j                  d      rd|v sd} |r|S dS )u   ``## allowed_resources`` 섹션에서 YAML 블록 파싱.

    수정 사항 (task-2471 hardening):

    * numbered heading 인식 (``## 7. allowed_resources`` 형식 OK)
    * list item 의 inline ``# 주석`` 제거
    z?^##\s+(?:\d+\.\s+)?allowed_resources\s*\n```(?:yaml)?\n(.*?)```NrT   zpaths:rK   zforbidden_paths:rL   z- )rK   rL      "'-:)
r   search	MULTILINEDOTALLgroup
splitlinesstripr)   rW   append)rX   m	yaml_textrP   current_keylinestrippedrQ   s           r   r=   r=      s;    			JbllRYY&	A 
I F"K$$& ::<x(!K"$F;  !34+K"$F;  &;:V+VQRL&&(E.u5EKKM'',2237E{#**51h11#63(?K  6%%r!   branchc                    	 ddl m}  ||       S # t        $ r t        | t              r| sY yt        j                  d|       }|sY yd|j                  d       }|j                  d      r|d|j                  d       z  }|j                  d	      r|d|j                  d	       z  }|j                  d
      r|d|j                  d
       z  }|cY S w xY w)u   branch 에서 task id 추출 (+N suffix 보존).

    ``utils.task_id_parser`` 의 동명 함수에 위임. 내부적으로
    Phase/parallel/retry 모두 보존한다.
    r   )extract_task_id_from_branchNz3task-(\d+)(?:_(\d+\.\d+))?(?:_([a-z]))?(?:\+(\d+))?ztask-rT   rZ   _      +)utils.task_id_parserrm   ImportError
isinstancerN   r   r_   rb   )rk   _v2_extractrf   outs       r   rm   rm      s    	
 6"" &#&fIIB
 aggaj\"771:Qqwwqzl##C771:Qqwwqzl##C771:Qqwwqzl##C
#s    CCBCCargsr   c                     dd|g| z   }t        j                  |ddd      }|j                  |j                  j	                         fS )u1   git 실행 → (returncode, stdout). shell=False.gitz-CTF)capture_outputrX   check)
subprocessrun
returncodestdoutrd   )rw   r   cmdrs       r   _run_gitr      sA    $
t
#Cs4d%HA<<)))r!   	diff_setsignore_patternr6   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 )Nr[   )r)   r(   rd   )r   s    r   _strip_quotesz,check_b1_working_tree.<locals>._strip_quotes  s*     ||C0QZZ_qwws|K!Kr!   working_tree_modified	untracked)Tu   변경 없음 (clean)rK   rL   c              3   6   K   | ]  }t        |        y wNr1   .0fpr#   s     r   	<genexpr>z(check_b1_working_tree.<locals>.<genexpr>#       >z"d#>   z (forbidden)c              3   6   K   | ]  }t        |        y wr   r   r   apr#   s     r   r   z(check_b1_working_tree.<locals>.<genexpr>'       @B:b$'@r   , N    ... (   건)Fu   task scope 밖 변경    건: u/    — git stash push -u 로 격리 후 재시도Tu   system-ignore 후    건 모두 scope 내)rN   setr   r_   anyre   r8   len)r   r   r6   r   r   dirtyafter_ignoreallowed_pathsrL   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-   ) — 로컬 전용 환경으로 간주, PASSrZ   u   rev-list 출력 파싱 실패 (u   ) — PASS (관대)rT   Fzbehind=z ahead=u%    — rebase 권장: git pull --rebase)Tu#   ahead=0 behind=0 — push 불필요zahead=z behind=u    — push 가능)r   rU   r   int)r   r   rcrv   partsbehindaheads          r   check_b2_ahead_behindr   8  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_diffrK   rL   )Tu)   head_diff 없음 (커밋 없음) — PASSc              3   6   K   | ]  }t        |        y wr   r   r   s     r   r   z&check_b3_task_scope.<locals>.<genexpr>i  r   r   c              3   6   K   | ]  }t        |        y wr   r   r   s     r   r   z&check_b3_task_scope.<locals>.<genexpr>p  r   r   r   Nr   Fu   forbidden_paths 침범 r   r   r   u   task scope 밖 파일 Tz
head_diff r   )r   r   re   r_   r8   r   )
r   r   r6   r   r   rL   forbidden_hitsr   r   r#   s
            @r   check_b3_task_scoper   S  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; 
violationsNro   FTzJSON=z
 / report=report_verdictu    — 일치)qc_report_guardr{   printsysstderrr8   )r   r   r   rP   r   s        r   check_b4_qc_reportr     s     ""7iHFn*Kb	
 P$<6,/34f}
~&'z&9I2J1K;W r!   c                     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                  }|dv r>t        |t        |            }||k7  r"t        d| d| dt        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-shar   u'   비교 기준 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   )r   autoAUTOu)   [pre-push-guard] base_sha 자동 분기: u    → z (lock_sha)r   z[pre-push-guard] task=z&[pre-push-guard] ERROR (fail-closed): rT   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   r   r   base_shar   r   r   r   r   rE   exitr    _task_scopeget_diff_setsr   r   r   r   r   )parserrw   r   r   r   r   resolvedr6   res_errr   r   diff_errb1_pass	b1_detail_statusb2_pass	b2_detailb3_pass	b3_detailb4_pass	b4_detailoverall_passs                         r   mainr     s   $$aF d9ST
mF  H
)>Q  S
/D^  `

<T  VDllG
((CI}}H 22%gtI?x=hZuXJVabilisist	"7)
,3::> "<GY!Ow 6wi@szzR *'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#   dirnameabspath__file___SCRIPTS_DIRinsert_WORKSPACE_ROOTr   
task_scoper   pathlibr   rN   r   Patternr    boolr1   tuplerO   rE   r;   rW   r=   rm   rM   r   r   r   r   r   r   r   __name__ r!   r   <module>r      ss  
    	 	  
wwrwwx89 <  ''//,/#(("HHOOA'  ! 
   0# "** D* *3 *4 *(!7 !7 !7dTkSVFV@W !7H
 
s 
t 
c c $& $&t $&N d
 B*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	@L^ zF r!   