
    OiL                       U 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	m
Z
 dZdZded<   d	Zd
Ze dZ e
e      j%                         j&                  j&                  Zedz  dz  ez  Z ej,                  d       G d d             Z eddddd       edddd       edddd       edddd       ed d!dd       ed"d#dd       ed$d%d&ddd'(      fZd)ed*<   dAdBd+ZdCdDd,ZdEd-ZdFd.ZdGd/ZdGd0ZdHd1ZdId2Z dFd3Z!dJd4Z"dJd5Z#	 	 dK	 	 	 	 	 	 	 	 	 	 	 dLd6Z$	 	 	 	 	 	 	 	 	 	 dMd7Z%dEd8Z&dFd9Z'dNd:Z(dOd;Z)dNd<Z*dNd=Z+dPdQd>Z,dPdRd?Z-e.d@k(  r e/ e-             y)Su  task-2450 — Multi-Repo Enforcement Phase A1 setup automation.

7개 회장 리포에 dev_workspace와 동일한 enforcement 패턴 (8 required checks
+ ruleset main-protection)을 일괄 적용합니다.

회장 절대 기준
--------------
- "task.md 수정 금지. 중간 정정 = 100% 무효."
- "taskctl을 거치지 않고는 main을 절대 변경할 수 없다 — 모든 회장 리포에서 동일하게."

본 스크립트의 책임 범위
----------------------
- 7개 외부 리포에 ci.yml(또는 taskctl-ci.yml) workflow + qc_report_guard.py stub
  추가 PR 생성 (idempotent)
- 7개 외부 리포에 ruleset main-protection 등록 (idempotent)
- dev_workspace 기존 ruleset 15896715 무변경 (회귀 0)

비즈니스 코드(src/app/server/lib)는 절대 손대지 않습니다.

사용 예시
--------
    # 사전 점검 (변경 0)
    python3 scripts/multi_repo_enforcement_setup.py --dry-run

    # workflow + stub PR 일괄 생성
    python3 scripts/multi_repo_enforcement_setup.py --create-prs

    # ruleset 일괄 등록 (Pro 활성화 후)
    python3 scripts/multi_repo_enforcement_setup.py --register-rulesets

    # 합격조건 A/B/C 검증
    python3 scripts/multi_repo_enforcement_setup.py --verify
    )annotationsN)PathJonghyukJeon)zcancel-kill-switchzqc-checkzhidden-path-auditzlock-in-checkzmerge-safety-checkzgemini-review-gatezci/guardguardztuple[str, ...]REQUIRED_CHECKSzmain-protectionz	task-2450z-multi-repo-enforcementscripts	templatesT)frozenc                  V    e Zd ZU dZded<   ded<   ded<   ded<   dZd	ed
<   dZded<   y)RepoSpecu   7 개 외부 리포 정의.strnameintprioritylanguageworkflow_filenameFboolneeds_initial_commit notesN)__name__
__module____qualname____doc____annotations__r   r        'scripts/multi_repo_enforcement_setup.pyr   r   C   s.    %
IMM!&$&E3Or   r   InsuRo   
TypeScriptztaskctl-ci.ymlu   기존 ci.yml 보호)r   InsuWiki   ci.ymlMediScan   Python
ThreadAuto   BlogAuto   InfoKeyword   zmy-asset-manager   unknownu$   empty repo — initial commit 필요)r   r   ztuple[RepoSpec, ...]REPOSc                >    dg| }t        j                  ||d|      S )u4   gh CLI 호출. capture=True면 stdout/stderr 캡쳐.ghT)capture_outputtextcheck
subprocessrun)argscapturer5   cmds       r   _ghr<   a   s*    -$-C>>	 r   c                <    t        j                  dg|| dd|      S )NgitT)cwdr3   r4   r5   r6   )r?   r9   r5   s      r   _gitr@   l   s(    >>	 r   c                    t        ddt         d| j                   ddg      }|j                  dk7  r%t	        d| j                   d|j
                         |j                  xs d	j                         }|xs d
