
    GiP              	          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ddlZddl	m
Z
 ddlmZ ddlmZ 	 ddlmZ  ee      Z eej0                  j3                  dd            Zed	z  d
z  Zej0                  j3                  dd      Zej0                  j3                  dd      Zedz  Zedz  Z ej@                  d      Z!dededefdZ"e
d*dedefd       Z#dedefdZ$dede%fdZ&de%ddfdZ'd+dededdfd Z(d,d!Z)d,d"Z*de%d#e+dede,e%   fd$Z-d,d%Z.d,d&Z/d,d'Z0d,d(Z1ed)k(  r e1        yy# e$ r< ej"                  j%                  d e ee      j*                               ddlmZ Y 6w xY w)-uQ  
Phase 자동 체이닝 시스템 — 멀티팀 Phase 오케스트레이터 (chain.py)

[역할] 여러 팀이 동일 Phase에서 병렬로 작업하고,
       모든 팀 완료 후 자동으로 다음 Phase를 dispatch한다.
[파일] memory/chains/{chain_id}.json (접두어 없음)
[호출] dispatch.py --chain <chain_id> → chain.py task-done
[구분] 순차 작업 체이닝은 chain_manager.py 참조

Usage:
    python3 chain.py create --id insuwiki-p1p2 --desc "InsuWiki Phase 1-2"
    python3 chain.py add-phase --chain insuwiki-p1p2 --name "Phase 1" --tasks '[{"team":"dev1-team","desc":"로그인 개발","level":"normal"}]'
    python3 chain.py task-done --chain insuwiki-p1p2 --task task-1.1
    python3 chain.py status --chain insuwiki-p1p2
    python3 chain.py list

참고: /home/jay/workspace/memory/docs/chaining-architecture.md
    N)contextmanager)datetime)Path)
get_loggerWORKSPACE_ROOTz/home/jay/workspacememorychainsCOKACDIR_CHAT_ID
6937032012COKACDIR_KEY_ANU zdispatch.pyz	.env.keysz^[a-zA-Z0-9_\-\.]+$value
field_namereturnc                 P    t         j                  |       st        | d|       | S )u;   영숫자+하이픈+밑줄+점만 허용. 인젝션 방어.u    에 허용되지 않는 문자: )_SAFE_ID_REmatch
ValueError)r   r   s     /home/jay/workspace/chain.py_validate_safe_idr   8   s,    U#J<'GyQRRL    
chain_pathmodec              #     K   | j                   j                  dd       t        | |d      }	 t        j                  |t        j
                         | t        j                  |t        j                         |j                          y# t        j                  |t        j                         |j                          w xY ww)uS   chain JSON 파일을 독점 락으로 열고, 컨텍스트 종료 시 언락한다.Tparentsexist_okutf-8encodingN)parentmkdiropenfcntlflockLOCK_EXLOCK_UNclose)r   r   file_objs      r   locked_chain_filer*   D   s      D48Jw7HHemm,Hemm, 	Hemm,s   ,C(B 5C6CCchain_idc                     t         |  dz  S )Nz.json)
CHAINS_DIR)r+   s    r   _chain_pathr.   V   s    8*E***r   c                    t        |       }|j                         s3t        d| t        j                         t        j
                  d       t        |dd      5 }t        j                  |      cddd       S # 1 sw Y   yxY w)uD   체인 파일을 읽어 dict로 반환. 락 없는 단순 읽기용.'   [ERROR] 체인 파일이 없습니다: file   rr   r   N)	r.   existsprintsysstderrexitr#   jsonload)r+   pathfs      r   _load_chainr>   Z   sd    x D;;=7v>SZZP	dC'	* ayy|  s   A<<Bdatac                     | j                  d       | j                          t        j                  || dd       | j	                          y)uK   이미 열린(락 보유) 파일 객체에 dict를 JSON으로 덮어쓴다.r   F   ensure_asciiindentN)seektruncater:   dumpflush)r)   r?   s     r   _save_chainrI   d   s6    MM!IIdH5;NNr   promptdelayc           
         t         st        j                  d       ydd| d|dt        dt         dg
}	 t	        j
                  |d	d	d
      }|j                  dk7  r1t        j                  d|j                  j                                 yt        j                  d| dd
         y# t        j                  $ r t        j                  d| dd
         Y yw xY w)u:   아누에게 cokacdir --cron으로 알림을 등록한다.u)   COKACDIR_KEY_ANU 미설정, 알림 스킵Ncokacdirz--cronz--atz--chatz--keyz--onceT<   )capture_outputtexttimeoutu4   cokacdir 알림 등록 타임아웃 (60초 초과): r   u   cokacdir 알림 등록 실패: u   알림 등록 완료: )ANU_KEYloggerwarningCHAT_ID
subprocessrunTimeoutExpired
returncoder8   stripinfo)rJ   rK   cmdresults       r   _cron_notifyr^   q   s    BCCDtRP A89L9L9N8OPQ,VCR[M:; $$ MfUXVXk][\s   B' '.CCc                    t         j                  dd       t        | j                        }|j	                         r=t        d| j                   t        j                         t        j                  d       | j                  | j                  ddt        j                         j                         g d}t        |d	d
      5 }t        j                  |t        j                          t#        j$                  ||dd       t        j                  |t        j&                         ddd       t        d|        t(        j+                  d| j                          y# 1 sw Y   :xY w)u"   빈 체인 파일을 생성한다.Tr   u-   [ERROR] 이미 존재하는 체인입니다: r1   r3   activer   )r+   descriptionstatuscurrent_phase_idx
created_atphaseswr   r   FrA   rB   Nu   [OK] 체인 생성 완료: u   체인 생성: )r-   r"   r.   idr5   r6   r7   r8   r9   descr   now	isoformatr#   r$   r%   r&   r:   rG   r'   rS   r[   )argsr   r?   r=   s       r   
cmd_createrl      s
   TD1TWW%J=dggYGcjjY GGyylln..0D 
j#	0 &AAu}}%		$a8Au}}%&
 
'
|
45
KK/$''+,& &s   A"E  E)c                 ^   	 t        j                  | j                        }g }D ]i  }d|vsd|vr0t	        dt
        j                         t        j                  d       |j                  |d   d|d   |j                  dd	      d
ddd       k | j                  d
|d}t        | j                        }|j                         s=t	        d| j                   t
        j                         t        j                  d       t        |d      5 }t        j                  |      }|d   j                  |       t!        ||       ddd       t#        d         dz
  }	t	        d| j                   d|	 d| j                   dt#        |       d	       t$        j'                  d| j                   d| j                   dt#        |              y# t         j                  $ r>}t	        d| t
        j                         t        j                  d       Y d}~d}~ww xY w# 1 sw Y   xY w)u    체인에 Phase를 추가한다.u"   [ERROR] tasks JSON 파싱 실패: r1   r3   Nteamrh   uA   [ERROR] tasks 항목에 'team', 'desc' 필드가 필요합니다.levelnormalpending)rn   task_idra   ro   rb   dispatched_atcompleted_at)namerb   tasksr0   r+re   u   [OK] Phase 추가 완료: u    → Phase[z] 'z' (u	   개 task)u   Phase 추가: chain=z, phase=z, tasks=)r:   loadsrv   JSONDecodeErrorr6   r7   r8   r9   appendgetru   r.   chainr5   r*   r;   rI   lenrS   r[   )
rk   	raw_tasksenormalized_taskst	new_phaser   r=   r?   	phase_idxs
             r   cmd_add_phaser      s   JJtzz*	  
?fAoU\_\f\fgHHQK&	 yw1#!% $
	
	
" 		!I TZZ(J7

|D3::V	:t	, yy|Xi(At
 DN#a'I	&tzzl+i[DII;VYZ]^nZoYppy
z{
KK&tzzl(499+XcRbNcMdefQ  21#6SZZHB s#   G 6H#H "3HH #H,r   c                    t        |d       | d   |   }g }|d   D ]  }t        |d   d       t        |d   d       t        dz  dz  }|j                  dd       d	| d
| d|d    d}||z  }|j                  |d   d       t        j
                  j                         }	t        t              }