S )Napirepos//--jqz.default_branchr   z!default_branch lookup failed for z: r   main)r<   OWNERr   
returncodeRuntimeErrorstderrstdoutstrip)reporesbranchs      r   fetch_default_branchrP   v   sv    
uugQtyyk2F<MN
OC
~~>tyykCJJ<XYYjjB%%'FVr   c                   t        ddt         d| j                   dg      }|j                  dk7  r0|j                  xs dj                         }d|v sd|v rd	|d
S d|d
S 	 t        j                  |j                  xs d      }|D cg c]  }|j                  d      t        k(  s| }}d||dS # t        j                  $ r}dd| d
cY d}~S d}~ww xY wc c}w )uI   리포 ruleset 요약. 403 (Pro 미가입) 시 status=blocked 로 마크.rB   rC   rD   	/rulesetsr   r   zUpgrade to GitHub Pro403blocked-pro)statusmessageerror[]zjson decode: Nr   ok)rU   rulesetsmatching)r<   rG   r   rH   rJ   rL   jsonloadsrK   JSONDecodeErrorgetRULESET_NAME)rM   rN   msgdataexcrr[   s          r   fetch_ruleset_summaryre   ~   s    
uugQtyyk;<
=C
~~zzR&&("c)Uc\+<<!c22Ezz#**,-  Aa155=L#@AHA(CC  E!cU.CDDEAs*   ##B4 
C(C4CCCCc           	         t        ddt         d| j                   d| j                   ddg      }|j                  dk(  xr+ |j
                  xs dj                         | j                  k(  S )	NrB   rC   rD   z/contents/.github/workflows/rE   .namer   r   )r<   rG   r   r   rH   rK   rL   rM   rN   s     r   workflow_presentri      so    
UG1TYYK'CDDZDZC[\		
C >>QWCJJ$4"#;#;#=AWAW#WWr   c                    t        ddt         d| j                   dddg      }|j                  dk(  xr! |j                  xs dj                         d	k(  S )
NrB   rC   rD   z$/contents/scripts/qc_report_guard.pyrE   rg   r   r   qc_report_guard.py)r<   rG   r   rH   rK   rL   rh   s     r   qc_stub_presentrl      s_    
UG1TYYK'KL		
C >>QUCJJ$4"#;#;#=AU#UUr   c                   t        dddt         d| j                   dddt        dd	g
      }|j                  d
k7  ry	 t        j                  |j                  xs d      }|r|d
   S dS # t
        j                  $ r Y yw xY w)uE   현재 BRANCH_NAME 으로 열려있는 PR을 반환 (없으면 None).prlist--reporD   z--stateopen--headz--jsonz"number,url,headRefName,title,stater   NrX   )	r<   rG   r   BRANCH_NAMErH   r\   r]   rK   r^   )rM   rN   rb   s      r   list_existing_prrt      s    
gQtyyk"0	
C ~~zz#**,- 47$$  s   #A* *B ?B c                     t         ddg dg dgdiddiddid	d
dg dddg ddddddt        D  cg c]  } d| i c} ddgdS c c} w )u   dev_workspace ruleset 15896715 와 동일 구조.

    `~DEFAULT_BRANCH` 키워드 사용으로 main/master 자동 매칭.
    rO   activeref_namez~DEFAULT_BRANCH)excludeincludetypedeletionnon_fast_forwardpull_requestr   FT)mergesquashrebase)required_approving_review_countdismiss_stale_reviews_on_pushrequired_reviewersrequire_code_owner_reviewrequire_last_push_approval!required_review_thread_resolutionallowed_merge_methods)rz   