t        |
      j                         rpt        j                  dddt        j                  |
       dgdd      }|j                  j!                         D ]!  }d|v s|j#                  dd      \  }}||	|<   # dt        t$              d|d   dt        |      d|d   d|g
}t&        j)                  d|d    d|        	 t        j                  |ddd|	      }|j0                  d(k(  r	 t3        j4                  |j                        }|j;                  d*      }||d*<   t=        j>                         jA                         |d+<   d,|d#<   |j/                  |d   |d-d.       t&        j)                  d/|d    d0|        )|jB                  j9                         xs |j                  j9                         }d"|d#<   ||d%<   |j/                  |d   d&d%|d'       t&        j-                  d1|d    d2|         |S # t        j*                  $ rF t&        j-                  d |d    d| d!       d"|d#<   d$|d%<   |j/                  |d   d&d%d$d'       Y w xY w# t2        j6                  $ r  d)|j                  j9                         i}Y w xY w)3uZ   지정 Phase의 모든 tasks를 dispatch.py를 통해 위임하고 결과를 반환한다.r+   re   rv   rn   ro   r   Tr   zchain-z-phase-z.mdra   r   r   bashz-czsource z && env)rO   rP   =r3   python3z--teamz--task-filez--level--chainu   dispatch 호출: team=z, chain=rN   )rO   rP   rQ   envu   dispatch 타임아웃: team=u    (60초 초과)dispatch_errorrb   u$   dispatch 타임아웃 (60초 초과)errorN)rn   rr   rb   r   r   rawrr   rs   in_progress
dispatched)rn   rr   rb   u   dispatch 성공: team=
, task_id=u   dispatch 실패: team=z, error=)"r   	WORKSPACEr"   
write_textosenvironcopystrENV_KEYSr   r5   rV   rW   shlexquotestdout
splitlinessplitDISPATCH_PYrS   r[   rX   r   rz   rY   r:   rx   ry   rZ   r{   r   ri   rj   r8   )r?   r   r+   phaseresultstask	tasks_dirtask_file_nametask_file_pathr   env_keys_pathload_resultlinekvr\   r]   resprr   err_msgs                       r   _dispatch_phaser      s}    h
+N9%EGg LS$v,/$w-1 (72	t4!(6)Ad6l^3O"^3!!$}"5!H jjooH%%'$..])C(DGLM#K
 $**557 $;::c1-DAqCF LM
 	,T&\N(8*MN	^^#F( !6zz&--0 hhy)G%DO$,LLN$<$<$>D!*DNNNDLWP\]^KK0fj	RSmm))+Dv}}/B/B/DG-DN#DMNNDLTW_fghLL1$v,xyQRYLSZ N= (( 	LL7V~XhZWfgh-DNBDMNN L#%C	 	  '' 6v}}22456s%   K.LALL/MMc                 `   t        | j                        }|j                         s=t        d| j                   t        j
                         t	        j                  d       t        |d      5 }t        j                  |      }|j                  d      dk(  rnt        d| j                   dt        j
                         t        j                  d	| j                   d
| j                          t        ||       	 ddd       yd}d}t        |d         D ]7  \  }}|d   D ]&  }|j                  d      | j                  k(  s"|}|} n |s7 n |t        d| j                   d| j                   dt        j
                         t        j!                  d| j                   d
| j                          t        ||       t	        j                  d       d|d<   t#        j$                         j'                         |d<   t        j)                  d| j                   d
| j                   d|        |d   }	|d   |	   }
t+        d |
d   D              }|sGt-        d |
d   D              }t        d| j                   d| d       t        ||       	 ddd       yd|
d<   t        j)                  d| j                   d|	 d|
d    d        |	dz   }t/        |d         }||k  r|d   |   }|j                  d      dk(  r?t        d| j                   d!t        j
                         t        ||       	 ddd       yt1        ||| j                        }|D cg c]  }|d   d"k(  s| }}|rd|d<   ||d   |t#        j$                         j'                         d#|d"<   t        ||       t        d$t        j
                         |D ]6  }t        d%|d&    d'|j                  d"d(       t        j
                         8 t        j!                  d)| j                   d|        	 ddd       yd*|d<   ||d<   t        ||       t        d+|