parametersrequired_status_checkscontext)$strict_required_status_checks_policydo_not_enforce_on_creater   )r   targetenforcementbypass_actors
conditionsrules)r`   r   )ctxs    r   build_ruleset_payloadr      s     -.
 Z '(&785:*,16279=-J 1<@054C/-0C(/	
% %>/s   Ac                ~   t               }t        |       }|j                  d      dk(  rdd|dS |j                  d      dk(  r|j                  dg       ng }d}d	t         d
| j                   d}|r!d}d	t         d
| j                   d|d   d    }t        j                  |      }t        j                  ddd||ddddg	|dd      }|j                  dk7  rd|j                  j                         dS i }	 t        j                  |j                  xs d      }	t        |	t              r|	}|j                  d      xs g }
|
D cg c]$  }t        |t              s|j                  d      & }}|dk(  rdnd|j                  d      |j                  d       |d!S # t
        j                   $ r i }Y w xY wc c}w )"uA   idempotent: 이미 main-protection 있으면 PUT, 없으면 POST.rU   rT   skippedzgithub-pro-required)actionreasondetailrY   r[   POSTrC   rD   rR   PUT
/rulesets/r   idr2   rB   z-Xz-Hz#Accept: application/vnd.github+jsonz--input-T)inputr3   r4   rW   )r   rV   {}r   rz   
registeredupdatedr   )r   
ruleset_idr   r   )r   re   r_   rG   r   r\   dumpsr7   r8   rH   rJ   rL   r]   rK   
isinstancedictr^   )rM   payloadexistingr[   methodendpointpayload_strrN   bodyloaded	rules_rawrd   
rule_typess                r   register_rulesetr      s   #%G$T*H||H.#/DPXYY/7||H/E/Mx||J+SUHFwa		{)4HE7!DII;j!T9J8KL**W%K
..1
	
 C  ~~!cjj.>.>.@AADCJJ.$/fd#D !'RI)2JAjD6I!%%-JJJ"(F"2,	hhtnxx.	 	   Ks   :5F F:F:F76F7c                &    | j                  d      S Nutf-8encoding	read_texttemplate_paths    r   render_workflowr          ""G"44r   c                &    | j                  d      S r   r   r   s    r   render_qc_stubr   $  r   r   c                   |rd| j                   dS t        |       }|rd|d   |d   dS |xs t        d      }|j                  dd	       || j                   z  }|j	                         rt        j                  |       d
t         d| j                    d}| j                  rt        | |||      S t        t        j                         ddd|t        |      gd      }|j                  dk7  rdd|j                  j                         dS t!        |       }	t        |d|	gd       t        |ddt"        gd       |dz  dz  | j$                  z  }
|
j&                  j                  dd	       |
j	                         rddt        |
      dS |
j)                  t+        |      d       |dz  dz  }|j&                  j                  dd	       |j	                         s-|j)                  t-        |      d       |j/                  d        t        |d!t        |
      t        |      gd       d"}t        |d#d$|gd       t        |d%d&d't"        gd      }|j                  dk7  rdd%|j                  j                         dS t1        d(d)d*t         d| j                    d+|	d,t"        d-d.d/t3        |       g      }|j                  dk7  rdd0|j                  j                         dS d1|j4                  xs d2j                         d3S )4u   리포 clone → branch 생성 → workflow + stub 추가 → push → PR 생성.

    idempotent:
    - 같은 BRANCH_NAME 의 OPEN PR 있으면 그대로 반환
    - workflow/stub 이미 있으면 추가하지 않음 (덮어쓰기 금지)
    zdry-run)r   rM   existsnumberurl)r   	pr_numberr   /tmp/multi-repo-enforcementTparentsexist_okhttps://github.com/rD   .gitclonez--depth10Fr5   r   rW   r   steprV   checkout-b.github	workflowsr   zworkflow-already-exists)r   r   pathr   r   r   rk     addu   task-2450: add multi-repo enforcement workflow + qc stub

- 8 required checks (dev_workspace ruleset 15896715 정합)
- 비즈니스 코드 0 변경 (PR enforcement만 추가)
commit-mpush-uoriginrn   createrp   z--baserr   z--titlez*task-2450: multi-repo enforcement Phase A1z--bodyz	pr-createcreatedr   )r   r   )r   rt   r   mkdirr   shutilrmtreerG   r   _bootstrap_empty_repor@   r?   r   rH   rJ   rL   rP   rs   r   parent
write_textr   r   chmodr<   _pr_bodyrK   )rM   workflow_templateqc_stub_templatedry_run	work_rootr   workdir	clone_urlrN   default_branchworkflow_dststub_dst
commit_msgpush_respr_ress                  r   create_pr_for_repor   (  s    #TYY77%H"(1CHUZO\\@T"?@IOOD4O0$))#G~~g%eWAdii[=I   $T74EGWXX
txxzGYiWNV[
\C
~~!7szz?O?O?QRR)$/N:~.e<:t[1?Y&4t7M7MMLdT:/%
 	

 O,=>Q"%99HOO$6??N+;<wOu5#l+S];5IF 
 	8T:.e<GfdHkB%PHa!6hoo>S>S>UVVgQtyyk"8TN	
F  A!;6==CVCVCXYY)<"(C(C(EFFr   c                   |j                  dd       t        |g dd       t        |dddd	t         d
| j                   dgd       |dz  }|j	                  d| j                   dd       |dz  dz  | j
                  z  }|j                  j                  dd       |j	                  t        |      d       |dz  dz  }|j                  j                  dd       |j	                  t        |      d       |j                  d       t        |ddt        |      t        |      gd       t        |g dd       t        |g dd      }|j                  dk7  rdd|j                  j                         dS dddS )u:   empty repo (커밋 0개) initial commit + workflow + stub.Tr   )initr   rF   Fr   remoter   r   r   rD   r   z	README.mdz# z?

task-2450: initial commit (multi-repo enforcement Phase A1).
r   r   r   r   r   rk   r   )r   r   z8task-2450: bootstrap empty repo + multi-repo enforcement)r   r   r   rF   r   rW   z
push-emptyr   bootstrappeduH   empty repo — initial commit pushed directly to main (no prior history))r   note)r   r@   rG   r   r   r   r   r   r   r   r   rH   rJ   rL   )rM   r   r   r   readmer   r   r   s           r   r   r     s    MM$M.(68UH0CE7!DII;VZ.[\dij{"F

TYYK I 	I   Y&4t7M7MMLdT:O,=>Q"%99HOO$6'787KNN55+s<'8#h-HPUVT
 G=UKHa!<HOODYDYD[\\ !Z r   c                <    d| j                    d| j                   dS )Nu1   ## task-2450 multi-repo enforcement Phase A1 — u2   

## Summary
- 8 required checks workflow 추가 (u&  )
- qc_report_guard.py minimum stub 추가 (부재 시에만)
- 비즈니스 코드 0 변경 (src/app/server/lib 미접근)

## 회장 절대 기준
- taskctl을 거치지 않고는 main을 절대 변경할 수 없다 — 모든 회장 리포에서 동일하게.

## 회귀 보호
- 기존 워크플로우 파일 덮어쓰기 0건
- 빌드 설정 (package.json / pyproject.toml) 수정 0건

## Test plan
- [ ] 8 required checks 모두 PASS 확인
- [ ] dummy task PR로 5 케이스 차단 검증
- [ ] 회장 manual merge (봇 자체 머지 금지)
)r   r   )rM   s    r   r   r     s1    
;DII; G0040F0F/G HA	Br   c           
     t   | j                   | j                  d}t        |       |d<   t        |       |d<   t	        |       }|j                  d      |d<   |j                  d      dk(  r|j                  dg       ng }|r|d   }|j                  d	      d
k(  |d<   |j                  d      |d<   t        ddt         d| j                    d|d    g      }|j                  dk(  r	 t        j                  |j                  xs d      }g }|j                  dg       D ]<  }|j                  d      dk(  s|d   j                  dg       D 	cg c]  }	|	d   	 }}	> ||d<   t        t              j                  t        |            |d<   |S d|d<   d|d<   |S # t        j                  $ r i }Y w xY wc c}	w )u<   합격조건 A (workflow), B (ruleset), C (8 checks) 검증.)rM   r   A_workflow_presentrl   rU   ruleset_statusrY   r[   r   r   rv   B_ruleset_activer   r   rB   rC   rD   r   r   r   rz   r   r   r   required_checksC_8_checks_presentF)r   r   ri   rl   re   r_   r<   rG   rH   r\   r]   rK   r^   setr   issubset)
rM   summaryrsr[   ruledetfullchecksrd   cs
             r   verify_acceptancer    s   !YYDMMBG$4T$:G !!0!6G	t	$B "x 0G)+)9T)Arvvj"%rH{&*hh}&=&I"# $5F5'499+ZT
|LMN>>Qzz#**"45 FXXgr* g55=$<<45lO4G4GH`bd4efqa	lfFfg *0G%&,/,@,I,I#f+,VG() N ',"#(-$%N '' 
 gs   '#F F5F21F2c                 P   t        d       t        D ]  } t        |       }t        |       }t	        |       }|j                  d      }|dk(  rt        |j                  d            nd}t        d| j                   d| j                  dd	|rd