d    d,|d    d-       |D ]  }t        d%|d&    d.|d            d/| j                   d0|
d    d,|d    d1}t3        |       t        j)                  d2|        nd|d<   t#        j$                         j'                         |d<   t        ||       t        d3| j                   d4       t        j)                  d5| j                          d/| j                   d6}t3        |       d/| j                   d0|
d    d7}t3        |       t        j)                  d8| j                          ddd       yc c}w # 1 sw Y   yxY w)9uH   특정 task를 완료 마킹하고, Phase 전환 로직을 수행한다.r0   r1   r3   rw   rb   pausedu   [WARNING] 체인 'u:   '이 paused 상태입니다. task-done을 무시합니다.u0   paused 체인에 task-done 호출 무시: chain=z, task=Nre   rv   rr   z[ERROR] task_id 'u   '를 체인 'u    '에서 찾을 수 없습니다.u   task_id 미발견: chain=	completedrt   u   task 완료 마킹: chain=z, phase_idx=rc   c              3   ,   K   | ]  }|d    dk(    yw)rb   r   N .0r   s     r   	<genexpr>z cmd_task_done.<locals>.<genexpr>g  s     W1AhK;6Ws   c              3   2   K   | ]  }|d    dk7  sd  yw)rb   r   r3   Nr   r   s     r   r   z cmd_task_done.<locals>.<genexpr>k  s     \!8P[A[A\s   z[OK] task 'u   ' 완료. 현재 Phase에 u   개 task 남음.u   Phase 완료: chain=z, name='ru   'uF   '이 paused 상태입니다. 다음 Phase dispatch를 건너뜁니다.r   )r   
phase_namedispatch_errorsoccurred_atu\   [ERROR] 다음 Phase dispatch 중 오류 발생. 체인을 paused 상태로 변경합니다.z	  - team=rn   z: u   알 수 없는 오류u-   Phase dispatch 실패로 체인 pause: chain=r   z[OK] u    전팀 완료. u    자동 dispatch 완료.r   u   체인 u   의 u    자동 dispatch됨.u   Phase 전환 알림 등록: u   [OK] 체인 'u   ' 전체 완료!u   체인 전체 완료: chain=u*    전체 완료. 종합 보고 작성하라uM    전팀 완료. 모든 Phase가 완료되어 체인이 종료되었습니다.u#   체인 완료 알림 등록: chain=)r.   r|   r5   r6   r7   r8   r9   r*   r:   r;   r{   rS   rT   r   rI   	enumerater   r   ri   rj   r[   allsumr}   r   r^   )rk   r   r=   r?   target_tasktarget_phase_idxp_idxr   r   rc   current_phaseall_completed	remainingnext_phase_idxtotal_phases
next_phasedispatch_resultsr4   errorsr   
notify_msg	final_msgs                         r   cmd_task_doner   =  s   TZZ(J7

|D3::V	:t	, vLyy| 88H)&tzzl2lmtwt~t~NNMdjj\Y`aeajaj`klm4 vL vL %d8n5 	LE5g 88I&$))3"&K',$	
 	 %dii[djj\Iijqtq{q{|LL4TZZL		{ST4 HHQK +H&.lln&>&>&@N#0GDII;l[kZlmn !!45X'89Wg@VWW\}W'=\\IK		{*DYKO_`a4 UvL vLZ #.h*4::,lCTBUU]^klr^s]ttuvw*Q.4>*L(h7J xx!X-(4z{ At$}vL vL@  /t^TZZP "2LAQx[G5KaLFL!)X!/",V"4'-#+<<>#;#;#=	!W At$t|  }G  }G  H oAIai[155BY3Z2[\cfcmcmnoLTZZLXdesdtuvcvL vLh $1Jx (6D$%4 E-/00@FAS@TTlmn% G	!F)Jq|nEFG
 $**T-*?)@@PU_`fUgThh|}  $KK6zlCD )DN#+<<>#;#;#=D 4 M$**-=>?KK6tzzlCD #4::,.XYJ$ $**T-*?)@ AP Q  #KK=djj\JKmvL vLF MGvL vLsM   /BV$>V$	V$E	V$"BV$V$"V0V4B>V$;EV$V$$V-c                 p    t        | j                        }t        t        j                  |dd             y)u0   현재 체인 상태를 JSON으로 출력한다.FrA   rB   N)r>   r|   r6   r:   dumps)rk   r?   s     r   
cmd_statusr     s%    tzz"D	$**Ta
89r   c                    t         j                  dd       t        t         j                  d            }|st	        d       yg }|D ]  }	 t        |dd      5 }t        j                  |      }ddd       |j                  j                  d	      |j                  d
      |j                  d      |j                  d      t        |j                  dg             |j                  d      d        t	        t        j"                  |dd             y# 1 sw Y   xY w# t        j                  t        f$ rM}t        j                  d| d|        |j                  |j                  t!        |      d       Y d}~=d}~ww xY w)u,   모든 활성 체인 목록을 출력한다.Tr   z*.jsonu   활성 체인이 없습니다.Nr4   r   r   r+   ra   rb   rc   re   rd   )r+   ra   rb   rc   r   rd   u   체인 파일 읽기 실패: z, )r+   r   FrA   rB   )r-   r"   sortedglobr6   r#   r:   r;   rz   r{   r}   ry   OSErrorrS   rT   stemr   r   )rk   chain_filesr`   r<   r=   r?   r   s          r   cmd_listr     sO   TD123K./F D	DdC'2 $ayy|$MM $ 4#'88M#:"hhx0)-2E)F$'2(>$?"&((<"8		D$ 
$**V%
:;!$ $ $$g. 	DNN:4&1#FGMMtyy3q6BCC	Ds2   D$D0BD$D!	D$$F
=AFF
c                  z   ddl m}   |         t        j                  dt        j                        }|j                  dd      }|j                  dd	
      }|j                  ddd       |j                  ddd       |j                  dd
      }|j                  ddd       |j                  ddd       |j                  ddd       |j                  dd
      }|j                  ddd       |j                  ddd       |j                  dd
      }|j                  ddd       |j                  dd
       |j                         }t        t        t        t        t        d }|j                  |j                        }	|	r	 |	|       y |j!                          t#        j$                  d!       y )"Nr   )load_env_keysu    Phase 자동 체이닝 시스템)ra   formatter_classcommandT)destrequiredcreateu   빈 체인 파일 생성)helpz--idu   체인 ID (예: insuwiki-p1p2))r   r   z--descu   체인 설명	add-phaseu   체인에 Phase 추가r   u	   체인 IDz--nameu   Phase 이름 (예: Phase 1)z--tasksuM   tasks JSON 배열 (예: [{"team":"dev1-team","desc":"...","level":"normal"}])	task-doneu*   특정 task 완료 마킹 및 Phase 전환z--tasku   완료할 task_idrb   u   체인 상태 조회listu   모든 체인 목록 조회)r   r   r   rb   r   r3   )utils.env_loaderr   argparseArgumentParserRawDescriptionHelpFormatteradd_subparsers
add_parseradd_argument
parse_argsrl   r   r   r   r   r{   r   
print_helpr7   r9   )
r   parser
subparsersp_createp_addp_donep_statusrk   dispatch_tablehandlers
             r   mainr     s   .O$$6 <<F &&I&EJ $$X4N$OH&46VW(TH !!+4L!ME	y4kB	x$5RS	D'v  
 "";5a"bF
	D{C
46IJ $$X4J$KH)dE &'DED ""N   .Gr   __main__)rw   )10s)r   N)2__doc__r   r$   r:   r   rer   rV   r7   
contextlibr   r   pathlibr   utils.loggerr   ImportErrorr<   insertr   __file__r!   __name__rS   r   r{   r   r-   rU   rR   r   r   compiler   r   r*   r.   dictr>   rI   r^   rl   r   intr   r   r   r   r   r   r   r   r   <module>r      s  &    	 	   
 %  ('
 
H	  02GHI	!H,

**..+\
:
**..+R
0-'{" bjj/0S c c  	$ 	c 	 	"+# +$ +# $   < <S <T <B-@,ghT$ T3 T# T$t* Tn}LJ:<H4n zF K  (HHOOAs4>0012''(s   D2 2=E32E3