nd d|rd
nd d| |rdnd         y)Nz.== task-2450 multi-repo enforcement dry-run ==rU   rY   r[   Fz  [z] z<20z
 workflow=YNz	 qc_stub=z	 ruleset=z/matchr   r   )	printr0   ri   rl   re   r_   r   r   r   )rM   wfqcr   	rs_statusrs_matchs         r   cmd_dry_runr    s    	
:; 
d#T""4(FF8$	/8D/@4z*+e$--499S/ 2!s+ , sc* + kh(B!?A	

 r   c                   |dz  }|dz  }|j                         r|j                         st        d| t        j                         yg }t        D ]{  }t        d|j
                   d       t        ||||       }t        t        j                  |d	d
             |j                  d      dk(  sa|j                  |j
                         } |rdS dS )Nr$   zqc_report_guard_stub.pyzERROR: templates missing in )filer#   == z ==)r   Fensure_asciiindentr   rW   r    r   )is_filer
  sysrJ   r0   r   r   r\   r   r_   append)r   templates_dirr   r   failuresrM   results          r   cmd_create_prsr    s    %0$'@@$$&.>.F.F.H,]O<3::NH 'DII;c"###%5
 	djjeA>?::h7*OODII&' 1ar   c                    g } t         D ]v  }t        d|j                   d       t        |      }t        t	        j
                  |dd             |j                  d      dv s\| j                  |j                         x | rdS d	S )
Nr  z ruleset ==Fr#   r  r   )rW   r    r   )r0   r
  r   r   r\   r   r_   r  )r  rM   rN   s      r   cmd_register_rulesetsr    sz    H 'DII;k*+t$djj5;<778
*OODII&' 1ar   c                     t         D  cg c]  } t        |        }} t        t        j                  |dd             t        d |D              }|rdS dS c c} w )NFr#   r  c              3     K   | ];  }|j                  d       xr$ |j                  d      xr |j                  d       = yw)r   r   r   N)r_   ).0rd   s     r   	<genexpr>zcmd_verify.<locals>.<genexpr>  sC       	
"#a.@(AaaeeL`Faas   AAr   r    )r0   r  r
  r\   r   all)rM   rowsall_oks      r   
cmd_verifyr&    s[    056d#6D6	$**Ta
89  F 1A 7s   Ac                l   t        j                  d      }|j                  dd       |j                  dd       |j                  dd       |j                  dd       |j                  d	t        t        d
             |j                  dt        t        d             |j	                  |       S )Nmulti_repo_enforcement_setup)progz	--dry-run
store_true)r   z--create-prsz--register-rulesetsz--verifyz--templates-dirz%/tmp/multi-repo-enforcement/templates)rz   defaultz--work-rootr   )argparseArgumentParseradd_argumentr   
parse_args)argvparsers     r   r/  r/    s    $$*HIF
L9
|<
-lC

<8
<=  
 23  
 T""r   c                j   t        |       }|j                  s.|j                  s"|j                  s|j                  s
t               S |j                  r
t               S |j                  r t        |j                  |j                        S |j                  r
t               S |j                  r
t               S y)Nr   )r/  r   
create_prsregister_rulesetsverifyr  r  r   r  r  r&  )r0  r9   s     r   rF   rF   /  s    dDLLDOOt/E/E}||}dnnd.@.@AA$&&{{|r   __main__)TF)r9   	list[str]r:   r   r5   r   returnsubprocess.CompletedProcess)T)r?   r   r9   r7  r5   r   r8  r9  )rM   r   r8  r   )rM   r   r8  r   )rM   r   r8  r   )rM   r   r8  zdict | None)r8  r   )r   r   r8  r   )FN)rM   r   r   r   r   r   r   r   r   zPath | Noner8  r   )
rM   r   r   r   r   r   r   r   r8  r   )r8  r   )r   r   r  r   r8  r   )N)r0  list[str] | Noner8  zargparse.Namespace)r0  r:  r8  r   )0r   
__future__r   r,  dataclassesr\   r   r7   r  pathlibr   rG   r   r   r`   TASK_IDrs   __file__resolver   	REPO_ROOTDEFAULT_TEMPLATES_DIR	dataclassr   r0   r<   r@   rP   re   ri   rl   rt   r   r   r   r   r   r   r   r  r  r  r  r&  r/  rF   r   
SystemExitr   r   r   <module>rE     s6   B #      
 	$ 	 !
	01N""$++22	!I-;gE  d#  $ Xq,(8@VWZL(3ZHh/\1h1ZHh/]A|X6	!4 $D 	X	V%2*Z1h55 !WG
WGWG WG 	WG
 WG 
WGt+
++ + 	+
 
+\&@" & #& z
TV
 r   