
    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ddlZddl	m	Z	m
Z
 ddlmZ ddlmZmZmZ 	 ej"                  j%                  d e ee      j+                         j,                  j,                               ddlmZ  e        	 ddlmZ 	 ddlmZ  ej<                         ZdZ 	 ddl!m"Z" ddl!m#Z$ ddl%m&Z& ddl(m)Z)m*Z*  e&e+      Z,	 ddl-m.Z/ dZ0	 ddl1m2Z3 dZ4	 ddl5m6Z7 dZ8	 ddl9m:Z; dZ<	 ddl=m>Z? dZ@	 ddlAmBZC dZD	 ddlEmFZG dZH	 ddlImJZK dZL	 ddlMmNZO  eOj                  d e ee      j,                  dz  d z              ZQ eOj                  eQ      ZSeQj                  j                  eS       eSj                  ZWdZXe rerej                  d!      nd"ZZ eej                  j                  d#eZ            Z]e]d$z  d%z  Z^e]d$z  d&z  Z_e rerej                  d'      nd(Zaej                  j                  d)ea      Zbd*Zc ej                         d+z  d,z  d-z  d$z  Zeej                  j                  d.      ej                  j                  d/      ej                  j                  d0      ej                  j                  d1      ej                  j                  d2      ej                  j                  d3      ej                  j                  d4      ej                  j                  d5      ej                  j                  d6      d7	Zf	 dd8lgmhZhmiZimjZj  ei       Zk eh       Zl ej       ZmdMdNdOdPdQdOdRdSdOdTdUdOdVZrh dWZsemj                         D ci c]  \  }}||
 c}}ZtddXedz  depeepeef   f   fdYZuddZedz  defd[Zvd\epeepeef   f   dewepeef      fd]Zx	 dd^edZedz  defd_Zyebfd'edepeef   fd`Zzebfdaedbed'ededz  fdcZ{ddaedbedde|ddfdeZ}dfedepfdgZ~ddhZdiedjeddfdkZdiedledefdmZdnedepdz  fdoZdpedepdz  fdqZ	 ddiedrepdsedtedz  def
duZdnedewfdvZdwewdxedewfdyZdwewdefdzZdwewd{edewfd|Zd}ewddfd~Zdnede|fdZh dZdnedxedefdZdnedxedefdZdwewdepfdZdedepfdZh dZg dZdnedwewdefdZdnede|fdZddiednedededee   f
dZdiede|ddfdZdepfdZdedefdZdde|defdZdefdZdede|fdZdefdZdieddfdZddedfednededepf
dZdnedee   defdZ	 	 	 	 ddfednediededee   dee   dedefdZ#dedfedieddfdZdieddfdZdneddfdZdnediededdfdZdnedee   fdZdfedee   fdZddwewde|dewfdZdnedee   fdZdnededdfdZdfedneddfdZddiednedededdf
dZdepfdZdnedee   fdZddfednededee   fdZdedee   fdZ	 	 	 	 ddee   dnedediee   dedbee   depfdZdedewep   fdZdededewep   fdZdedfedepfdZ	 ddededfediede|deep   fdZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddfee   dnededeee      dee   dee   dee   dedediee   dee|   dedee   dedbee   dedee   dededreep   dedepf,dǄZdiedepfdȄZddɄZe+dk(  r e        yy# e$ r Y 'w xY w# e$ r ded	ed
eddfdZY 8w xY w# e$ r dZdZ Y .w xY w# e'$ r ddl!m"Z" ddl!m#Z$ ddl%m&Z& Y 9w xY w# e'$ r dedefdZ/dZ0Y 6w xY w# e'$ r dZ3dZ4Y =w xY w# e'$ r dZ7dZ8Y Dw xY w# e'$ r dZ;dZ<Y Kw xY w# e'$ r dZ?dZ@Y Rw xY w# e'$ r dZCdZDY Yw xY w# e$ r dZGdZHY `w xY w# e'$ r dZKdZLY gw xY w# e$ r dZWdZXY w xY w# e'$ r e rceraej                  d9      Znej                  d:      Zo epen      Zk epeo      Zmeoj                         D  ci c]  \  } }|en|     nc c}} w c}} Zln!d;d<d=d>d?d@dAdBdCZkd;d<d=d>d?d@dAdBdDZldEdFdGdHdIdJdKdLdCZmY w xY wc c}}w )uf  dispatch 패키지 — 작업 위임 디스패처 (task-2388 Phase ε 분리).

dispatch.py 4336줄을 패키지로 변환. __init__.py가 코드 본체를 포함하고,
하위 모듈(task_id, retry, prompt, audit, core)은 facade로 영역별 함수만 노출하여
단위 테스트 정밀화 가능. 외부 호출자 호환 100% 유지:
- `python3 dispatch.py --team ...` (호환 shim)
- `from dispatch import build_prompt, ...` (이 파일에서 모두 노출)
- `mock.patch("dispatch.subprocess.run", ...)` 등 모든 mock 경로 동작

분리 모듈 (facade):
- dispatch.task_id  : task-2380 4-layer fix 영역
- dispatch.retry    : task-2387 status 가드 영역
- dispatch.prompt   : task-2386 슬림 prompt 영역
- dispatch.audit    : 자원 검증 + 봇 풀 + 팀 라우팅 영역
- dispatch.core     : dispatch + cancel + main + composite + PRD 영역
- dispatch._state   : 모듈 상수/optional imports

Usage:
    python3 dispatch.py --team dev1-team --task-file /path/to/task-desc.md [--level normal|critical|security]
    python3 dispatch.py --team dev2-team --task "간단한 버그 수정" --level critical
    N)datetime	timedelta)Path)AnyListOptionalload_env_keys)atomic_json_writepathdata_kwreturnc                 F   ddl }t        |       }|j                  j                  dd       |j	                  t        |j                        d      \  }}	 t        j                  |dd	      5 }t        j                  ||d
d       |j                          t        j                  |j                                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)u@   fallback: utils.atomic_write 로드 실패 시 인라인 구현.r   NTparentsexist_okz.tmp)dirsuffixwutf-8encoding   F)indentensure_ascii)tempfiler   parentmkdirmkstempstrosfdopenjsondumpflushfsyncfilenoreplaceBaseExceptionunlinkOSError)r   r   r   _tftargetfdtmp_pathfs           B/home/jay/workspace/.worktrees/task-2460-dev6/dispatch/__init__.pyr   r   3   s    dD48{{s6=='9&{IH	2sW5 %		$!%@	$% JJxV-	% %
  			(#   	sI   C0 /AC$<'C0 $C-)C0 0	D :DD 	DD DD )ConfigManagerTF)	TEAM_INFO)build_prompt)
get_logger)COMPOSITE_ALLOWED_TEAMSMAX_COMPOSITE_TEAMS)redact_sensitive_texttextc                     | S N )r:   s    r2   _redact_textr>   j   s        )scan_content)check_command)log_file_operation)route_model)should_sanitize)BotStatusManager)SessionResilienceimage_skill_routertoolszimage-skill-router.pyzroots.workspacez/home/jay/workspaceWORKSPACE_ROOTmemoryzorganization-structure.jsonztask-timer.pychat_id
6937032012COKACDIR_CHAT_IDi   z.claudeprojectsz%-home-jay--cokacdir-workspace-autosetCOKACDIR_KEY_ANUCOKACDIR_KEY_DEV1COKACDIR_KEY_DEV2COKACDIR_KEY_DEV3COKACDIR_KEY_DEV4COKACDIR_KEY_DEV5COKACDIR_KEY_DEV6COKACDIR_KEY_DEV7COKACDIR_KEY_DEV8)	anudev1dev2dev3dev4dev5dev6dev7dev8)build_bot_to_key_mapbuild_team_bot_mapbuild_team_to_bot_id_mapteamsteam_to_botrY   rZ   r[   r\   r]   r^   r_   r`   )	dev1-team	dev2-team	dev3-team	dev4-team	dev5-team	dev6-team	dev7-team	dev8-teambot-bbot-cbot-dbot-ebot-fbot-gbot-hbot-iro   rp   rq   rr   rs   rt   ru   rv   u   마아트 (Ma'at)u   QC 매니저)nameroleu   로키 (Loki)u   레드팀 리더u   비너스 (Venus)u   디자인 디렉터u   야누스 (Janus)DevOps)qcredteamdesigndevops>   r|   content	marketing
consulting
publishingexclude_task_idc                    t         r&t         t        t              j                  |       S t        dz  dz  }i }|j	                         s|S 	 t        |dd      5 }t        j                  |      }ddd       j                  d	i       j                         D ]i  \  }}|j                  d
      dk7  r| r|| k(  r#|j                  dd      }|t        v rt        |   }||d||<   |j                  d      }	|	sb||d||	<   k 	 |S # 1 sw Y   xY w# t        j                  t        f$ r#}
t        j                  d|
        Y d}
~
|S d}
~
ww xY w)u   task-timers.json에서 running 상태 봇의 점유 정보 반환.

    Args:
        exclude_task_id: 이 task_id의 엔트리는 결과에서 제외 (자기 자신 제외용)

    Returns:
        {bot_id: {"task_id": ..., "team_id": ...}} 매핑
    Nworkspace_rootr   rJ   task-timers.jsonrr   r   tasksstatusrunningteam_id )task_idr   botu4   task-timers.json 읽기 실패 (봇 점유 정보): )_BOT_STATUS_AVAILABLE_BotStatusManager	WORKSPACEget_busy_botsexistsopenr$   loadgetitemsTEAM_TO_BOT_IDJSONDecodeErrorr,   loggerwarning)r   
timer_filebusyr1   r   r   
task_entryr   r   	bot_fieldes              r2   _get_busy_bots_infor     s`    !2!> 	:HHYhHii X%(::J&(DS*cG4 	 99Q<D	 #'88GR#8#>#>#@ 	KGZ~~h'947o#= nnY3G.($W-(/GDS	"u-I.5'"JY	K" K'	  	     '* SMaSQRRKSs7   D D	2B	D <
D 	DD E.EErequired_modelc           
         t        t               j                               }i }| rct               }dD ]T  }t        j                  |d      }t        j                  |d      }t        j                  |d      }|sH||v sM||   ||<   V dD ]P  }||v r| rD|j                  |d      | k7  r/t        j                  d| d|j                  |d       d|         N|c S  | rt        d|  d      t        d	      )
u  task-timers.json에서 running 상태 확인 후 가용 봇 반환.

    우선순위: bot-b > bot-c > bot-d > bot-e > bot-f > bot-g > bot-h > bot-i
    dev team running → 해당 봇 사용 중
    composite/marketing/design running → bot 필드로 판별

    Args:
        required_model: 필요한 모델명 (예: "claude-opus-4-6"). 지정 시 해당 모델의 봇만 선택.

    Returns:
        "bot-b" | "bot-c" | ... | "bot-i"
    Raises:
        RuntimeError: 조건에 맞는 가용 봇이 없을 때
    rn   r   [model-filter]     스킵: model=unknown    ≠    모델 조건(i   )에 맞는 가용 봇이 없습니다. 모든 opus 봇이 작업 중이거나 모델 미설정입니다.E   모든 봇이 작업 중입니다. 잠시 후 다시 시도하세요.)setr   keys_read_bot_modelsBOT_TO_DEFAULT_TEAMr   TEAM_BOTBOT_KEYSr   infoRuntimeError)	r   	busy_botsbot_model_map
key_modelsbot_iddefault_teamkey_namekey_hashr   s	            r2   _find_available_botr   <  s)    ')..01I %'M%'
^ 	=F.2262>L||L"5H||Hb1HH
2(28(<f%	= X )m//R8NJKK/#om>O>OPSU^>_=``efteuvw
 ^, -P Q
 	
 ^
__r?   r   c                 z    g d}g }|D ]/  }|| vs|j                  |t        j                  |d      d       1 |S )u  전체 봇 중 busy가 아닌 가용 봇 목록과 기본 팀 매핑을 반환.

    Args:
        busy_bots: _get_busy_bots_info()의 반환값 {bot_id: {"task_id": ..., "team_id": ...}}

    Returns:
        [{"bot_id": "bot-c", "default_team": "dev2-team"}, ...]
    rn   r   )r   r   )appendr   r   )r   all_bots	availabler   s       r2   _get_available_bots_with_teamsr   h  sT     XHI i!$7$;$;C$K r?   timer_task_idc           
      &   t         dz  dz  }t         dz  dz  }|j                  j                  dd       d}	 t        |d      }t	        j
                  |t        j                         t               }|j                         r	 t        |dd	
      5 }t        j                  |      }ddd       j                  di       j                         D ]w  \  }}	|	j                  d      dk7  r|| k(  r!|	j                  dd      }
|
t        v r|j                  t        |
          |	j                  d      }|sg|j                  |       y 	 i }|rct'               }dD ]T  }t(        j                  |d      }t*        j                  |d      }t,        j                  |d      }|sH||v sM||   ||<   V d}dD ]P  }||v r|rD|j                  |d      |k7  r/t"        j/                  d| d|j                  |d       d|        N|} n ||rt1        d| d      t1        d      |j                         r	 t        |dd	
      5 }t        j                  |      }ddd       j                  di       j                  |       }|rQ||d<   t        |dd	
      5 }t        j2                  ||dd       ddd       t"        j/                  d|  d| d       ||6	 t	        j
                  |t        j4                         |j7                          S S # 1 sw Y   lxY w# t        j                  t         f$ r#}t"        j%                  d|        Y d}~d}~ww xY w# 1 sw Y   xY w# 1 sw Y   xY w# t        j                  t         f$ r%}t"        j%                  d |  d!|        Y d}~d}~ww xY w# t8        $ r Y S w xY w# |E	 t	        j
                  |t        j4                         |j7                          w # t8        $ r Y w w xY ww xY w)"u   봇 선택과 타이머 기록을 원자적으로 수행.

    파일 락을 잡은 상태에서:
    1. busy bots 확인
    2. 가용 봇 선택
    3. task-timers.json에 bot 필드 즉시 기록

    이렇게 해야 동시 dispatch 간 봇 충돌을 방지할 수 있음.

    Args:
        timer_task_id: 봇을 예약할 태스크 ID
        required_model: 필요한 모델명 (예: "claude-opus-4-6")

    Returns:
        선택된 봇 ID (예: "bot-b")
    Raises:
        RuntimeError: 조건에 맞는 가용 봇이 없을 때
    rJ   r   .task-timers.lockTr   Nr   r   r   r   r   r   r   r   r   r   u-   task-timers.json 읽기 실패 (봇 예약): rn   r   r   r   r   r   r   r   Fr   r   r   u   [봇 예약]     → u    (원자적 예약 완료)u   봇 예약 기록 실패 (): )r   r   r   r   fcntlflockLOCK_EXr   r   r$   r   r   r   r   addr   r,   r   r   r   r   r   r   r   r   r%   LOCK_UNclose	Exception)r   r   r   lock_file_pathlock_fdr   r1   r   tidentryteamr   r   r   r   r   r   r   r   selected_botr   r   s                         r2   _select_and_reserve_botr   ~  s   , X%(::J),??Nt<GK~s+GU]]+ "e	T*cG< (99Q<D("&((7B"7"="="? 
1JCyy*i7 m+  99Y3D~-!nT&:; %		% 0I !i0
1 )+)+Jb A266vrB#<<b9#<<"5J 6,6x,@M&)A $([ 	Ci-"3"3C"<"NocU/-BSBSTWYbBcAddijxiyz{L	 "$^$4 5X Y  fgg 
S*cG< (99Q<D(!XXgr266}E
(4Ju%j#@ IA		$aHIKK-eL>Qk lm GU]]3 }( ( (('2 T!NqcRSSTH( (
I I (('2 S!;M?#aSQRRS  	 GU]]3 	 s   A
O L L(BL 7L 
AO #O (BO 8M7 M>M7 M+4$M7 O 4N8LL M8MO MO M(#M7 +M40M7 7N5N0+O 0N55O 8	OOP4P ?P 	P	PPPc                    t        j                         dz  dz  }|j                         si S 	 t        |dd      5 }t	        j
                  |      }ddd       i }j                         D ]:  \  }}|j                  di       }|j                  t        |       d      }|s6|||<   < |S # 1 sw Y   ZxY w# t        $ r$}	t        j                  d	|	        i cY d}	~	S d}	~	ww xY w)
u   bot_settings.json에서 봇 키 해시별 모델 설정을 읽어 반환.

    Returns:
        {key_hash: model_name} 예: {"c38fb9955616e24d": "claude-opus-4-6"}
    	.cokacdirbot_settings.jsonr   r   r   Nmodelsr   u.   [bot_models] bot_settings.json 읽기 실패: )r   homer   r   r$   r   r   r   r!   r   r   debug)
rK   settings_pathr1   settingsresultr   cfgr   modelr   s
             r2   r   r     s     IIK+-0CCM!	-w7 	$1yy|H	$%^^- 	)MHcWWXr*FJJs7|R0E#(x 		)
 	$ 	$  EaSIJ	s;   B7 B+AB7 "B7 +B40B7 7	C$ CC$C$r   r   c                    t        j                         dz  dz  }|j                         st        j	                  d       y	 t        |dd      5 }t        j                  |      }ddd       j                  |       }|st        j	                  d|         y|j                  d	i       }|j                  t        |            }||t        |      <   ||d	<   ||| <   t        |d
d      5 }t        j                  ||dd       ddd       t        j                  d|  d| d|        |S # 1 sw Y   xY w# 1 sw Y   5xY w# t        $ r"}	t        j                  d|	        Y d}	~	yd}	~	ww xY w)u"  bot_settings.json에서 특정 봇의 모델을 변경한다.

    Args:
        key_hash: 봇 키 해시 (예: "c38fb9955616e24d")
        model: 설정할 모델명 (예: "claude-opus-4-6")
        chat_id: 채팅 ID

    Returns:
        이전 모델명 또는 None (변경 실패)
    r   r   u)   [model-override] bot_settings.json 없음Nr   r   r   u(   [model-override] 봇 설정 없음: key=r   r   Fr   r   u(   [model-override] 봇 모델 변경: key=, r   u+   [model-override] 봇 모델 변경 실패: )r   r   r   r   r   r   r$   r   r   r!   r%   r   r   error)
r   r   rK   r   r1   r   bot_cfgr   
prev_modelr   s
             r2   _set_bot_modelr     sa    IIK+-0CCM!BC-w7 	$1yy|H	$,,x(NNEhZPQXr*ZZG-
$s7|"$-w7 	A1IIha@	A>xj:,V[\a[bcd	$ 	$	A 	A  B1#FGsO   E D.&3E AE ,D:'E .D73E :E?E 	E1E,,E1delayc                     d|  dt          d| d}t        j                  ddd| d| gd	t        j                  t        j                  
       t        j                  d| d|  d|        y)u   delay초 후 봇 모델을 복원하는 백그라운드 프로세스를 포크한다.

    세션이 시작된 후 모델을 원래대로 복원하여 다음 일반 작업에서 Sonnet이 사용되도록 한다.
    z}import json, pathlib; p=pathlib.Path.home()/'.cokacdir'/'bot_settings.json'; d=json.loads(p.read_text(encoding='utf-8')); d['z']['models']['z']='zK'; p.write_text(json.dumps(d,ensure_ascii=False,indent=2),encoding='utf-8')python3z-czimport time; time.sleep(z); T)start_new_sessionstdoutstderrz[model-override] u"   초 후 모델 복원 예약: key=r   N)CHAT_ID
subprocessPopenDEVNULLr   r   )r   r   r   restore_scripts       r2   _schedule_model_restorer   #  s    	 Z~gYd5' :S	T  	D4UG3~>NOP!!!!	 KK#E7*LXJV[\a[bcdr?   r   c           	         ddd| d}	 d}t         j                         rt        t         dd      5 }t        j                  |      }ddd       j                  di       j                  d	i       j                  d
g       }|D ]  }|j                  d      | k(  r&|j                  di       }|j                  dd      } nW|j                  dg       D ]<  }|j                  d      | k(  s|j                  di       }|j                  dd      } n |s n d}	t        j                  |       }
|
rt        j                  |
      }|rt        j                         dz  dz  }|j                         rmt        |dd      5 }t        j                  |      }ddd       j                  |i       }|j                  di       j                  t        t              d      }	||d<   |	|d<   |r-|	r+||	k7  r&d|d<   t        j                  d|  d| d|	 d       |S t        j                  d|  d|xs d d|	xs d d       	 |S # 1 sw Y   xY w# 1 sw Y   xY w# t        $ r#}t        j                  d |        Y d}~|S d}~ww xY w)!u  org-structure.json과 bot_settings.json의 모델 설정을 비교 검증한다.

    org-structure.json에서 해당 team_id의 lead.model 값과
    bot_settings.json에서 해당 봇의 models[chat_id] 값을 비교하여
    불일치 시 WARNING 로그를 출력한다.
    디스패치는 bot_settings.json 기준을 유지한다.

    Args:
        team_id: 검증할 팀 ID (예: "dev8-team")

    Returns:
        dict: {"consistent": bool, "org_model": str, "bot_model": str, "team_id": str}
    Tr   )
consistent	org_model	bot_modelr   r   r   r   N	structurecolumnsrd   r   leadr   	sub_teamssub_team_idr   r   r   r   r   Fr   u   [모델 불일치] z: org-structure=z, bot_settings=u/    — 디스패치는 bot_settings 기준 유지u   [모델 일관성] z: org=N/A, bot=u     — 일치 또는 확인 불가u(   [모델 검증] 검증 실패 (무시): )ORG_FILEr   r   r$   r   r   r   r   r   r   r!   r   r   r   r   r   )r   r   r   r1   org_datard   r   r   subr   r   r   r   r   r   r   s                   r2   _validate_model_consistencyr   8  s    #'RbU\]F1G	??hg6 (!99Q<(LLb155iDHHRTUE 88I&'188FB/D $" 5I88K4 Cww}-8"wwvr2$(HHWb$9		
   	<<(||H-H $		k 9<O O '')mS7C 0q#'99Q<0&ll8R8G 'Hb 9 = =c'lB OI'{'{yI'=#(F< NN%gY.>yk J  ){*Y[ M KK%gYfY5G%4H
S\Se`eRf  gG  H M_( (40 0$  GA!EFFMGs[   (I IB"I *)I A+I ?IB I 'I II II 	J#JJc                  n   ddl } 	 t        j                         dz  dz  }|j                         st        j                  d       yt        |dd      5 }t        j                  |      }ddd       | j                        }|j                         D ]  \  }}d	|v sd
|d	<    t        dz  dz  }|j                  j                  dd       t        |dd      5 }t        j                  ||dd       ddd       t        j                  d|        	 t        j                         dz  dz  }|j                         syt        |dd      5 }t        j                  |      }ddd       t        dz  dz  }	t        |	dd      5 }t        j                  |      }
ddd       
j%                  dd      }j                         D ]R  \  }}|j%                  dd      }|j%                  dd      }|j%                  di       }|j%                  di       }|r|sT|j'                  d      }t)        |      dk\  r0|d   j+                  d      r|d   dd j-                         r|d   }nd }|r
||v r||   }n'|r#t/        t1        |j3                                     }nd}|r
||v r||   }n'|r#t/        t1        |j3                                     }nd}||||d!|
j5                  d"i       |<   |d k7  s,| d#}|
j5                  d$i       j5                  ||       U t7        j8                         j;                  d%      |
j5                  d&i       d'<   t        |	dd      5 }t        j                  |
|dd       ddd       t        j                  d(       y# 1 sw Y   2xY w# 1 sw Y   xY w# t         $ r#}t        j#                  d|        Y d}~d}~ww xY w# 1 sw Y   xY w# 1 sw Y   TxY w# 1 sw Y   xY w# t         $ r"}t        j#                  d)|        Y d}~yd}~ww xY w)*uY  bot_settings.json의 민감 정보(token)를 마스킹한 사본을 workspace에 저장한다.

    원본의 "token" 키 값을 "***REDACTED***"로 교체한 사본을
    {WORKSPACE}/memory/bot_settings_sync.json에 저장한다.
    기타 필드는 그대로 유지된다.
    실패해도 디스패치 자체는 중단되지 않는다.
    r   Nr   r   u>   [bot_settings_sync] bot_settings.json 없음, 동기화 스킵r   r   r   tokenz***REDACTED***rJ   zbot_settings_sync.jsonTr   r   Fr   r   u&   [bot_settings_sync] 동기화 완료: u/   [bot_settings_sync] 동기화 실패 (무시): configconstants.jsonrK   r   display_nameusernamer   last_sessions_   devrX   )r  r  team_dirr   bots-teamrd   z%Y-%m-%dmetalast_updatedu;   [bot_settings_sync] constants.json 봇 정보 반영 완료uA   [bot_settings_sync] constants.json 업데이트 실패 (무시): )copyr   r   r   r   r   r   r$   r   deepcopyr   r   r   r   r%   r   r   r   r   splitlen
startswithisdigitnextitervalues
setdefaultr   nowstrftime)r  r   r1   r   maskedr   r   	sync_pathr   constants_path	constantschat_id_key	_key_hashr  r  r   r  partsbot_short_idr   r  r   s                         r2   _sync_bot_settingsr$  }  s    N		k14GG##%LLYZ-w7 	$1yy|H	$ x(#\\^ 	0MHc#~/G	0 (+CC	td;)S73 	?qIIfaeA>	? 	<YKHI
C`		k14GG##%-w7 	$1yy|H	$ #X-0@@.#8 	%A		!I	%  mmIr2&nn. +	TNIs # ;LGGJ3H778R0F"%''/2">Mx !&&s+E5zQ58#6#6u#=%(12,BVBVBX$Qx  % {f4#K0T&--/23{m; -k :]%9%9%; <= !-$$	>I  ,\: u$)N%0$$Wb1<<WlSW+	T\ <D<<>;R;RS];^	VR(8.#8 	BAIIiqA	B 	QRi	$ 	$	? 	?  NHLMMN	$ 	$	% 	%j	B 	B  `Z[\Z]^__`s   ?N4 N4 N*3N4 >N4 N'6 N4 *P	 P	 O#&"P	 O0EP	 4A.P	 "O=<P	 N$N4 'N1,N4 4	O =OO #O-(P	 0O:5P	 =PP	 		P4P//P4r   metadatac                 4   t         dz  dz  }t         dz  dz  }|j                  j                  dd       d}	 t        |d      }t	        j
                  |t        j                         |j                         s:	 |6	 t	        j
                  |t        j                         |j                          yyt        |dd	
      5 }t        j                  |      }ddd       j                  di       j                  |       }|r8|j                  |       t        ||       t         j#                  d|  d|        |6	 t	        j
                  |t        j                         |j                          yy# t        $ r Y yw xY w# 1 sw Y   xY w# t        $ r%}t         j%                  d|  d|        Y d}~}d}~ww xY w# t        $ r Y yw xY w# |E	 t	        j
                  |t        j                         |j                          w # t        $ r Y w w xY ww xY w)uI   task-timers.json에서 지정된 task_id 항목에 메타데이터 추가rJ   r   r   Tr   Nr   r   r   r   r   u   메타데이터 패치 완료: r   u   메타데이터 패치 실패 (r   )r   r   r   r   r   r   r   r   r   r   r   r$   r   r   updater   r   r   r   )	r   r%  r   r   r   r1   r   r   r   s	            r2   _patch_timer_metadatar(    s   X%(::J),??Nt<G~s+GU]]+  " GU]]3  *cG4 	 99Q<D	 XXgr*..w7
h'j$/KK9'%zRS GU]]3   	  	   J8	QCHIIJ  	 GU]]3 	 s   A F =4E3 3F FA#F =4F? 3	E?>E?FF 	F<F72G 7F<<G ?	G
GH4HH	HHHH
new_statusc                 r   t         dz  dz  }	 t        |d      5 }t        j                  |      }ddd       j                  di       j                  | i       j                  d      }|d	v rt        j                  |  d
| d| d       yy# 1 sw Y   ^xY w# t        t        j
                  f$ r Y yw xY w)uM  task-timers.json의 status를 변경하되, archived/escalated는 영구 박제하여 변경을 차단한다.

    Returns:
        True: 변경 가능(archived/escalated 아님). 호출자가 실제 변경을 수행해야 함.
        False: 변경 차단됨(archived/escalated). 호출자는 후속 작업을 중단해야 함.
    rJ   r   r   r   NTr   r   )archived	escalatedz	: status=u    영구 박제 — 'u   ' 갱신 차단F)	r   r   r$   r   r,   r   r   r   r   )r   r)  timers_filer1   
timer_datacurrents         r2   _set_task_statusr0    s     h&);;K+0 	&A1J	& nnWb)--gr:>>xHG++wiy	1Ej\Q`ab	& 	&T))* s'   B BB BB B65B6	task_descc                    t        j                  d| t         j                        }|D ]E  }d|vr	 ddl}|j	                  |      }t        |t              rd|v r|d   c S 	 t        |      c S  y# t        $ r Y w xY w# t        $ r Y cw xY w)uN  task_desc 안의 fenced yaml block에서 allowed_resources를 파싱한다.

    ```yaml
    allowed_resources:
      paths: [...]
      forbidden_paths: [...]
      commands: [...]
      merge_policy: manual
      ttl_hours: 48
    ```
    형태의 블록을 찾아 dict로 반환한다.
    못 찾으면 None을 반환한다.
    z```yaml\s*\n(.*?)```allowed_resources:r   Nallowed_resources)	refindallDOTALLyaml	safe_load
isinstancedictr   _parse_allowed_resources_regex)r1  fenced_blocksblockr8  r   s        r2   _parse_allowed_resourcesr?    s     JJ6	299MM u,	>>%(D$%*=*E/00	1%88    		
  		s#   -A1"
B 1	A=<A= 	BB
yaml_blockc                 p   d| vryi }t        j                  d|       }|rs|j                  d      j                         D cg c]F  }|j	                         j                  d      r%t        j                  dd|      j	                         H c}|d<   n}t        j                  d	|       }|r`|j                  d      j                  d
      D cg c]2  }|j	                         s|j	                         j	                  d      4 c}|d<   ng |d<   t        j                  d|       }|rs|j                  d      j                         D cg c]F  }|j	                         j                  d      r%t        j                  dd|      j	                         H c}|d<   n}t        j                  d|       }|r`|j                  d      j                  d
      D cg c]2  }|j	                         s|j	                         j	                  d      4 c}|d<   ng |d<   t        j                  d|       }|rs|j                  d      j                         D cg c]F  }|j	                         j                  d      r%t        j                  dd|      j	                         H c}|d<   n}t        j                  d|       }|r`|j                  d      j                  d
      D cg c]2  }|j	                         s|j	                         j	                  d      4 c}|d<   ng |d<   t        j                  d|       }|r|j                  d      nd|d<   t        j                  d|       }	|	rt        |	j                  d            nd|d<   |j                  d      |S dS c c}w c c}w c c}w c c}w c c}w c c}w )uR   yaml 파서 없이 정규식으로 allowed_resources 키를 추출하는 fallback.r3  Nzpaths:\s*\n((?:\s*-\s*.+\n?)*)   -z^\s*-\s*["\']?|["\']?\s*$r   pathszpaths:\s*\[([^\]]*)\],z"'z(forbidden_paths:\s*\n((?:\s*-\s*.+\n?)*)forbidden_pathszforbidden_paths:\s*\[([^\]]*)\]z!commands:\s*\n((?:\s*-\s*.+\n?)*)commandszcommands:\s*\[([^\]]*)\]z!merge_policy:\s*["\']?(\w+)["\']?manualmerge_policyzttl_hours:\s*(\d+)   	ttl_hours)
r5  searchgroup
splitlinesstripr  r   r  intr   )
r@  r   paths_matchlineinline_matchpfp_match	cmd_matchmp_match	ttl_matchs
             r2   r<  r<  @  sB   :-F ))=zJK $))!,779
zz|&&s+ FF/T:@@B
w yy!9:F0<0B0B10E0K0KC0P+,TUT[T[T]	&F7O !F7O yyDjQH !q)446%
zz|&&s+ FF/T:@@B%
 ! yy!CZP0<0B0B10E0K0KC0P)+,TUT[T[T]	&)F$% )+F$% 		>
KI "*557
zz|&&s+ FF/T:@@B
z yy!<jI0<0B0B10E0K0KC0P"+,TUT[T[T]	&"F: "$F: yy=zJH2:X^^A.F> 		/<I5>#iooa01BF;ZZ(46>$>s
%
)
"s9   ANN#!N	AN$N)+!N)AN.N33!N3r4  source_textsource_pathc                    t        t              dz  dz  }|j                  dd       ||  dz  }t        |j	                  dg             }d|vr|j                  d       t        |      }||d<   | t        j                         j                         |xs d|  d	t        j                  |j                               j                         |d
}t        ||       |S )u   allowed_resources를 immutable snapshot으로 저장한다.

    저장 경로: memory/capabilities/{task_id}.json
    forbidden_paths에 memory/capabilities/** 자동 보강 (봇 변조 방지).
    반환: snapshot 파일 경로
    rJ   capabilitiesTr   .jsonrF  zmemory/capabilities/**memory/tasks/.md)r   captured_atsourcesource_sha256r4  )r   r   r   listr   r   r;  r   r  	isoformathashlibsha256encode	hexdigestr   )	r   r4  rY  rZ  snapshot_dirsnapshot_file	forbiddenenrichedsnapshot_datas	            r2   _save_capability_snapshotrn    s     	?X->Ltd3 gYe#44M &**+<bABIy012%&H"+H ||~//1==	!= (:(:(<=GGI%M m]3r?   c           
         | j                         }|D ]  }|j                         }|j                  d      s%|t        d      d j                         }|sg c S |j	                  d      D cg c]#  }|j                         s|j                         % c}c S  d}g }|D ]  }|j                         }|r|j                  d      r |S |s,|j                  d      r#|j                  |dd j                                `|sg|j	                  d      D cg c]8  }|j                         st        j                  dd	|j                               : }}|D 	cg c]  }	|	s|		 c}	c S  |S |d
k(  sd} |S c c}w c c}w c c}	w )u  task_desc 문자열에서 affected_files 정보를 찾아 파일명 리스트를 반환한다.

    세 가지 형식을 지원한다:
    - 인라인: 'affected_files: server.py, app.js'
    - 섹션 목록: '## affected_files' 헤더 이후 '- path' 줄들
    - 섹션 인라인: '## affected_files' 헤더 이후 쉼표 구분 값 (괄호 코멘트 자동 제거)

    인라인 → 섹션 목록 → 섹션 인라인 순서로 시도한다.
    없으면 빈 리스트를 반환한다.
    zaffected_files:NrE  Fz##- r   z\s*\(.*?\)\s*$r   ## affected_filesT)rN  rO  r  r  r  r   r5  r   )
r1  linesrR  strippedvaluer1   
collectingr   r   items
             r2   _parse_affected_filesrw    s      "E  F::<01S!2356<<>E	',{{3'7E!1779AGGIEEF JF ::<""4( M ""4(hqrl0023KS>>Z]K^labcbibibk 12qwwyAll).7$77 M ,,J!$ M/ F  m7s$   -E9E9E>,'E>F!Faffected_filesr   c                    | s| S t        |      dz  dz  }|j                         st        j                  d|        | S 	 dt	        |      d|dg| d}t        j                  |ddd	
      }|j                  dk7  rBt        j                  d|j                   d|j                  j                         dd         | S t        j                  |j                        }t        |       }g }dD ]C  }|j                  |g       D ],  }	|	s|	|vs|j                  |	       |j!                  |	       . E |r't        j#                  dt%        |       d       | |z   S | S # t
        j&                  $ r t        j                  d       | cY S t        j(                  $ r%}
t        j                  d|
 d       | cY d}
~
S d}
~
wt*        $ r%}
t        j                  d|
 d       | cY d}
~
S d}
~
ww xY w)u  AST 의존성 스크립트를 호출하여 blast radius를 계산하고 affected_files를 보강한다.

    scripts/ast_dependency_map.py를 subprocess로 호출하여 직접 임포터(direct_importers)와
    테스트 파일(test_files)을 추출한 뒤 기존 목록에 중복 없이 병합한다.
    스크립트 실패·타임아웃·JSON 파싱 오류 시 원래 목록을 그대로 반환한다 (graceful fallback).
    scriptszast_dependency_map.pyu:   [ast-blast-radius] AST 스크립트를 찾을 수 없음: r   --root--filesz--jsonT<   capture_outputr:   timeoutr   u5   [ast-blast-radius] 스크립트 비정상 종료 (rc=r   N   )direct_importers
test_filesu)   [ast-blast-radius] blast radius 보강: +u   개 파일 추가uE   [ast-blast-radius] 타임아웃(60s) — 원래 affected_files 사용u'   [ast-blast-radius] JSON 파싱 실패: u!    — 원래 affected_files 사용u"   [ast-blast-radius] 예외 발생: )r   r   r   r   r!   r   run
returncoder   rO  r$   loadsr   r   r   r   r   r   r  TimeoutExpiredr   r   )rx  r   
ast_scriptcmdr   r   existingextrakeyr1   r   s              r2   _enrich_affected_files_with_astr    s    n%	14KKJST^S_`a-
O
 
 
 	
 !NNGHYHYGZZ]bhbobobububwx|y|b}a~ "!zz&--(~&5 	$CXXc2& $(*LLOLLO$	$ KKCCJ<O`ab!E))$$ ^_ @Cdef ;A3>_`asQ   A=E ?AE E AE E *G-
G-F<6G-<G-G("G-(G-c                 ,    | syt        d | D              S )u  affected_files 중 InsuRo FastAPI 서버 코드를 건드리는 항목이 있는지 검사한다 (task-2339).

    매칭 규칙:
    - 'server/main.py' 부분 매치
    - 'server/'로 시작
    - '/projects/InsuRo/server' 절대 경로 부분 매치
    Fc              3      K   | ]<  }d t        |      v xs) t        |      j                  d      xs dt        |      v  > yw)zserver/main.pyzserver/z/projects/InsuRo/serverN)r!   r  ).0rT  s     r2   	<genexpr>z+_is_insuro_server_change.<locals>.<genexpr>%  sJ       
SV	#kA(9(9)(DkHaehijekHkks   AA)anyrx  s    r2   _is_insuro_server_changer    s$        r?   current_task_idc                 `   t         dz  dz  }|j                         sg S 	 t        |dd      5 }t        j                  |      }ddd       g }j                  di       }t        |       }|j                         D ]t  \  }	}
|	|k(  r|
j                  d	      d
k7  r!t        |
j                  dg             }||z  }|sDdj                  t        |            }|j                  d|	 d|        v |S # 1 sw Y   xY w# t
        $ r$}t        j                  d|        g cY d}~S d}~ww xY w)u  task-timers.json의 running 상태 task들과 affected_files 교집합을 확인한다.

    current_task_id는 제외(자기 자신).
    겹침 발견 시 경고 메시지 문자열 리스트를 반환한다.
    파일이 없으면 빈 리스트를 반환한다.
    rJ   r   r   r   r   Nu@   [_check_affected_files_overlap] task-timers.json 읽기 실패: r   r   r   rx  r   u   [파일 충돌 경고] u   (running)와 파일 겹침: )r   r   r   r$   r   r   r   r   r   r   r   joinsortedr   )rx  r  r   r1   r   r   warningsr   affected_setr   	task_infoother_filesoverlapoverlap_lists                 r2   _check_affected_files_overlapr  +  s9    X%(::J	*cG4 	 99Q<D	  HHHWb!E~&L#kkm 	ko%=="i/)--(8"=>,99VG_5LOO5gY>Z[gZhij	k O+	  	  YZ[Y\]^	s4   D  C4D  4C=9D   	D-	D("D-(D-r  c                 (   | syt         j                  j                  dd      }|st        j	                  d       yddj                  |       z   }	 ddl}d| d	}t        j                  t        |d
      j                  d      }|j                  j                  ||ddi      }|j                  j                  |d       t        j                  dt        |        d       y# t         $ r"}t        j	                  d|        Y d}~yd}~ww xY w)u^   겹침 경고를 Telegram으로 발송한다. 실패해도 dispatch를 중단하지 않는다.NANU_BOT_TOKENr   uB   [overlap-telegram] ANU_BOT_TOKEN 미설정, Telegram 경고 스킵u%   ⚠️ affected_files 겹침 감지:

r   zhttps://api.telegram.org/botz/sendMessage)rK   r:   r   zContent-Typezapplication/json)r   headers
   )r  u9   [overlap-telegram] 겹침 경고 Telegram 발송 완료 (u   건)u+   [overlap-telegram] Telegram 발송 실패: )r"   environr   r   r   r  urllib.requestr$   dumpsr   rg  requestRequesturlopenr   r  r   )r  	bot_tokenmessageurlliburlr   reqr   s           r2   _send_overlap_telegram_warningr  O  s    

3I[\689LLG	J,YK|Dzzgw?@GGPnn$$StnN`=a$bsB/OPST\P]^bcd JDQCHIIJs   BC& &	D/DD
task_levelc                 6    |dk  ryt        |       }|sd| dS y)u   task_level >= 2이고 affected_files가 없으면 경고 문자열을 반환한다.

    그 외에는 None을 반환한다.
    r   Nu   [경고] Lv.um    작업에 affected_files가 기재되지 않았습니다. 영향받는 파일 목록을 추가해 주세요.)rw  )r1  r  filess      r2   _warn_missing_affected_filesr  d  s8    
 A~!),E:, 'A A	
 r?   >	   jqcatnpmnpxtsccurlgreppytestr   c                 X   d| v r| S t        j                  d|       }h d}|D cg c]5  }|j                  d      j                         |vs%|j                  d      7 }}t	        t
        j                  |            dd }t               }|D ]  }	 t        j                  ddd	d
ddd||g	ddd      }|j                  j                         j                  d      D ]4  }|s|j                  t        j                  j!                  ||             6  t)        |      dkD  r$t$        j'                  dt)        |       d       | S |r)ddj+                  d t-        |      D              z   }	| |	z  } | S c c}w # t        j"                  $ r t$        j'                  d|        Y w xY w)uW   task_desc에 ## affected_files 미기재 시, 백틱 코드 토큰 기반 자동 탐지rq  z`([A-Za-z_]\w*(?:\(\))?)`>   idr  r   rv  rc  rw   r   typer   eventindexr   propsstatert  r  r   optionsz()Nr  r  z-rlz--include=*.pyz--include=*.tsz--include=*.tsxz--exclude-dir=node_modulesz--exclude-dir=.gitTr~  r  u#   [auto-affected] grep 타임아웃:    z[auto-affected] u*   개 파일 감지 (>20) — 주입 안 함z$

## affected_files (auto-detected)
c              3   &   K   | ]	  }d |   yw)rp  Nr=   )r  r1   s     r2   r  z._auto_inject_affected_files.<locals>.<genexpr>  s     GkUV"QCGks   )r5  r6  rstriplowerrc  r;  fromkeysr   r   r  r   rO  r  r   r"   r   relpathr  r   r   r  r  r  )
r1  r   tokensCOMMON_FILTERtaffectedr  r   rR  sections
             r2   _auto_inject_affected_filesr  {  s   i' ZZ4i@FGM '-\0D0D0Fm0[ahhtn\F\$--'("-F uH 	^^ 02BDU-/C(  $$	F ++-33D9 HLL~!FGH  8}r)#h-8bcd ;diiGkZ`aiZjGk>kkW	= ]  (( 	NN@HI	s$   &E5E5AE:#1E::+F)(F)c                     d| v r| S t        j                  d|       }|s| S g }|D ]/  }|j                         d   }|t        v s|j	                  |       1 |rd}|dd D ]  }|d| dz  } | |z  } | S )	uH   검증 시나리오에서 실행 가능한 goal_assertions 자동 생성z## goal_assertionsz:`((?:grep|curl|pytest|python3|tsc|cat|jq|npx|npm)\s[^`]+)`r   z&

## goal_assertions (auto-generated)
N   z- `z`
)r5  r6  r  ALLOWED_COMMANDSr   )r1  r   rG  safe_commandsr  
first_wordr  s          r2   _auto_generate_goal_assertionsr    s    y( zzWYbcH M &YY[^
))  %&
 = !$ 	&CSE~%G	&W	r?   c                    d}g }g }| D cg c]  }t        |      j                  d      s|! }}|sg g ddS |D ]  }t        |      }t        t        |      j                        }	t        |      j                  }
	 t        j                  d|d|	d|
gddd	
      }|j                  dk7  r6t        j                  d|
 d|j                   d|j                  dd         t        j                  |j                        }|j                  di       }|j                  |j                  dg              |j                  |j                  dg               t'        t(        j+                  |            }t'        t(        j+                  |            }||t-        |      t-        |      z   dS c c}w # t
        j                   $ r t        j                  d|
 d       Y t        j"                  $ r&}t        j                  d|
 d|        Y d}~d}~wt$        $ r&}t        j                  d|
 d|        Y d}~d}~ww xY w)u  affected_files의 .py 파일을 AST 분석하여 blast radius(영향 범위)를 반환한다.

    각 .py 파일에 대해 ast_dependency_map.py 스크립트를 호출하여
    direct_importers와 test_files를 수집한다.

    Args:
        affected_files: 영향받는 파일 경로 리스트
        workspace_root: 워크스페이스 루트 경로 (Path 또는 str)

    Returns:
        {
            "direct_importers": [...],  # 직접 임포터 목록 (중복 제거)
            "test_files": [...],        # 관련 테스트 파일 목록 (중복 제거)
            "total_affected": int,      # 총 영향 파일 수
        }
    z1/home/jay/workspace/scripts/ast_dependency_map.pyz.pyr   )r  r  total_affectedr   r{  r|  Tr}  r~  z[ast-blast-radius] u    분석 실패 (returncode=r   Nr  blast_radiusr  r  u)    분석 timeout(60s) 초과 — 건너뜀u    JSON 파싱 실패: u    예외 발생: )r!   endswithr   r   rw   r   r  r  r   r   r   r$   r  r   r   extendr  r   r   rc  r;  r  r  )rx  r   r  r  r  r1   py_filesfpath	fpath_strroot_dirfilenamer   r   blastr   s                  r2   _get_ast_blast_radiusr    s3   " EJJ)DaSV__U-CDHD$&bANN PJ	tI--.	?''	P^^J(IxP#	F   A%)(3NvO`O`Naadekererswtwexdyz ::fmm,DHH^R0E##EII.@"$EFeiib9:'P8 DMM*:;<dmmJ/0J - ./#j/A G E0 (( 	fNN0
:cde## 	UNN0
:OPQsSTT 	PNN0
:J1#NOO	Ps<   F/F/>A#F4"A3F44,I#I5HI"IIbatch_idc                    t         dz  dz  }|j                         sdddg dS 	 t        |dd      5 }t        j                  |      }d	d	d	       j                  di       }|j                         D ci c]  \  }}|j                  d      | k(  s|| }}}t        |      }	|	dk(  rdddg dS d}
g }|j                         D ]2  \  }}|j                  dd      }|dv r|
dz  }
"|j                  |       4 |
|	k(  }||	|
|dS # 1 sw Y   xY w# t
        $ r)}t        j                  d
|        dddg dcY d	}~S d	}~ww xY wc c}}w )u  task-timers.json에서 batch_id가 일치하는 모든 task의 완료 여부를 반환한다.

    Returns:
        {
            "complete": bool,   # total > 0이고 모두 done/completed인 경우 True
            "total": int,       # 해당 batch_id task 수
            "done": int,        # done 또는 completed 상태 수
            "pending": list[str],  # 미완료 task id 목록
        }
    rJ   r   Fr   )completetotaldonependingr   r   r   Nu9   [check_batch_completion] task-timers.json 읽기 실패: r   r  r   r   )r  	completedrB  )r   r   r   r$   r   r   r   r   r   r   r  r   )r  r   r1   r   r   r   r   r   matchedr  
done_countr  r   r  s                 r2   check_batch_completionr    sp    X%(::J!AqRHHI*cG4 	 99Q<D	  HHWb!E27++-d$488JCW[cCcw}dGdLEz!AqRHHJG  $(B'**!OJNN7#$ U"H 5*QXYY/	  	  IRSTRUVW!AqRHHI
 es@   D D	D 6EEDD 	EE :E E>
   app.pyasgi.pymain.pywsgi.py	celery.py	config.py	manage.py	server.pydispatch.pysettings.py)u   아키텍처u   리아키텍처u   전체 구조architecturec                     t         D ]  }|| v sdd| dfc S  |D ](  }t        |      j                  }|t        v s!dd| fc S  t	        |      }|dk\  rdd| dfS |dk\  rdd| d	fS y
)u  task_desc와 affected_files를 분석하여 권장 레벨과 이유를 반환한다.

    우선순위:
    1. 고레벨 키워드('아키텍처' 등) → (3, 이유)
    2. 핵심 파일 포함(server.py 등) → (2, 이유)
    3. affected_files >= 5 → (3, 이유)
    4. affected_files >= 3 → (2, 이유)
    5. 그 외 → (1, "")

    Returns:
        (level: int, reason: str)
    r	  'u   ' 키워드 감지r   u   핵심 파일 포함: r  zaffected_files u   개로 Lv.3 이상 권장u   개로 Lv.2 이상 권장)rB  r   )_HIGH_LEVEL_KEYWORDSr   rw   _CORE_FILESr  )r1  rx  keywordr1   r  ns         r2   _estimate_task_levelr  C  s     ( 8i7)#56778
  <7<<{"/z:;;< 	NAAv_QC'@ABBAv_QC'@ABBr?   c                 h    t        j                  d|       }|rt        |j                  d            S y)uu   task_desc에서 Lv.{숫자} 패턴을 찾아 정수로 반환한다.

    없으면 기본값 1을 반환한다.
    z	Lv\.(\d+)rB  )r5  rL  rP  rM  )r1  matchs     r2   _parse_task_levelr  e  s-    
 IIlI.E5;;q>""r?   levelskip_meetingc                    dddd}|j                  |d      }|dk  ryt        j                  dt        j                        }|j	                  |      sy|rt
        j                  d|         yt        dz  d	z  d
|  dz  }|j                         s-d|  d|j                   d}t
        j                  d|        |S 	 |j                  d      g d}	t        fd|	D              }
dv xs dj                         v }|
s|s,d|  d|j                   }t
        j                  d|        |S 	 y# t        $ r"}t
        j                  d|        Y d}~yd}~ww xY w)u  Lv.4 작업의 Agent 미팅 만장일치 결과 파일 존재 여부를 검증한다.

    Lv.4 판정: level이 'critical' 또는 'security'이고, task_desc에 Lv.4 패턴이 포함.
    미팅 파일 경로: memory/meetings/agent-meeting-{task_id}.md
    파일이 있으면 '만장일치' 또는 'unanimous' 키워드 포함 여부도 체크.
    부정 패턴('만장일치 실패', 'unanimous not', 'not unanimous')은 제외.

    Returns:
        WARNING 메시지 문자열, 또는 None (체크 통과/대상 아님)
    r   r	     normalcriticalsecurityNu%   (?:Lv\.4|레벨:\s*4|레벨:\s*Lv\.4)uB   [agent-meeting] --skip-meeting 플래그로 미팅 체크 스킵: rJ   meetingszagent-meeting-r_  u   ⚠️ Lv.4 작업(u1   )에 Agent 미팅 결과 파일이 없습니다: u,   . --skip-meeting 플래그로 스킵 가능.z[agent-meeting] r   r   )u   만장일치 실패zunanimous notznot unanimousu   만장일치하지c              3   &   K   | ]  }|v  
 y wr<   r=   )r  negr~   s     r2   r  z'_check_agent_meeting.<locals>.<genexpr>  s     Gc3'>G   u   만장일치	unanimousuE   ) 미팅 파일에 만장일치 합의가 확인되지 않습니다: u-   [agent-meeting] 미팅 파일 읽기 실패: )r   r5  compile
IGNORECASErL  r   r   r   r   rw   r   	read_textr  r  r,   )r   r1  r  r  _level_to_int_meetingdispatch_levellv4_patternmeeting_filemsgnegative_patternshas_negativehas_unanimousr   r~   s                @r2   _check_agent_meetingr  p  s    ()aQG*..ua8N **Er}}UKi( XY`Xabc x'*4yPS7TTL !'*[\h\m\m[n o9 : 	 	)#/0
L(('(:kG5FGG&'1S[GMMO5S}%gY.s  uA  uF  uF  tG  H  NN-cU34J  -   LFqcJKKLs   A1D6 6	E!?EE!zOptional[Path]c                    ddl }ddl}ddlm} |j	                  d|       st
        j                  d|        yt        dz  dz  dz  }|| z  }t        t        j                  j                  t        |                  }t        t        j                  j                  t        |                  }	 |j                  |       t        dz  dz  dz  }	g d}
|j                         j!                         }	 |j#                  ddd       |
D ]  \  }}||z  }|j)                         rt
        j+                  d|        4|	|z  }|j)                         st
        j                  d|        b	 |j-                  d      }|j/                  d|       j/                  d|      }|j1                  |d       t
        j+                  d|         |S # t        $ r t
        j                  d	| d
| d       Y yw xY w# t$        $ r%}t
        j'                  d| d|        Y d}~yd}~ww xY w# t$        $ r&}t
        j'                  d| d|        Y d}~Ld}~ww xY w)u  작업 3문서(계획서/맥락노트/체크리스트)를 자동 생성한다.

    템플릿 파일을 복사하고 YAML의 {task_id}, {date} 플레이스홀더를 치환한다.
    파일이 이미 존재하면 개별 파일 단위로 스킵(덮어쓰기 금지).

    Args:
        task_id: 작업 ID (예: task-1872, task-1872.1, task-1872_6.2)
        level:   작업 레벨 정수 (3 이상일 때만 호출됨)

    Returns:
        생성된 디렉토리 Path, 또는 검증 실패/에러 시 None
    r   N)datez^task-[\d._]+$u?   [3docs] task_id 검증 실패 (허용 패턴: ^task-[\d._]+$): rJ   plansr   u   [3docs] path traversal 감지: u    는 u    하위가 아님prompts	templatesz	task-docs))zplan.template.mdzplan.md)zcontext-notes.template.mdzcontext-notes.md)zchecklist.template.mdzchecklist.mdTi  )r   r   modeu$   [3docs] 디렉토리 생성 실패: : u   [3docs] 이미 존재, 스킵: u!   [3docs] 템플릿 파일 없음: r   r   z	{task_id}z{date}u   [3docs] 생성: u   [3docs] 파일 쓰기 실패: )r5  shutilr   r  r   r   r   r   r   r"   r   realpathr!   relative_to
ValueErrortodayrd  r   r,   r   r   r   r  r)   
write_text)r   r  _re_shutil_date
tasks_root
target_dirreal_target	real_roottemplate_dirr  r&  exc	tmpl_nameout_nameout_path	tmpl_pathr~   s                     r2   _create_task_docsr5    sV    & 99&0YZaYdef X%/'9Jg%Jrww''J89KRWW%%c*o67I	* y(;6DLI KKM##%E$EB
  ) M	8)??LL:8*EF 9,	!NN>ykJK	M))7);Gook7;CCHeTG':LL+H:67M" K  8U9+Ufgh   ;K=3%PQ$  	MLL9(2cUKLL	MsC   1G 4G; -AH,%G87G8;	H)H$$H),	I5IIc                  6   t         rt        	dddg g ddS t        t        t                    } | j	                         }t
        j                  d|j                  dd      t        |j                  dg             t        |j                  d	g                    |S )
u$  모든 running 세션의 토큰 사용량 체크 및 자동 대응.

    SessionResilience를 사용하여:
    - 70% 이상: WARNING 이벤트 기록
    - 85% 이상: CRITICAL 이벤트 + 세션 요약 저장 + resume 트리거

    Returns:
        check_all_sessions()의 결과 dict
    unavailableu7   SessionResilience 모듈을 사용할 수 없습니다.r   )r   r  checkedr  	criticalsnormalsr   u;   세션 체크 완료: checked=%d, warnings=%d, criticals=%dr8  r  r9  )	_SESSION_RESILIENCE_AVAILABLE_SessionResiliencer!   r   check_all_sessionsr   r   r   r  )
resiliencer   s     r2   check_sessionsr?    s     ),>,F#P
 	
 $3y>BJ**,F
KKE

9a FJJz2&'FJJ{B'(	 Mr?   bot_key_hashc                     | sy	 t        j                  ddd|  gddd      }|j                  dk(  S # t        $ r Y yw xY w)	uM  봇의 cokacdir 프로세스가 실행 중인지 확인.

    pgrep -f로 'cokacdir.*{key_hash}' 패턴을 검색하여
    해당 봇의 cokacdir 프로세스 존재 여부를 판별한다.

    Args:
        bot_key_hash: 봇의 키 해시 (BOT_KEYS 값)

    Returns:
        프로세스 존재 시 True, 미존재 시 False
    Fpgrepz-fz
cokacdir.*Tr  r~  r   )r   r  r  r   )r@  r   s     r2   _check_bot_processrC    s\     
dj78	
   A%% s   -3 	??delay_secondsc                 f    t        j                         t        |       z   }|j                  d      S )u9   현재 시간 + delay_seconds 후의 절대 시간 반환)secondsz%Y-%m-%d %H:%M:%S)r   r  r   r  )rD  r  s     r2   get_dispatch_timerG  *  s'    =99A::)**r?   c                  2   	 t        j                  g dt        t              dddd      } | j                  j                         }t        |      }|j                         st        |z  j                         }|j                  S # t        $ r	 t        cY S w xY w)u   현재 cwd가 git worktree여도 항상 메인 워크스페이스 경로 반환.

    워크트리는 git tracked .task-counter snapshot을 가지므로 stale.
    카운터/타이머 파일은 항상 메인에서 단일 SoT로 관리한다.
    )gitz	rev-parsez--git-common-dirTr  )cwdr  r:   checkr  )r   r  r!   r   r   rO  r   is_absoluteresolver   r   )r   common_dir_str
common_dirs      r2   _resolve_main_workspacerP  0  s    4I
  ,,..)
%%'#j099;J     s   BB BBr   c                    | j                         sy	 t        | d      5 }t        j                  |      }ddd       t        j                  di       j                               }|syg }|D ]n  }|j                  dd      }t        j                  d|      }|r*|j                  t        |j                  d                   Wt         j#                  d	|        p |syt%        t'        |            }	|	d
   }
t)        dt+        |	            D ]  }|	|   |	|dz
     z
  dk\  r |
dz   S |	|   }
  |
dz   S # 1 sw Y   xY w# t        j                  t
        f$ r}t        d|       d}~ww xY w)uD   task-timers.json에서 다음 ID 계산 (이상치 필터링 적용)rB  r   Nu5   task-timers.json이 손상됨. 수동 복구 필요: r   task-r   z^(\d{4})(?:[+_]|$)u   variant task ID 무시: r   i  )r   r   r$   r   r   r,   r   rc  r   r   r)   r5  r   r   rP  rM  r   r   r  r   ranger  )r   r1   r   r   r  numsr  basemsorted_numsfiltered_maxis               r2   _compute_next_id_from_timersrZ  K  s   X*c" 	 a99Q<D	 
 DHHWb)..01HD 	9yy"% HH*D1KKAGGAJ(LL3A378	9  T#Kq>L1c+&' &q>KA..$6 ! #1~&
 !A	  	   '* XRSTRUVWWXs-   E D5E 5D?:E E.E))E.c                  6   t               } | dz  dz  }| dz  dz  }| dz  dz  }|j                  j                  dd       t        |d      }	 t	        j
                  |t        j                         d}|j                         rR	 t        |j                         j                               }|d	k  r"t        j                  d
|       t        |      }nd}nt        |      }|r)t        |      }||k  rt        j                  d||       |}d| }|j!                  t#        |dz                |j                         r,	 t        |d      5 }	t%        j&                  |	      }
ddd       ndi i}
d
vri |
d<   dt+        j,                         j/                         d|
d   |<   |j                  j                  dd       	 t        |d      5 }	t%        j0                  |
|	dd       ddd       |t	        j
                  |t        j4                         |j7                          S # t        t        f$ r$ t        j                  d       t        |      }Y uw xY w# 1 sw Y   xY w# t$        j(                  t        f$ r'}t        j                  d|        di i}
Y d}~3d}~ww xY w# 1 sw Y   xY w# t        $ r"}t        j3                  d|        Y d}~d}~ww xY w# t	        j
                  |t        j4                         |j7                          w xY w)u   자동 태스크 ID 생성 (카운터 파일 기반, 파일 락으로 중복 방지).

    워크트리에서 호출되어도 항상 메인 워크스페이스의 카운터/타이머 파일 사용.
    rJ   r   .task-counterr   Tr   r   Fr   uB   카운터 파일 값 비정상 (%d), task-timers.json에서 복구u6   카운터 파일 손상, task-timers.json에서 복구u9   카운터(%d) < timers 최대(%d), 위로 보정합니다rR  rB  r   Nu.   task-timers.json 재읽기 실패, 초기화: r   reserved)r   reserved_atr   r   u    task-timers.json 쓰기 실패: )rP  r   r   r   r   r   r   r   rP  r  rO  r   r   rZ  r%  r,   r'  r!   r$   r   r   r   r  rd  r%   r   r   r   )main_workspacer   counter_filer   	lock_filefrom_counternext_num
timers_maxnext_idr1   r.  r   s               r2   generate_task_idrf  s  s   
 -.N(*-??J!H,>L#h.1DDNt<^S)I8Iu}}-  	D|557==?@q=NN#giqr;JGH#'L 4J?H 5jAJ*$Z\dfpq% ($ 	HqL 12 +*c* .a!%1J. "2J*$"$Jw2<X\\^MeMeMg'h
7G$t<	Aj#& G!		*aeAFG
 Iu}}-W ( DWX7
CD.. .(('2 +!OPQsST%r]
+G G 	ALL;A3?@@	A
 	Iu}}-s   6K" AH# A)K" >I% 
I I% (AK" ?J4 J(%J4 -K" #/IK" IK" I"I% %J%>J K"  J%%K" (J1-J4 4	K=KK" KK" "6Lc                    t         dz  dz  }t         dz  dz  }| j                  dd      }t        j                  d|      }|syt	        |j                  d            }t        |d	      }	 t        j                  |t        j                         d
}|j                         r(	 t	        |j                         j                               }||k\  r8|j                  t!        |dz                t"        j%                  d||dz   |        t        j                  |t        j&                         |j)                          y# t        t        f$ r d
}Y w xY w# t        j                  |t        j&                         |j)                          w xY w)uS   외부 지정된 task_id가 카운터보다 크면 카운터를 업데이트한다.rJ   r\  r   rR  r   z^(\d+)NrB  r   r   u2   카운터 동기화: %d → %d (외부 task_id=%s))r   r)   r5  r   rP  rM  r   r   r   r   r   r  rO  r%  r,   r'  r!   r   r   r   r   )r   r`  r   rU  r   	given_numra  r/  s           r2   _sync_counter_if_neededri    s@   x'/9L),??N??7B'DHHY%EEKKN#I^S)IIu}}- l446<<>? ##C	A$67KKLgW`cdWdfmnIu}}- (  	Iu}}-s0   +6E "'D; 	=E ;EE EE 6Fbase_task_idforcec                    | j                  d      s	dd|  ddS t        t              dz  dz  |  dz  }|j                         sdd	| dS t        t              dz  d
z  |  dz  }|j                         rdddS t	        j
                  dd|       }t        t              dz  d
z  |  dz  }d}|j                         r*	 t        |j                  d      j                               }t        t              dz  dz  }	d}
|	j                         rt	        j                  dt	        j                  |       d      }|	j                         D ]B  }|j                  |j                        }|s!t        |j!                  d            }||
kD  sA|}
D t#        ||
      dz   }|dk\  r|s	dd| ddS | d| }t        t              dz  dz  | dz  }|j                         s|}t        t              dz  dz  | dz  }	 |j                  d      }d|  d| d| d}|j%                  ||z   d       t        t              dz  d
z  | dz  }	 |j&                  j)                  d d !       |j%                  t+        |      d       d#||d$S # t        t        f$ r d}Y w xY w# t        $ r}dd| dcY d}~S d}~ww xY w# t        $ r"}t,        j/                  d"|        Y d}~bd}~ww xY w)%u   --resume 옵션 처리: base task 상태 확인 + 자동 채번 + task 파일 복사.

    Returns:
        성공: {"status": "ok", "new_task_id": "task-XXXX+N", "retry_count": N}
        실패: {"status": "error", "message": "..."}
    rR  r   u#   잘못된 task ID 형식입니다: u    (task- 접두사 필요)r   r  rJ   r   r_  u*   task 파일이 존재하지 않습니다: eventsz.doneu=   이미 완료된 작업입니다. 새 task로 위임하세요\+\d+$r   z.retry_countr   r   r   ^z\+(\d+)\.md$rB  r	  uE   3회 이상 재시도. 계속하려면 --force 추가 (현재 retry #)+u   > **재시도**: r   	 (retry #u7   )
> **재시도 사유**: 이전 세션 실패/중단

u   task 파일 복사 실패: NTr   u"   retry_count 파일 쓰기 실패: ok)r   new_task_idretry_count)r  r   r   r   r5  r   rP  r  rO  r%  r,   r  escapeiterdirr   rw   rM  maxr'  r   r   r!   r   r   )rj  r   r1  rk  	task_file	done_filebase_without_plusretry_count_filefile_retry_count	tasks_dirmax_sibling_nsibling_patternr1   rV  r  new_retry_countru  origin_task_filenew_task_fileoriginal_content
retry_metar   new_retry_count_files                          r2   _resolve_resumer    s    ""7+!0ST`Saaz.{|| Y(*W4,s7KKI!0Z[dZe.fgg Y(*X5<.8NNI!.mnn y"l; I1H<,|?\\ 	!"#3#=#=w#=#O#U#U#WX
 Y(*W4IM**"))4E*F)G|%TU""$ 	&A%%aff-A
O}$$%M	& *M:Q>O !E!0u  wF  vG  GH  /I  J  	J ''q(9:K I1G;AR@SSV>WW""$$Oh.8k]#;NNMQ+55w5G~U;-yHY ZF G 	 	  .>!> Q
  	?X5@k]R^C__A##))$)F''O(<w'O
 ;WWi G$ 	! 	!P  Q!0KA3.OPPQ  A;A3?@@AsH   )J 4J* 9K J'&J'*	K3K :K K	K3K..K3
project_idc                 \   |s| S t         dz  dz  | dz  }|j                         s| S 	 |j                  d      }g d}g }|j	                         D ]  }|j                         j                         }|D ]\  }||v sd|v sd|v s	d	|v sd
|v s|j                         j                  d      }	|	r"|	j                  d      s|j                  |	          |rCt        t        j                  |            }dj                  |dd       }
d| d|
 d| d}| |z  } | S # t        $ r | cY S w xY w)u
  프로젝트 맵에서 네비게이션/라우팅 파일 목록을 추출하여 task_desc에 자동 삽입.

    project_id가 지정된 경우, project-maps/{project_id}.md에서
    routes, navigation, config 관련 파일을 찾아 경고 메시지를 주입.
    rJ   project-mapsr_  r   r   )route
navigationnavsidebarmenulayout.u   ├u   │u   └u   ├─│└ #r   Nr  u5   

## ★ 프로젝트 네비게이션 구조 주의 (u+   )
네비게이션/라우팅 관련 파일: uy   
- 이 파일들은 페이지 추가/수정 시 반드시 함께 확인하세요
- 프로젝트 맵: memory/project-maps/u   .md 참조
)r   r   r  r   rN  r  rO  lstripr  r   rc  r;  r  r  )r1  r  map_pathmap_contentnav_patterns	nav_filesrR  
line_lowerpatterncleaned
files_listr   s               r2   _inject_project_map_contextr  -  su    8#n4*S7IIH??(('(:
 OLI&&( 	ZZ\'')
# 	G*$#*;u}PUY]P]afjnan**,--o>7#5#5c#:$$W-		 y12	YYy"~.
Ej\ R88B| D77Al,P 	 	W	;  s   D D+*D+chain_id	task_typec           	      |    | t         vr#t        d|         t        j                  d       t	        | ||||||      S )uA   팀장에게 전달할 프롬프트 생성 (공통 모듈 위임)u   Error: 알 수 없는 팀 ID: rB  r  r  r  )r4   printsysexit_build_team_prompt)r   r1  r   r  r  r  r  s          r2   r5   r5   \  sD     i.wi89)UzH`i r?   c                    t         dz  dz  |  dz  }|j                         st        j                  d|        y|j	                  d      }t        |d      }	 t        j                  |t        j                         t        |dd	
      5 }t        j                  |      }ddd       j                  dd      }|t        |j                  dg             k  re|d   |   }	|	d   D ]U  }
|
d   |k(  s|
d   dv s|
j                  d      &||
d<   t        j                         j                         |
d<   d|
d<    n t        |dd	
      5 }t        j                   ||dd       ddd       t        j#                  d|  d| d|        t        j                  |t        j(                         |j+                          y# 1 sw Y   "xY w# 1 sw Y   ixY w# t$        $ r"}t        j'                  d|        Y d}~td}~ww xY w# t        j                  |t        j(                         |j+                          w xY w)ug   chain.json 파일에서 해당 팀의 pending task에 task_id, dispatched_at, status=in_progress 기록rJ   chainsr]  u   chain 파일 없음: Nz.lockr   r   r   r   current_phase_idxr   phasesr   r   r   )r  in_progressr   dispatched_atr  Fr   r   u   chain 업데이트: chain=, team=
, task_id=u   chain 업데이트 실패: )r   r   r   r   with_suffixr   r   r   r   r$   r   r   r  r   r  rd  r%   r   r   r   r   r   )r  r   r   
chain_file	lock_pathr   r1   r   current_idxphasetaskr   s               r2   _update_chain_taskr  n  s   X%0hZu3EEJ.zl;<&&w/I9c"GGU]]+*cG4 	 99Q<D	  hh2A6TXXh344N;/Eg 	LG+X*DD+3&-DO,4LLN,D,D,FD)%2DN	 *cG4 	=IIdAE!<	=0
''*U\T]^_ 	GU]]+/	  	  	= 	=  821#6778 	GU]]+sn   2G G"AG 3G ;G A G G'&G GG GG 	H$H<H	 HH	 	6H?c                 B   t         dz  dz  }t         dz  dz  }|j                  j                  dd       d}	 t        |d      }t	        j
                  |t        j                         |j                         s:	 |6	 t	        j
                  |t        j                         |j                          yyt        |dd	
      5 }t        j                  |      }ddd       j                  di       }|j                  |       }|St        j                  d|  d       	 |6	 t	        j
                  |t        j                         |j                          yy|j                  d      }|dvrVt        j                  d|  d| d       	 |6	 t	        j
                  |t        j                         |j                          yy|| = ||d<   t        |dd	
      5 }t        j                   ||dd       ddd       t        j#                  d|  d| d       |6	 t	        j
                  |t        j                         |j                          yy# t        $ r Y yw xY w# 1 sw Y   xY w# t        $ r Y yw xY w# t        $ r Y yw xY w# 1 sw Y   xY w# t        $ r%}	t        j%                  d|  d|	        Y d}	~	d}	~	ww xY w# t        $ r Y yw xY w# |E	 t	        j
                  |t        j                         |j                          w # t        $ r Y w w xY ww xY w)uO  task-timers.json에서 해당 task_id의 reserved 또는 running 엔트리 삭제.

    dispatch() 실패 시 호출하여 orphan 항목을 정리한다.
    task-timer 자동 시작 도입으로 reserved뿐 아니라 running 상태도 정리 대상.
    에러 발생 시 로깅만 하고 무시 (본래 에러 반환이 우선).
    rJ   r   r   Tr   Nr   r   r   r   r   z_cleanup_task: task_id u    없음, 정리 불필요r   )r]  r   z_cleanup_task: u$    상태가 정리 대상이 아님 (u   ), 건너뜀Fr   r    u    엔트리 삭제 완료u   _cleanup_task 실패 (task_id=r   )r   r   r   r   r   r   r   r   r   r   r   r$   r   r   r   r   r%   r   r   )
r   r   r   r   r1   r.  r   r   r   r   s
             r2   _cleanup_taskr    s    X%(::J),??Nt<G&~s+GU]]+  "8 GU]]3 5 *cG4 	&1J	& w+YYw'
LL27);TUV$ GU]]3 ! )00LL?7)3WX^W__klm GU]]3  'N#
7*cG4 	CIIj!%B	C 	ogYax7OPQ
 GU]]3   =	& 	&<  9 	C 	C
  G5gYc!EFFG  	 GU]]3 	 s   A J =4I 3J IAJ !4I+ 1J 4I: J J	2$J 4K 	III(#J +	I76I7:	JJ	JJ 	KJ>9K >KK 	KKL4LL	LLLLc                 f    t        |       dkD  r#t        j                  dt        |        d       yy)uR   지시서 크기가 3000자 이상이면 Phase 분리 권고 WARNING 로그 출력i  u#   [large-task-desc] 지시서 크기 u   자 (3000자 초과). Phase 분리를 권장합니다. 한 세션에서 모든 작업을 처리하면 성능이 저하됩니다.N)r  r   r   )r1  s    r2   _warn_large_task_descr    s8    
9~1#i.1A By y	
 r?   generated_idc                 t    ||k7  ryt        j                  d|       rt        j                  d| d|        yy)u  Phase 작업 감지 시 --task-id 미지정 경고.

    task_desc에서 Phase 패턴(Phase N, phase N, Phase N.N)을 감지하고,
    task_id가 자동 생성(generated_id와 동일)이면 WARNING 로그를 출력한다.
    작업을 차단하지 않는다 (경고만).
    Nz(?<![a-zA-Z])[Pp]hase\s+\duT   ⚠️ Phase 작업 감지됨. --task-id를 수동 지정하세요. (예: --task-id u   _N.N) 자동 생성 ID: )r5  rL  r   r   )r1  r   r  s      r2   _warn_phase_without_task_idr    sH     ,	yy.	:&i (!!(	+	
 ;r?   c           
         t        j                  t        t                    dz   }t        j                  ||       }g }d}g }|D ]  }t
        j                  j                  |      s#	 t
        j                  j                  |      }||z  }|dkD  sNt
        j                  j                  |      }t        |dz  d      }	t        |dz        }
|j                  ||	|
f       |j                  d| d|	 d	|
 d
        t        |dz  d      }|t        kD  r!|j                  dd| dt        dz   d       n,|t        dz  kD  r |j                  dd| dt        dz   d       |rdj                  |      S dS # t        $ r Y Aw xY w)uh   task_desc에서 참조 파일을 추출하여 대용량 파일 경고 + 총 크기 한도 경고 반환.z[a-zA-Z0-9/_.\-]+r   ia  i   rB  g      @u    ⚠️ 대용량 참조 파일:  (zKB, ~u#   tok) → offset/limit 사용 필수u   🚨 참조 파일 총 크기 u   KB (한도: ug   KB 초과!) — 전체 읽기 금지. 반드시 offset/limit 분할 읽기 또는 요약 파일 참조.gffffff?u    ⚠️ 참조 파일 총 크기 uD   KB의 70% 초과) — offset/limit 사용을 강력 권장합니다.r  N)r5  rw  r!   r   r6  r"   r   isfilegetsizer,   basenameroundr   MAX_REF_FILE_TOTAL_BYTESinsertr  )r1  r  rD  r  
total_sizelarge_filesr   sizer  size_kbestimated_tokenstotal_kbs               r2   _check_referenced_file_sizesr    s   iiI'*>>GJJw	*EHJK ww~~d#	77??4(D 	d
%<ww''-HD4K+G$TCZ0'3CDEOO28*BwiuM]L^  _B  C$ Z$&*H,,,XJlC[_cCcBd ej k	

 
.4	4.xjE]aeEeDf gA B	
 #+499X447  		s   (E99	FFc                    | dk(  ry| j                  d      sy| j                  dd      }t        dz  |z  dz  dz  }|j                         sd	|  d
S t        j
                  j                  t        |            sd	|  dS t        j
                  j                  t        |            }t        j
                  j                  ddd      }|j                  |      s	d	|  d| dS y)u   팀의 QC 환경(verifiers symlink) 사전 검증.

    dev8은 독립 구조 허용으로 예외.
    비정상이면 WARNING 메시지 반환 (블로킹 아닌 경고).
    2026-04-17 에이전트 미팅 합의 항목 3.
    rm   Nr
  r  r   rd   rz   	verifiersu   ⚠️ QC 환경 경고: u*   의 verifiers 디렉토리가 없습니다uY   의 verifiers가 symlink가 아닙니다. shared/verifiers로의 symlink여야 합니다.shareduP   의 verifiers symlink가 shared/verifiers를 가리키지 않습니다 (실제: rq  )r  r)   r   r   r"   r   islinkr!   r#  r  r  )r   
team_shortverifiers_path	real_pathexpected_suffixs        r2   _check_team_qc_envr    s     + e$"-J(:5<{JN  "*7)3]^^77>>#n-.'y  1N  O	
   ^!45Iggll7HkBOo.'y 1FFO[PQS	

 r?   	thresholdc                    g }| D ]  }|}t         j                  j                  |      s-t         j                  j                  t	        t
              |      }t         j                  j                  |      sq	 t        |ddd      5 }t        d |D              }ddd       |kD  r|j                  ||d        |S # 1 sw Y   &xY w# t        $ r Y w xY w)u   affected_files 중 threshold줄을 초과하는 대형 파일 목록 반환.

    Returns:
        list of dict: [{"path": "...", "lines": 3036}, ...]
    r   r   ignore)r   errorsc              3       K   | ]  }d   ywrB  Nr=   )r  r  s     r2   r  z#_get_large_files.<locals>.<genexpr>@  s     qs   N)r   rr  )r"   r   isabsr  r!   r   r  r   sumr   r,   )rx  r  largefilepathresolvedr1   
line_counts          r2   _get_large_filesr  1  s     E" ww}}X&ww||C	NH=Hww~~h'	hghG /1 A.
/I%hDE L/ /  		s*   7C
B>!C
>C	C

	CCc                      j                         g d}d}dv xs t        t        j                  d|              xsg t        t        j                  | d             xsC t        t        j                  d             xs" t        t        j                  d|             }|rt	         fd|D              rd	}|sy
t
        dz  dz  }|j                         st        j                  d|       y
	 |j                  d      }t        j                  d       |S # t        $ r }t        j                  d|       Y d
}~y
d
}~ww xY w)u=  지시서 키워드 기반 플랫폼 규칙 자동 주입.

    네이버 블로그 관련 키워드가 감지되면 config/naver-blog-rules.md 내용을 반환.
    향후 티스토리 등 다른 플랫폼 규칙도 동일 패턴으로 확장 가능.

    Returns:
        주입할 규칙 텍스트 또는 None
    )u   검증u   폐기u   리팩토링u   마이그레이션u   삭제
deprecatedu   코드 검토u   셀프체크u	   일원화verifyrefactoru   테스트 실행u   코드 분석u   버그 수정u	   디버깅r   u   코드 리뷰uM   (작성|발행|글\s*쓰기|글쓰기|포스팅|올리기|publish|write|post)zblog-publish-naveru   네이버\s*블로그.{0,20}u   .{0,20}네이버\s*블로그u%   네이버.{0,10}(블로그\s*)?발행znaver.{0,10}blog.{0,20}c              3   2   K   | ]  }|v xs |v   y wr<   r=   )r  kwr1  
task_lowers     r2   r  z)_inject_platform_rules.<locals>.<genexpr>x  s"     "c22?#FbJ6F#F"cs   FNr  znaver-blog-rules.mdu+   [naver-blog-rules] 규칙 파일 없음: %sr   r   uB   [naver-blog-rules] 네이버 블로그 규칙 자동 주입 완료u2   [naver-blog-rules] 규칙 파일 읽기 실패: %s)r  boolr5  rL  r  r   r   r   r   r  r   r,   )r1  _EXCLUDE_KEYWORDS_WRITE_INTENTnaver_blog_detected
rules_pathrules_contentr   r  s   `      @r2   _inject_platform_rulesr  H  sO    "J* eM 	
* 	W		;M?KYWX	W 		m_,JKYWX		W 		BINO	W 		8H*UV  s"cQb"cc# X%(==JDjQ",,g,>XY KQOs   :(D# #	E,EEc                      g d}g d}t         fd|D              }t         fd|D              }|r|r|dk7  rt        j                  d       yyyy)uB   리서치+구현 혼합 지시서 감지 시 WARNING 로그 출력)u	   리서치u   조사u   파악u   현재 상태u   가능한지u   방법 조사u
   API 문서u   HTML 구조u	   셀렉터u   외부 서비스u   인증 방식2FAOTPresearchinvestigatefeasibility)
u   구현u   구축u   코딩u   코드 작성u   테스트 작성u   파이프라인	implementbuild	PublisherPipelinec              3   &   K   | ]  }|v  
 y wr<   r=   r  r  r1  s     r2   r  z*_warn_research_impl_mix.<locals>.<genexpr>  s     C2rYCr  c              3   &   K   | ]  }|v  
 y wr<   r=   r  s     r2   r  z*_warn_research_impl_mix.<locals>.<genexpr>  s     ;r2?;r  r  u   [research-impl-mix] 지시서에 리서치와 구현이 혼합되어 있습니다. Phase 분리를 권장합니다. (specs/research-impl-separation.md 참조) 세션 경량화: 리서치 후 /compact 실행 후 구현에 진입하세요.N)r  r   r   )r1  r  research_keywordsimpl_keywordshas_researchhas_impls   `     r2   _warn_research_impl_mixr    s\    $M C1BCCL;];;HY*%<]	
 &=|r?   c           	         t         dz  }|j                         st        j                  d       y	 |j	                  d      }|j                  d      D cg c]  }d|v s|j                          }}t               }d	}|D ]e  }	t        j                  d
|	      D ]J  }
|
|v rt         |
z  }|j                         s"	 |j	                  d       |j                  |
       |dz  }L g g d}|j                         }|D cg c]	  }||v s| }}t        |      }|rHd}||vrBt         |z  }|j                         r)	 |j	                  d       |j                  |       |dz  }d	}	 d	dlm}  |       }	 |j!                  |dd      }t#        |      }|j%                          	 t        j)                  dt#        |       d| d| d       | j+                  d      r|rt        j-                  d|  d|        yyy# t
        $ r"}t        j                  d|        Y d}~yd}~ww xY wc c}w # t
        $ r&}t        j                  d|
 d|        Y d}~d}~ww xY wc c}w # t
        $ r&}t        j                  d| d|        Y d}~.d}~ww xY w# |j%                          w xY w# t&        $ r#}t        j                  d|        Y d}~1d}~ww xY w)u   MEMORY.md ★ 항목 확인 및 디자인 작업 dev팀 위임 위반 감지.

    파일 I/O 실패 시 예외를 전파하지 않고 debug 로그만 남긴다.
    z	MEMORY.mdu5   [memory_check] MEMORY.md 파일 없음, 체크 스킵Nr   r   u(   [memory_check] MEMORY.md 읽기 실패: r  u   ★r   z\[.*?\]\((.*?\.md)\)rB  u,   [memory_check] 링크 파일 읽기 실패: u    – )		   디자인   배너	   이미지u	   포스터u   일러스트bannerimageposterr|   z"feedback_design_team_routing_v2.mdu/   [memory_check] 피드백 파일 읽기 실패: )MemoryIndexerr	  r  )limitlayeru4   [memory_check] FTS5 Layer 1 검색 실패 (무시): u   [memory_check] ★ u   개 확인, 관련 피드백 u"   개 확인, FTS5 관련 메모리 u   건r
  uT   ⚠️ [memory_check] 디자인 작업을 dev팀에 위임하려고 합니다! team=u   , 감지 키워드: )_MEMORY_BASE_PATHr   r   r   r  r,   r  rO  r   r5  r6  r   r  r  utils.memory_indexerr	  rL  r  r   r   r   r  r   )r   r1  memory_file	memory_mdr   rR  
star_itemsloaded_filesmatched_countrv  fnameref_pathdesign_keywordsr  r  detecteddesign_keywords_foundextra_fname
extra_path	fts_countr	  _indexerfts_resultss                          r2   _check_memory_before_dispatchr    s   
 $k1KLM))7);	 ,5??4+@R4ETM$**,RJR LM aZZ 7> 
	aE$(50H a&&&8 $$U+!Q&M
	aa O"J,Arj0@AHA N :l**[8J  "j(('(: $$[1!Q&M
 I
Q6 ?	"//)1G/LKK(INN KK
c*o..KM? [##,+S	2 % %:efmen  oC  DL  CM  N	
 &; }  ?sCD
 S  aLL#OPUwV[\][^!_``a B  jLL#RS^R__defdg!hiij NN QKA3OPPQs   H 	H/ H/>(H4	I&I&?(I+ *J2 8J J2 	H,
H''H,4	I#=II#+	J4JJJ//J2 2	K;KKskip_brainstormingc                    |rt         j                  d       ydddd}|j                  |d      }|dk  ryg d}|j                         }|D cg c]  }|j                         |v s| }	}|	syt        dz  d	|  d
z  }
|
j                         rt         j                  d|
        yt         j                  d| d|	 d       yc c}w )u*  Lv.3+ UX 작업에 brainstorming 사전 실행 여부를 확인한다.

    skip_brainstorming=True이면 즉시 반환한다.
    level이 3 미만이면 즉시 반환한다.
    UX 관련 키워드가 없으면 즉시 반환한다.
    brainstorming 파일이 없으면 warning을 남긴다.
    u6   [brainstorming-gate] --skip-brainstorming으로 스킵Nr   r	  r  r  )UXUIr  u   사용자 경험u   인터페이스u   레이아웃u   화면u	   페이지u   컴포넌트u	   프론트frontendr
  zbrainstorming-r_  u5   [brainstorming-gate] brainstorming 파일 확인됨: u   ⚠️ [brainstorming-gate] Lv.u9    UX 작업에 brainstorming 미실행. 감지 키워드: uf   . /brainstorming 스킬 실행 후 재위임하세요. --skip-brainstorming 플래그로 스킵 가능)r   r   r   r  r  r   r   r   )r   r1  r  r  	level_map	level_intux_keywordsr  r  detected_keywordsr   s              r2   _check_brainstorming_gater'  		  s     MN!;IeQ'I1}K "J&1NRXXZ:5MNNz)nWIS,IID{{}KD6RS
NN
)) 5./ 0o	p Os   C'Cc                     t         r*t        r$	 t        j                  d      } | rt        |       S 	 t
        r<t        6t        t              }|j                         }|j                  di       } | r| S t        dz  dz  }|j                         r?	 t        |dd      5 }t        j                  |      }ddd       j                  di       S i S # t        $ r Y w xY w# 1 sw Y   ,xY w# t        j                  t        f$ r Y i S w xY w)	uE   config/constants.json에서 logical_teams 도메인 매핑을 로드.logical_teamsNr   r  r  r   r   r   )_CONFIG_AVAILABLE_cfgget_constantr;  r   r   r   r   _load_constantsr   r   r   r$   r   r   r,   )r   mgrr  r  r1   r   s         r2   _load_logical_teamsr/  /	  s    T	&&7FF|#  !2!>y9'')	3M ),<<N	ncG< $yy|$88OR00 I+  		 $ $ $$g. 	I	s;   !C C+ C4C+ 	CCC($C+ +DDc                 x    t         r%t        t        t              j                         S t	               }|syd}d}|j                         D ]b  \  }}|dk(  r|j                  dg       }|j                  dg       }t         fd|D              rEt         fd|D              }||kD  s_|}|}d |dkD  r|S dS )	u   task 설명에서 키워드 매칭 → 적합한 논리적 팀 추천.

    Returns:
        추천 팀 ID (예: "design") 또는 None (매칭 없음)
    Nr   r   	compositekeywordsanti_keywordsc              3   &   K   | ]  }|v  
 y wr<   r=   )r  akr1  s     r2   r  z _suggest_team.<locals>.<genexpr>h	  s     72rY7r  c              3   ,   K   | ]  }|v sd   ywr  r=   r  s     r2   r  z _suggest_team.<locals>.<genexpr>l	  s     <"B)OA<s   	)	r   r   r   suggest_teamr/  r   r   r  r  )	r1  r)  	best_team
best_scorer   r  r2  r3  scores	   `        r2   _suggest_teamr;  O	  s     !2!> 	:GG	RR ()M#IJ(..0  k!J3$jj"= 777 <<<:JI! $ #Q90D0r?   override_routingc                    t         rTt        Nt        t              j                  | ||      }|y|j	                  dd      }|j                  d      sd|z   }|S | t        v ryt        |      }|y|rt        j                  d|  d| d	       yt               }|j                  |i       j                  d
d      }d| d| dS )u  dev팀에 논리적 팀 소관 작업이 위임되면 WARNING 반환.

    Args:
        team_id: 위임 대상 팀 ID
        task_desc: 작업 설명
        override_routing: True이면 라우팅 경고를 무시

    Returns:
        경고 메시지 또는 None (문제 없음)
    Nr   u(   override_routing=True를 사용하세요u%   --override-routing을 추가하세요u   ⚠️u   ⚠️ z[routing-override] u   에 u1    소관 작업 위임 (--override-routing 적용)descriptionr   u   ⚠️ 이 작업은 --team u   이 적합합니다 (u9   ). 계속하려면 --override-routing을 추가하세요.)r   r   r   validate_routingr)   r  DYNAMIC_BOT_TEAMSr;  r   r   r/  r   )r   r1  r<  r  	suggestedr)  r>  s          r2   _validate_team_routingrB  u	  s     !2!>y9JJ7T]_op;kkDFmn~~h'c/C
 ## i(I,WIT)Duvw')M##Ir266}bIK
&yk1F{m  TQ  	Rr?   	teams_strc                    | j                  d      D cg c]#  }|j                         s|j                         % }}t        |      dk  rt        d| d      t        |      t        kD  rt        dt         dt        |       d      |D cg c]  }|t
        vs| }}|rt        d| d	t        t
                     t        |      t        t        |            k7  rt        d
|       |S c c}w c c}w )u   composite teams 문자열 파싱 + 유효성 검증.

    Args:
        teams_str: 쉼표 구분 팀 ID 문자열 (예: "marketing,design")

    Returns:
        검증된 팀 ID 리스트

    Raises:
        ValueError: 유효하지 않은 입력
    rE  r   u<   composite는 2개 이상의 팀이 필요합니다 (입력: rq  u   최대 u   개 팀까지 허용 (입력: u   개)u   알 수 없는 팀 ID: u   . 허용 목록(소문자): u   중복 팀 ID: )r  rO  r  r%  r8   r7   r  r   )rC  r  rd   r   s       r2   _validate_composite_teamsrE  	  s     !* 4B1	QWWYBEB
5zA~WXaWddefgg
5z''7#6"77UVYZ_V`UaaefggDQ1,C#CqDGD27);WX^_vXwWxyzz
5zSU_$?5'233L C
 Es   C4C4C9C9composite_teamsc           
         d}|t               }|}nt        |       |t        |||       t        dj	                  |              t
        rst        m	 t        |      }|j                  sU|j                  D cg c]  }|j                  dv s| }	}|	r+t        |       dd|	D cg c]  }|j                   c} dS t        rIt         C	 d| }t!        |      }t        j#                  d	|j$                   d
|  d|j&                          |}|4t(        r.t*        (	 t+        |      }t        j#                  d| d
|         n|rt        j#                  d| d
|         t        j#                  d|  d|        |dd t-        |      dkD  rdndz   }t/        |d      s	dd| ddS dt1        t2              d|ddd|g}t5        j6                  |ddd      }|j8                  d k7  r3t        j;                  d!| d"|j<                  j?                                 t@        d#z  d$z  }|jC                         r	 tE        |d%d&'      5 }tG        jH                  |      }ddd       jK                  d(i       jM                         D ]  \  }}|jK                  d)      dk7  s||k(  r |jK                  d*d      }|jK                  d+g       }|rtO        |      n|h}|tO        |       z  }|se|st        |       dd,| d-| d.dc S t        j;                  d/| d0| d1        n tU        jV                  d3d4| d5|d67      }t@        d#z  d(z  | d8z  }|jX                  j[                  dd9       |j]                  |d&'       t^        rt`        	 ta        |t1        |      d:d;<       	 d d>l1m2}  || |||      }dAdBdCdD}|jK                  |dA      }th        r/tj        )tk        |      r|dEz  }t        j#                  dF| dG       	 tm        ||H      } tp        |    }!tr        jK                  |!      }"|"Ht        j;                  dI|!ju                          dJ       t        |       ddK|!ju                          dS tw        |d| | L       dM}#dNdO|dPty        |#      dQtz        dR|"dSg
}$	 t5        j6                  |$ddd      }%|%j8                  d k(  rd	 tG        j~                  |%j                        }&d dVlAmB}'  |'ddW       t                |dd t-        |      dkD  rdndz   }t        r	 t        |      }dXj	                  |       }(dY| dZ|( d[| })t5        j6                  dt1        t2              d\|)d]d:gddd       t        j#                  d^| d_|(        d`|d| da||db|( dc|&dd	}*t        |&t              r|&jK                  de      nd}+|+r|+d dAdf| d8dg},tw        |fi |, 	 d dhlHmI}-  |-||      }.|.r>t        j#                  di|.jK                  dj       dk|        |.jK                  dj      |*dj<   |*S t        jg                  dm|%j<                  j?                                 t        |       d|%j<                  j?                         dnj	                  |$ddC dogz         dpS c c}w c c}w # t        $ r#}
t        j                  d|
        Y d}
~
d}
~
ww xY w# t        $ r#}
t        j                  d|
        Y d}
~
d}
~
ww xY w# t        $ r#}
t        j                  d|
        Y d}
~
d}
~
ww xY w# 1 sw Y   xY w# tF        jP                  tR        f$ r#}t        j;                  d2|        Y d}~[d}~ww xY w# t        $ r#}
t        j                  d=|
        Y d}
~
d}
~
ww xY w# t        $ r5}t        jg                  d?|        t        |       dd@| dcY d}~S d}~ww xY w# tn        $ r#}t        |       dt1        |      dcY d}~S d}~ww xY w# t4        j|                  $ r( t        jg                  dT       t        |       ddTdcY S w xY w# tF        jP                  $ r  dU|%j                  j?                         i}&Y w xY w# t        $ r Y rw xY w# t        $ r#}/t        j;                  dl|/        Y d}/~/|*S d}/~/ww xY w)qu   복합업무 임시팀 디스패치.

    하나의 봇 세션에 여러 논리적 팀을 조합하여 Phase별로 순차 실행.
    임시팀장(Opus)이 Phase 관리, 핸드오프, 품질 검수를 일괄 수행.
    NrE  highr  r   u)   보안 위협 감지 (composite 차단): rm  *   [injection_guard] 스캔 실패 (무시): z
composite [approval] risk_level=z, composite_teams=, patterns=#   [approval] 검사 실패 (무시):    [model_router] 권장 모델: *   [model_router] 라우팅 실패 (무시): (   [model_router] 명시적 모델 지정: u,   복합업무 위임 시작: composite_teams=r  r}  ...r   r   task @   : archived/escalated 상태 — 영구 박제로 dispatch 차단r   start--teamr1  --descT   r~  r   !   task-timer start 실패 (task_id=r   rJ   r   r   r   r   r   r   r   rF  u   겹치는 팀(/   )에 이미 running 태스크가 있습니다: uB   . 완료 후 재시도하거나 --force로 강제 실행하세요.u   [force] 겹치는 팀(&   )에 이미 running 태스크 있음: u   . --force로 강제 진행.!   running 태스크 확인 실패: ^# task-\d+\.\d+:# :rB  countr_  r   dispatchwriter   r  tool	operation.   [audit_logger] 감사 기록 실패 (무시): )build_composite_promptu   build_composite_prompt 실패: u   프롬프트 생성 실패: r   r	  r  r    

## ⚠️ Sanitize 게이트 (Lv.3+)
외부 AI(Codex/Gemini) 호출 전 반드시 sanitize 검사를 수행하세요:
- 대상: affected_files의 코드 + 설계 문서
- 마스킹 항목: 주민등록번호, 연락처, API 키, 계좌번호, 보험 증권번호
- 방법: `from utils.sanitize_gate import sanitize_text` 사용
- PII가 마스킹된 코드만 외부 AI에 전달할 것
[sanitize-gate] Lv.u5    composite 작업에 sanitize 게이트 지시 삽입r      환경변수 COKACDIR_KEY_
    미설정6   봇 키가 설정되지 않았습니다: COKACDIR_KEY_)rx   r   rF  r  cokacdir--cron--at--chat--key--once+   cokacdir 호출 타임아웃 (60초 초과)rawset_bot_status
processingrr  [u   ] 복합업무 임시팀(   )에게 위임: log--typeu   복합업무 위임 완료: r   
dispatchedu   복합업무 임시팀장u   복합업무 임시팀(u@   )에게 위임 완료. 즉시 독립 세션에서 작업 시작.)	r   r   r   rF  r   r  r>  r  cron_responser  r^  )schedule_idrv  	max_retryrz  issue_mc   [memory-check] MC 발급: mc_id for *   [memory-check] MC 발급 실패 (무시): u   복합업무 위임 실패: r     ...(생략)r   r  command)Jrf  ri  r  rE  r  _INJECTION_GUARD_AVAILABLE_scan_contentis_safethreatsseverityr  pattern_namer   r   r   _APPROVAL_AVAILABLE_check_commandr   
risk_levelmatched_patterns_MODEL_ROUTER_AVAILABLE_route_modelr  r0  r!   
TASK_TIMERr   r  r  r   r   rO  r   r   r   r$   r   r   r   r   r   r,   r5  r   r   r   r'  _AUDIT_LOGGER_AVAILABLE_log_file_operationprompts.team_promptsrg  r   _SANITIZE_GATE_AVAILABLE_should_sanitizer   r   
BOT_TO_KEYr   upperr(  rG  r   r  r  r   utils.bot_activityrw  r$  _REDACT_AVAILABLEr>   r:  r;  utils.memory_checkr  )0rF  r1  r  r   rk  r   r  _scan_resultr  _high_threats_e_approval_input_approval_result_recommended_model
short_desc	timer_cmdtimer_resultr   r1   existing_dataexisting_tidexisting_taskexisting_teamexisting_compositeexisting_team_setr  r   rz  rg  prompt_composite_level_to_int_composite_dispatch_levelr   r   r  _dispatch_delayr  r   responserw  teams_labellog_msg_result_schedule_id_watchdog_metar  	mc_result_mc_errs0                                                   r2   _dispatch_compositer  	  s	    L"$ 	 ( #IwE chh78 "m&?	L(3L'',8,@,@ gqAJJRfDf g g !'*")%NhuOvcdPQP^P^OvNw#x  ~9		E *9+6O-o>KK()9)D)D(E F##2"3 4,==>@ &+!&=,BZ	L!-i!8KK89K8LL^_n^opq 
>?Q>RRdetduvw
KK>>OzZaYbcd 3BC	NR,?5RHJ GY/!gY>~.  A  	A 	J	I >>)DtUWXL!#:7)3|GZGZG`G`GbFcde X%(::J	Dj#8 -A $		!-/</@/@"/M/S/S/U +m $$X.);|w?V - 1 1)R @%2%6%67H"%M"?QC(:$;XeWf!+c/.BB %g.&-"0	9h#/.0r!t   NN0	9_'.(CE /8 +r'!_iqQI H$w.G9C@I4$7W5 #6#B	P#i.zelm
R?')US *+qI 7 ; ;E1 E$4$@EUVoEpK	
 	)*C)DDyz{6.wuM ,'H
,,x
 C
{3HNN4D3EZPQg!0fgogugugwfx.yzz 'WfgO 	/*C]DtRP A	6zz&--0H
 	6{L1 s^I0CuL
)*5
 hh/gY7}DTU_T`aJ:N		
 	27)5NO"./$0=}~%

 .8$-Gx||D)T+ ,WIS9	N "'<^<	S3 )4I8w9O8PPUV]U^_`#,==#9  3FMM4G4G4I3JKLg!fmm.A.A.CPSPXPXY\]_^_Y`dqcrYrPsttW !h
 Pw 	LLLEbTJKK	L  	ELL>rdCDD	E  	LLLEbTJKK	L@- -4 $$g. 	DNN>qcBCC	D  	PLLI"NOO	P  R6qc:;g!0LQC.PQQR,  6g!c!f556> $$ ]BCg!.[\\] ## 	6v}}2245H	6  V  	SNNGyQRR	SsM  %Z/ 8Z%Z%Z/ %Z*8Z/ A[ &\ :]	 \<B]	 +]	 ]	 ^ *^7 	_8 )`' a% *b %Ab+ %
Z/ /	[8[[	\
'\\
	\9\44\9<]]	 	^"^  ^	^4^//^47	_5 *_0*_50_58	`$``$`$'8a"!a"%/bb	b('b(+	c4ccprd_contentc                    t        j                  dt         j                        }t        j                  dt         j                        }g }t        |j	                  |             }t        |      D ]  \  }}|j                  d      }t        |j                  d            }|j                  d      j                         }	|j                  d      j                         }
|j                         }|j                  | |      }|r|j                         n
t        |       }| || j                         }d}t        j                  dt         j                        }|j                  |      }|r|j                  d      j                         }g }t        j                  d	|
      }|r;|j                  d      j                  d
      D cg c]  }|j                          }}|j                  |||
|	|||d        |S c c}w )u!  PRD 문서에서 Phase/Sprint 섹션을 정규식으로 추출한다.

    Args:
        prd_content: PRD 파일 전체 텍스트

    Returns:
        phase 딕셔너리 리스트. 각 항목은 phase_number, phase_type, title,
        duration, body, features, dod 키를 가진다.
    u?   ^###\s+(Sprint|Phase)\s+(\d+)\s*\(([^)]+)\)\s*[-—–]\s*(.+)$z^###\s+(Sprint|Phase)\s+|^##\s+rB  r   r	  r  Nz\*\*DoD\*\*:(.*?)(?=\n##|\Z)z\[([^\]]+)\]rE  )phase_number
phase_typetitledurationbodyfeaturesdod)r5  r  	MULTILINErc  finditer	enumeraterM  rP  rO  endrL  rT  r  r7  r  r   )r  header_patternnext_section_patternr  matchesrY  rV  r  r  r  r  
body_startnext_mbody_endr  r  dod_patterndod_mr  feat_mr1   s                        r2   _parse_prd_regexr  
  s    ZZJ
N ::&H",,WF>**;78G'" $
1WWQZ
1771:771:##%
  " UUW
%,,[*E%+6<<>[1A:h/557 "jj!@"))L""4(++a.&&(C !?E2+1<<?+@+@+EFa	FHF ,($$
	
5$
L M Gs   G=prd_pathc                    d| d}d}	 t        j                  |d|ddgdddd	
      }|j                  dk7  r2t        j	                  d|j
                  j                                 g S |j                  j                         }	 t        j                  |      }t        |t              r|S t        |t              r@d|v r<|d   }t        |t              rt        j                  |      S t        |t              r|S t        j                   d|t        j"                        }|r$t        j                  |j%                  d            S t        j	                  d       g S # t        j                  $ r Y ww xY w# t         j&                  $ r t        j	                  d       g cY S t(        $ r$}	t        j	                  d|	        g cY d}	~	S d}	~	ww xY w)u  claude CLI를 호출해 PRD에서 Phase/Sprint 정보를 JSON으로 추출한다.

    Args:
        prd_path: PRD 파일 경로 (프롬프트 참조용)
        prd_content: PRD 파일 전체 텍스트

    Returns:
        phase 딕셔너리 리스트. 실패 시 빈 리스트.
    u  다음 PRD 문서에서 구현 로드맵 섹션의 Phase/Sprint별 태스크를 JSON 배열로 추출해줘.

각 항목 형식:
{"phase_number": 0, "phase_type": "Sprint" 또는 "Phase", "title": "제목", "duration": "소요시간", "body": "항목 내용 전체", "features": ["F1", "F2"], "dod": "완료조건 또는 null"}

JSON 배열만 출력하세요. 다른 텍스트 없이.

--- PRD 문서 시작 ---
u   
--- PRD 문서 끝 ---z/home/jay/.local/bin/claudez-pz--output-formatr$   Tx   z/tmp)r  r:   r  rJ  r   u    [handle_prd] claude CLI 오류: r   z\[.*\]uG   [handle_prd] claude 출력에서 JSON 배열을 찾지 못했습니다.z&[handle_prd] claude CLI timeout (120s)u'   [handle_prd] claude CLI 호출 실패: N)r   r  r  r   r   r   rO  r   r$   r  r:  rc  r;  r!   r   r5  rL  r7  rM  r  r   )
r  r  r  
claude_binr   ru  outerinnerarr_mr   s
             r2   _parse_prd_clauder    s   	& - !	!  /J%v'8&A
 !LL;FMM<O<O<Q;RSTImm!!#	JJsOE%&%&8u+<heS)::e,,eT* L 		)S"))4::ekk!n--^_	 ## 		 $$ =>	 >qcBC	sa   AE7 *E7 &E ,=E *E <A
E7 E7 E41E7 3E44E7 7*G#G+G
G
Gc                 j   t        |       }|j                         s	t        | z  }|j                         sdd|  dS |j                  d      }t	        |j                               }t        |      }d}|s#t        j                  d       t        ||      }d}|j                  }|j                  d	      r|d
d n|}t        dz  dz  }	|	j                  dd       g }
g }t        |      D ]  \  }}|j                  d|      }|j                  dd      }|j                  dd      }|j                  dd      }|j                  dd      }|j                  d      xs g }|j                  d      }d| d| }|	| dz  }|j                         r|j                  t	        |             g }|dkD  r$||dz
     j                  d|dz
        }d| d| g}t!        j"                         j%                  d      }|rd j'                  |      nd}d!| d!}|r|d"| z  }t)        |      }d}|rd#| d$}d%| d&| d'| d(| d)| d*| d*| d+| d,| d-| d$| }|j+                  |d       |
j                  t	        |             t        j                  d.|         d/| |||
|t-        |      d0S )1u  PRD 파일을 읽어 Phase별 task 파일을 생성한다. 위임은 하지 않는다.

    Args:
        prd_path: PRD 파일 경로 (절대경로 또는 WORKSPACE 기준 상대경로)
        team_id:  담당 팀 ID

    Returns:
        처리 결과 딕셔너리
    r   u'   PRD 파일을 찾을 수 없습니다: rm  r   r   regexuE   [handle_prd] 정규식 파싱 결과 없음, claude CLI 폴백 시도claudezprd-r  NrJ   r   Tr   r  r  Phaser  r   r  r  r  r  z	dispatch-z-phaser_  r   rB  rF  )timespecr   `u    — u   
## 완료 조건 (DoD)
r  z---
task_id: "z	"
team: "z$"
level: 2
priority: P2
depends_on: z
created_at: "z"
deadline: null
---

# r  r!  u   

## PRD 참조 (필수)


---

u   [handle_prd] 파일 생성: rt  )r   prdr   methodcreatedskippedtotal_phases)r   r   r   r  r!   rM  r  r   r   r  stemr  r   r  r   r   r   r  rd  r  reprr'  r  )r  r   prd_filer  prd_absr  r  r  prd_stemr  r  r  idxr  	phase_numr  r  r  r  r  r  r   	file_path
depends_onprev_phase_num
created_atfeatures_strprd_ref_linedepends_on_yamldod_sectionr~   s                                  r2   
handle_prdr  F  s,    H~H??x'??!0WX`Wa.bcc$$g$6K(""$%G k*FF[\"7K8 ==D??62tABxHH$w.IOOD4O0GG' 6@
UIInc2	YY|W5
		'2&99Z,yy$#ii
39riihZvi[97)3/	NN3y>* !#
7#C!G_00qIN%hZvn5EFGJ\\^--y-A
.6tyy*B7)1~eL>22Lz*6se2>K 	 "Y  ++ ,&< ( 
!J<q2eW ='n fBm 	$ 	Ww7s9~&29+>?m6@t F r?   r  original_keydispatch_delayc                    g }t         j                         D ]!  \  }}|s	||k7  s|j                  ||f       # |st        j	                  d| d       yt        t               j                               }|D ]  \  }}d}	t        j                         D ]  \  }
}||k(  s|
}	 n |	r|	|v r5t        j                  d| d| d       dd| dt        |      d	t        d
|dg
}	 t        j                  |ddd      }|j                  dk(  rC	 t!        j"                  |j$                        }t        j                  d| d| d       ||dc S t        j	                  d| d| d|j*                  j)                                  t        j-                  d| d       y# t        j                  $ r  t        j	                  d| d| d       Y [w xY w# t         j&                  $ r d|j$                  j)                         i}Y w xY w)u  cokacdir --cron 실패 시 봇별 key fallback으로 재전송.

    원래 봇 키 전송 실패 시, 동일 팀의 다른 사용 가능한 봇 키로 재시도.
    BOT_KEYS에서 original_key와 다른 키를 순회하여 첫 번째 성공 시 반환.
    z[fallback] u*   : 대체 가능한 봇 키가 없습니다Nu&   : 대체 키로 재전송 시도 (key=rq  rn  ro  rp  rq  rr  rs  Tr}  r~  u   : 대체 키 u    타임아웃r   ru  u   로 재전송 성공)r  fallback_keyu	    실패: u   : 모든 대체 키 실패)r   r   r   r   r   r   r   r   r  r   rG  r   r   r  r  r  r$   r  r   r   rO  r   r   )r  r  r   r   r  fallback_keysr   r   r   bot_id_for_keybidknamer  retry_resultr  s                  r2   _retry_with_fallback_keyr    s@    M&nn. 7(L0  (H!567 WI-WXY ')..01I+ +q($**, 	JC !$	 n	9k'*PQYPZZ[\] n-
	%>>#dWYZL
 ""a'@::l&9&9: KK+gYmH:EYZ[$ ( 
 NN[	xj	R^ReReRkRkRmQnopW+qZ LL;wi'ABC' (( 	NN[	xjVW	 '' @!<#6#6#<#<#>?@s$   %F#G#/GG/H
H
session_idrefresh_mapr  resume_from
agent_typeallow_no_scopec                 r*   |=t        |      }|j                         sdd| dS |j                  d      }d| d| }| s|sdd	dS | r|rdd
dS |r2t        |d   |       t	        |	xs d|||       t        ||||	||      S | J t        | ||      }|rd|dS d}|	t               }	|	}nt        |	       |t        ||	|       t        rt        	 t        |      }|j                  s|j                  D cg c]  }|j                  dv s| }}|r1t        j!                  d|D cg c]  }|j"                   c}        n:t        j%                  d|j                  D cg c]  }|j"                   c}        t*        rNt,        H	 | d| }t-        |      }t        j%                  d|j.                   d|  d| d|j0                          |}|4t2        r.t4        (	 t5        |      }t        j%                  d| d|         n|rt        j%                  d| d|         t        j%                  d|  d|	        t6        rt8        	 t9        t;        t<                    } | j?                         }!| jA                         }"|!jC                         D ]o  \  }#}$|$jE                  d      | k7  r|"jE                  |#i       }%| jG                  |#|$|%      }&|&d   d k(  sIt        j!                  d!|#|&jE                  d"d#             q 	 dd%l$m%}'  |'       }(|(jM                  d&      r|})nd'})d}*d}+|
0|	r.tO        jP                  d(|	      },|,r|,jS                  d)      }+d*|+ }*|xs | tT        v }-t<        d+z  d,z  }.|.j                         r	 tW        |.d-d      5 }/tY        jZ                  |/      }0ddd       0jE                  d.i       jC                         D ]  \  }1}2|2jE                  d/      d0k(  s|2jE                  d      | k(  s0|1|	k7  s6|-s/t]        |	       dd1|  d2|1 d3|2jE                  d4d5      dd6  d7dc S t        j!                  d8|  d9|1 d3|2jE                  d4d5      dd6  d:| tT        v rd;nd< d=	        n |dd? tc        |      d?kD  rd@nd5z   }4|	}5te        |5d0      s	ddA|5 dBdS dCt;        tf              dD|5dE| dF|4g}6|r|6ji                  dG|g       |r|6ji                  dH|g       tk        jl                  |6dIdId6J      }7|7jn                  dk7  r3t        j!                  dK|5 dL|7jp                  js                                 |/t<        dMz  |z  }8|8j                         st]        |5       ddN|8 dS |r|rt<        d+z  dOz  | dPz  }9dQ}:|9j                         sdI}:nI|9ju                         jv                  };ty        jz                         j}                         |;z
  dRz  }<|<dSkD  rdI}:|:rqt<        dMz  |z  }8|8j                         rUt<        dTz  dUz  }=|=j                         r9tk        jl                  dCt;        |=      t;        |8      dVt;        |9      gdIdId6J       tO        j~                  dWdX|	 dY|d)Z      }t        | |       t	        |	|||       t        ||       t        |       t        |      }>|>rt        j!                  d[|>        t        |t;        t<                    }t        ||      }|d\k(  rt        |t;        t<                    }t<        d+z  d.z  |	 dPz  }?|?j                  j                  dIdI]       |?j                  |d       |E	 t        |	||t;        |?j                  t<                    ^      }@t        j%                  d_|@        n|r	 t<        d+z  daz  }A|Aj                  dIdI]       |A|	 dbz  }B|	ty        jz                         j                         t;        |?j                  t<                    dcdd}Ct        |B|C       t        j%                  de|B        t        rt        	 t        |	t;        |?      dgd'h       t        |       }D|Drt        j!                  djD        t        | ||	||||k      }Edldmdndo}F|FjE                  |dl      }Gt        r/t        )t        G      rEdpz  }Et        j%                  dqG dr       t        |      }Ht        |Ht;        t<                    }Ht        |      }I|HrCt        H|	      }J|JD ]  }Kt        j!                  ds|K         t        J       t        |5Ht       t        |I      }L|Lrt        j!                  dsL        t        |H      \  }M}Ndldmdndo}F|FjE                  |dl      }O|O|Mk  r$Nr"t        j!                  duO dv| dwN dxM dy	       |rt        |5|z       |>r	Ed{|> d|z  }EHr.t        H      }P|Pr!d}j                  d~ PD              }QEd|Q dz  }EHr)t        H      rEdz  }Et        j%                  d|	 d       t        |      }R|RrEdR z  }E| dk(  r9t        r3t        -	 t        |      }SEd|S dz  }Et        j%                  d|S        t        |    }T| tT        v r|	 t        |5|      }Ut        U   }Vt        jE                  |V      }W|WHt        j!                  dVj                          d       t]        |5       dd|Vj                          dS | }XU}Ynt        jE                  |       }Z|ZrZt        vrt]        |5       dd|  ddS t        Z   }W|WHt        j!                  dZj                          d       t]        |5       dd|Zj                          dS | j                  dd5      }Xt        jE                  | d5      }Y|Yrt        |5Y       Yrt        |	      }[t        j%                  dY d|  dt        |[j                                       |Y|[v r[Y   }\|\d   }]|\d   }^|]|	k7  r^| k7  r|sHt]        |5       t        [      }_dj                  d |_D              }`ddY d^ d] d|_rd` ndz   _dS t        j!                  dY d^ d] d       nt        j%                  dY d       Yrt        |5XY|xs dd|	 dP       dldmdndo}a|ajE                  |dl      }b|bdmk\  r&t        |	b      }c|crt        j%                  dc        t        |	|||      }d|dr	Edd d}z  }Et	        |	|||       dQ}ed}fW}g|dv r3gr1t        gd      }f|fr#fdk7  rdI}et        j%                  d| df d       d}hddEdt        |h      dt        dWdg
}i	 tk        jl                  idIdId?J      }jjjn                  dk(  r	 tY        j                  jj                        }kddlwmx}l  |l| d       t        |       }mt        j%                  d|  d|md    d|md   xs d dmd   xs d        t                erfrt        gfd6       |dd? tc        |      d?kD  rd@nd5z   }4t        r	 t        |4      }4d|	 dTd    dv|  d|4 }ntk        jl                  dCt;        tf              d|nddggdIdId6J       |rt        || |	       |
|*|+g }ot        d)|
d)z         D ]'  }pd|+ d|p }qd|q dP}roj                  |p|r| |qdʜ       ) dCt;        t<        dz        dd|*dtY        j                  odQϫ      d|?g	}stk        jl                  |sdIdId6J      }t|tjn                  dk7  r4t        j!                  d|* dLtjp                  js                                 nt        j%                  d|*        Td   }ut        r	 t        u      }ut        j%                  d|	 du        d|	| Td   |||Td    d֝kdל}v|*|*vd<   t        kt              rkjE                  d٫      nd}w|wrwddldڜ}x|?r|?xd<   t        |	fi x 	 ddlm}y  |y|	|      }z|zr>t        j%                  dzjE                  dޫ       d|	        |zjE                  dޫ      vd<   vS t        j                  djjp                  js                                 t        EW| |	      }|||r|d   }kt        j%                  d||d    d       ddlwmx}l  |l| d       t                erfrt        gfd6       |dd? tc        |      d?kD  rd@nd5z   }4t        r	 t        |4      }4d|	 dTd    dv|  d|4 }ntk        jl                  dCt;        tf              d|nddggdIdId6J       d|	| |Td   ||dd d|d    dkdId	}vt        |kt              rkjE                  d٫      nd}w|wrwddldڜ}x|?r|?xd<   t        |	fi x vS er&fr$t        gf       t        j%                  d|g        t]        |5       djjp                  js                         dj                  iddn dgz         dS c c}w c c}w c c}w # t&        $ r#}t        j)                  d|        Y d}~d}~ww xY w# t&        $ r#}t        j)                  d|        Y d}~d}~ww xY w# t&        $ r#}t        j)                  d|        Y d}~d}~ww xY w# t&        $ r!}t        j)                  d$|       Y d}~d}~ww xY w# 1 sw Y   XxY w# tX        j^                  t`        f$ r#}3t        j!                  d>|3        Y d}3~3d}3~3ww xY w# t&        $ r#}t        j!                  d`|        Y d}~Ed}~ww xY w# t&        $ r#}t        j!                  df|        Y d}~td}~ww xY w# t&        $ r#}t        j)                  di|        Y d}~~d}~ww xY w# t&        $ r#}3t        j!                  d|3        Y d}3~3	wd}3~3ww xY w# t        $ r#}3t]        |5       dt;        |3      dcY d}3~3S d}3~3ww xY w# tj        j                  $ r( t        j                  d       t]        |5       dddcY S w xY w# tX        j^                  $ r  djj                  js                         i}kY w xY w# t&        $ r#}t        j)                  d|        Y d}~dd}~ww xY w# t&        $ r	 Td   }uY 4w xY w# t&        $ r#}{t        j!                  d{        Y d}{~{vS d}{~{ww xY w# t&        $ r Y w xY w)un  작업을 독립 세션으로 디스패치

    목차→요약→상세 원칙:
    - task_desc를 memory/tasks/<task_id>.md에 저장 (요약 파일)
    - 프롬프트 본문에는 파일 경로만 포함 (build_prompt 내에서 처리)
    - session_id: 아누의 현재 세션 ID. 전달 시 followup이 동일 대화에서 실행됨.
    - task_type: 작업 유형 (coding/research/check). coding일 때만 QC 검증 포함.
    - allowed_resources: capability snapshot 데이터 (task-2364 P0). None이면 snapshot 미저장.
    - allow_no_scope: True면 allowed_resources 없어도 허용 + audit log 생성.
    Nr   1   resume_from 파일이 존재하지 않습니다: rm  r   r   uL   ## 이전 세션 요약
아래 요약을 읽고 이어서 작업하세요.

r  u0   team_id 또는 composite_teams 중 하나 필수u2   team_id와 composite_teams는 동시 사용 불가r   r   )r   rk  r   rH  uh   [injection_guard] task_desc에서 위험 패턴 감지 (차단 없음, 워크플로우 호환): threats=uG   [injection_guard] task_desc에서 낮은 위험 패턴 감지: threats=rJ  r  rK  r  z, task_type=rL  rM  rN  rO  rP  u   위임 시작: team=r  r   r   r  r  u>   [session-health] CRITICAL 세션 감지: task=%s, usage=%.1f%%	usage_pctg        u2   [session-health] 세션 체크 실패 (무시): %s)FeatureFlagLoaderrw_isolation_enabledrb  ztask-(\d+)\.rB  zscoped-rJ   r   r   r   r   r   u   같은 팀(rY  z ('r>  r   rW  uN   '). 완료 후 재시도하거나 --force 플래그로 강제 실행하세요.u   [force] 같은 팀(rZ  z'). u   논리적 팀 자동 --forceu   --force 플래그로u    강제 진행.r[  r}  rQ  rR  rS  r   rT  rU  rV  	--projectz--work-levelTr~  rX  r   rN   u8   프로젝트 디렉토리가 존재하지 않습니다: r  r_  Fi  rJ  rz  zproject-map.pyz--outputr\  r]  r^  r_  z[file-size-check] codingr   )r   r4  rY  rZ  u   [capability] snapshot 저장: u.   [capability] snapshot 저장 실패 (무시): rn  z.allow-no-scope.logu2   --allow-no-scope 플래그로 legacy 호환 통과)r   
allowed_atra  reasonz)[capability] --allow-no-scope audit log: u7   [capability] allow-no-scope audit log 실패 (무시): ra  rc  rf  z	[qc-env] r  r   r	  r  r  rh  ri  u+    작업에 sanitize 게이트 지시 삽입z[affected_files] r  u   [level-estimate] ⚠️ Lv.(u   ) 지정했지만 u    — Lv.u    이상 권장)r  u(   

## ⚠️ 참조 파일 크기 주의
u   
- 위 파일은 반드시 offset/limit 파라미터를 사용하여 분할 읽기하세요.
- 요약 파일(*.summary.md)이 있으면 원본 대신 요약 파일을 먼저 읽으세요.
- 한 번에 200줄 이상 읽지 마세요.r  c              3   :   K   | ]  }d |d    d|d    d  yw)z  - r   r  rr  u   줄)Nr=   )r  lfs     r2   r  zdispatch.<locals>.<genexpr>y  s'      _B46
|2bk]$!G _s   u@   

## ⚠️ 대형 파일 프로토콜 (2000줄 초과)
대상:
u  
1. 전체 읽기 금지 → offset/limit으로 수정 대상 ±200줄만 읽기
2. Edit 전 수정 위치의 정확한 라인 번호 확인
3. Edit 후 반드시 grep -n으로 변경 반영 확인
4. 한 번에 50줄 이상 삽입 금지 → 여러 Edit으로 분할
u  

## ⚠️ InsuRo 백엔드 변경 — systemd reload 필수
이 task는 InsuRo FastAPI 서버(`server/main.py` 계열) 코드를 수정합니다.
PR 머지 후 또는 코드 변경 직후 반드시 아래 명령으로 uvicorn을 재시작하세요:
```
systemctl --user restart insuro-api.service
systemctl --user status insuro-api.service --no-pager | head -10
```
재시작을 누락하면 새 endpoint가 404로 응답합니다 (2026-05-01 사건 재발 위험).
z[insuro-reload] task u3   : InsuRo 서버 변경 감지, reload 안내 삽입

r|   u?   

## 🎨 이미지 스킬 라우터 추천
- 추천 스킬: **u   **
- 이 추천은 image-skill-router.py의 `get_skill_recommendation()` 결과입니다.
- 추천 스킬을 우선 사용하되, 작업 특성상 다른 스킬이 적합하면 사유를 명시하세요.
u/   [image-skill-router] design 팀 추천 스킬: u-   [image-skill-router] 추천 실패 (무시): rj  rk  rl  rm  u   팀 uH   에 할당된 봇이 없습니다. 봇 토큰을 먼저 등록하세요.r  )r   r   u   [봇 충돌 검사] bot=z, busy_bots=r   r   c              3   8   K   | ]  }|d     d|d    d  yw)r   r  r   rq  Nr=   )r  as     r2   r  zdispatch.<locals>.<genexpr>  s)     1k\]Qx[M1^CTBUUV2W1ks   u   봇 u   가 u    작업(u   )에 점유 중입니다. u   가용 대안: u"   모든 봇이 작업 중입니다.)r   r  available_botsu   [force] 봇 u2   )에 점유 중이지만 --force로 강제 진행.u    [봇 충돌 검사] 통과: bot=u    가용defaultr^  )rx   r   r   rz  u&   [3docs] 작업 3문서 생성 완료: u    

## ⚠️ Agent 미팅 경고
)r  r	  claude-opus-4-6z[opus-upgrade] level=u   , 봇 모델 Opus 승격: u    → claude-opus-4-6r  rn  ro  rp  rq  rr  rs  rt  ru  rv  rx  u   [모델 검증 결과] z: consistent=r   z, org=r   r   r   r   )r   u$   [redact] 마스킹 실패 (무시): ry  z] leaderrz  r{  r|  rR  r  )orderrz  r   r   zchain_manager.pycreatez
--chain-idz--tasksr   z--original-task-fileu)   chain_manager.py create 실패 (chain_id=u)   chain_manager.py create 완료: chain_id=u   위임 완료: r   r}  u?   에게 위임 완료. 즉시 독립 세션에서 작업 시작.)r   r   r   r   r  r>  r  r~  r  r  )r  rv  r  rz  r  r  r  r  r  u   위임 실패: )r  r  r   r   r  u   [fallback] 대체 키(r  u   )로 위임 성공u   )에게 fallback 위임: d   u#   원래 키 실패 → fallback 키()	r   r   r   r  r  r>  r  r~  fallback_usedu<   [opus-upgrade] 디스패치 실패로 모델 즉시 복원: r  r  )r   r   r  r  r'  r  rB  rf  ri  r  r  r  r  r  r  r   r   r  r   r   r   r  r  r  r  r  r  r;  r<  r!   r   _load_running_tasks_load_ledger_tasksr   r   check_sessionutils.feature_flagsr
  
is_enabledr5  rL  rM  r@  r   r$   r   r  r   r,   r  r0  r  r  r   r  r  r   rO  statst_mtimer   r  	timestampr   r  r  r  r  r  r  r   r   r'  rn  r$  rd  r   r  r  r  r5   r  r  rw  r  r  r  r  r(  r  r  r  r  r  r  _IMAGE_SKILL_ROUTER_AVAILABLE_get_skill_recommendationr4   r   r   r  r   r  r   r)   r   r   rc  r   r   r5  r  r   rG  r   r  r   r  r   r  rw  r   r$  r   r  r>   r  rS  r   r  r:  r;  r  r  r  )}r   r1  r  rF  r  r  r  r  r  r   r  rk  r  r  r   r<  r  r  r  r4  r  resume_pathsummary_contentrouting_warningr  r  r  r  r  r  r  r  _sr_running_ledger_rtid_rtinfo_linfo_statusr
  	_rw_flagseffective_agent_type_phases_chain_id_phases_base_num_base_matcheffective_forcer   r1   r  r  r  r   r  r   r  r  project_dirr  needs_refreshmtime	age_hoursscriptfile_size_warningsrz  
_snap_path_events_dir_audit_file_audit_dataqc_env_warningr  _level_to_int_dispatch_level_af_task_lv_overlap_warnings_ow_af_missing_warn
_est_level_est_reason_manual_level_large_files_lf_listplatform_rulesrecommended_skillr   r   r   r  rx   bot_id_metar   busy_bots_infoconflictconflict_task_idconflict_team_idr   available_str_level_to_int_docs_docs_level
_docs_path_meeting_warning_opus_upgraded_opus_original_model_opus_key_hashr  r  r   r  rw  consistencyr  _tasks_json_i_t_id
_task_file_chain_create_cmd_chain_result_log_leaderr  r  r  r  r  r  fallback_results}                                                                                                                                r2   ra  ra    sA   F ;'!!#!N{m\  &///AE  k	 	 ?!.`aa?!.bcc %oa&8)D!'"6Y	5J\]"?Iug]bjopp  -WiAQRO!o>>L"$ 	 ( #IwE "m&?	L(3L'',8,@,@ gqAJJRfDf g g NN#<I#JqANN#J"KM
 KK#<H<P<P#QqANN#Q"RT ~9		E!*1YK8O-o>KK()9)D)D(E FyYK 8,==>@ &+!&=,BZ	L!-i!8KK89K8LGT[S\]^ 
>?Q>RRYZaYbcd
KK&wiz'CD %);)G	S$C	NCC..0H,,.G"*.."2 
w;;y)W4 UB/++E7FC7#z1NNXK5
 6!#I23)& '+&*gii9*003!()9(:; =+< <O X%(::J	Dj#8 -A $		!-/</@/@"/M/S/S/U +m!%%h/9<%)))4?$/*%g.&-"-gY6e#/.M4E4EmUW4XY\Z\4]3^ _m!n   NN-gY6\'.M,=,=mR,PQTRT,U+VVZ=DHY=Y9_uv  wFG
 +4 3BC	NR,?5RHJM M95!m_  EE  /F  G  	GC
OWmXwX`blmI+z23.%01>>)DtUWXL!#:=/\M`M`MfMfMhLijk *,z9!!#-(%4lmxly2z{{ kx'.8j\;MM  M MMO,,E!113e;tCI2~ $#j0:=K!!#"Y.1AA==?NN"CK[1A:sS[}]'+! "	 +r'!_iqQI "'95gy%9KL Iy1)$5i@+,>+?@A ,Is9~FI+IzBIH29c)nM	 H$w.G9C@I4$7W5 $		R2"3%	 5 5i @A	J KK8EF 
	[#h.9KdT:%7)3F(GGK"&lln668i33I>?N	K k;7KKCK=QR
 #6#B	PY!	 (0N>"234GUzH`iF
  !aQ?M#''q1O$4$@EUVeEfK	
 	)/)::efg  	
*C
)#s9~
>C +H
9#wG$ 	6CNN.se45	6&'89mC@3IxH*+;*<=>29cBJ aQ?M!%%eQ/Mz!k)-%@RS^R__klvkw  xF  G	
 mh?9:L9M=>	
 ',yy _R^ __H$: &Z[F #C(x	F KK/y8klm ,I6ND()) (<AZAf
	P 9) D&&7%8 9FGF KKIJ[I\]^ WD ##	:2=QVWL l+ll8$;NN78H7ITU-(%4jkskykyk{j|2}~~" g&x/-(%D	  BJ  3K  L  Lv;NN77GzRS-(%4jkqkwkwkyjz2{||w+$(("5!-[A0INKK*;-wwiGWX\]k]p]p]rXsWtu n,)+6#+I#6 #+I#6 #w.3Cw3N %m4$B>$R	(,		1kaj1k(k&-"&{m48H7IRbQc d8 !9HQ_]O#DW{!} /8   NN&{m48H7IRbQc dH I
 >{m7ST $1	%gYc2	
 %&1!D$((2Ka&w<
KK@MN ,GY|T78H7ILL gy%9KL NN((^-n>OP$8<M$M!NKK'w.HMaLbbvw O 	/*C]DtRP A	6zz&--0H
 	6w-1':%gYmK<U;V W{+4u5VK<T<]X];^`	
 	 2#N4HPRS
 s^I0CuL
J)*5
 gYbh 0':J:,WJ:N		
 x': "2">CSC_ "KAvz* 
 012$7,UG37
""!#%/ '#(	
 I 223 

;U;&
! 'NN+<TX\fhiM''1,?@P?QQTYfYmYmYsYsYuXvw GHXGYZ[8n-*;7 	ogYeK=AB"N$x.))hi%	
 '"2GJ .8$-Gx||D)T+ N
 .7{+!'<^<	S3 )4I8w9O8PPUV]U^_`#,==#9  v}}':':'<&=>? 3	
 &z2HKK01P0QQcde :7L1  "6'8LTVW"3BC	NR4G5RPJ !-j!9J '"T(^$4AgY>WXbWcdGNNC
OUGXzR#$ '"x.(#@Q_A`@aast!)!%
G 2<Hd1K8<<-QUL#/#$!""
 2;N;/%g@@N 2>+?@KKVWeVfghm$!fmm.A.A.CPSPXPXY\]_^_Y`dqcrYrPsttY !h $K
 $R 	LLLEbTJKK	L  	ELL>rdCDD	E  	LLLEbTJKK	L0  	SLLMrRR	S<- -0 $$g. 	DNN>qcBCC	Dr  	RNNKB4PQQ	R   	[NNTUWTXYZZ	[  	PLLI"NOO	PV  	PNNJ1#NOO	P  	:-(%#a&99	:f $$ ]BDm$!.[\\] ## 	6v}}2245H	62  JCB4HIIJf  -"8n-H  	SNNGyQRR	S@ ! s[  %AI ;AIAIAI +AI
>(AI &AI
9	AI AAJ  '&AJ/ BAK %)AK AL AL5AAL 8AL AL 0AL AAL $AAM +BAN AN5 (,AO$ &AP AQ AR  .AR6 7AS% @9AAS: D8AT) IAI I	AI=IAI8I8AI=J 	AJ,J	AJ'J'AJ,J/	AKJ8AKKAKK	ALK'ALLALLALLAL LAML1AMMAMM	ANM AM>M>ANN	AN2NAN-N-AN2N5	AO!N>AOOAO!O$	APO-APPAPP	AP?PAP:P4AP?P:AP?Q8AQ=Q<AQ=R /AR3R2AR3R6	AS"R?ASSAS"S%AS7S6AS7S:	AT&TAT!T!AT&T)	AT6T5AT6c           
          ddl }g }i }|j                  d|       sdddS t        dz  dz  |  d	z  }d
}|j                         ro	 |j	                  d      }|r||j                         d   vrDn	 |j                  | d| d       |j                  d       t        j                  d|  d       nt        j                  d|  d       t        dz  dz  }|j                  dd       ||  dz  }		 | t        j                         j!                         dd}
t#        |	dd      5 }t%        j&                  |
|dd       ddd       |j                  d       t        j                  d|  d       t        dz  d!z  }d"}|j                         r	 t#        |d#d      5 }t%        j(                  |      }ddd       j+                  di       j+                  | i       }|j+                  d$d"      }|j+                  d%d"      xs |j+                  d&d"      }|j-                  d'      rd(d)d*d+d,d-d.d/d0}|j+                  |d"      }n#|j/                  d1      r|j1                  d1d"      }|rd2d3|d4t2        d5t4        j6                  j+                  d6d"      g}t9        j:                  |ddd78      }|j<                  dk(  r.|j                  d9       t        j                  d|  d:| d;       nj|j>                  jA                         |d<<   t        j                  d|  d=|j>                  jA                                 nt        j                  d|  d>       |rt4        j6                  j+                  d@|jC                          d"      }|r	 |  dA}d2dB|dCtE        dD      d4t2        d5|dEg
}t9        j:                  |ddd78      }|j<                  dk(  r.|j                  dF       t        j                  d|  dG| dH       nP|j>                  jA                         |dI<   t        j                  d|  dJ|j>                  jA                                 n6t        j                  d|  dG| dK       nt        j                  d|  dL       	 t9        j:                  dMt        tF              dN| dOdPgddd78      }|j<                  dk(  r+|j                  dQ       t        j                  d|  dR       n3t        j                  d|  dS|j>                  jA                                 dT| |dU}|r||dV<   |S # t        $ r4}t        |      |d<   t        j                  d|  d|        Y d}~d}~ww xY w# 1 sw Y   4xY w# t        $ r4}t        |      |d<   t        j                  d|  d |        Y d}~Ed}~ww xY w# 1 sw Y   xY w# t        $ r4}t        |      |d<<   t        j                  d|  d?|        Y d}~d}~ww xY w# t        $ r4}t        |      |dI<   t        j                  d|  dJ|        Y d}~d}~ww xY w# t        $ r&}t        j                  d|  dS|        Y d}~Sd}~ww xY w)Wu2  진행 중인 task를 강제 취소한다.

    동작 순서:
    1. task_id 형식 검증
    2. task 파일 STOP 마커 prepend
    3. .cancelled 이벤트 마커 생성
    4. schedule_id 조회 → cron 제거
    5. 봇에게 즉시 중단 메시지 cron 발송
    6. task-timer end (CANCELLED)
    r   Nz
^task-\d+$r   zinvalid task_idrm  rJ   r   r_  u0   ★★★ 작업 취소됨 (CANCELLED) ★★★r   r   Tr  stop_marker_addedz	[cancel] u'   : task 파일 STOP 마커 추가 완료stop_markeru)   : task 파일 STOP 마커 추가 실패: u$   : task 파일 없음 (마커 스킵)rn  r   z
.cancelledzmanual --cancel)r   cancelled_atr  r   Fr   r   cancelled_eventu!   : .cancelled 마커 생성 완료u#   : .cancelled 마커 생성 실패: r   r   r   r  r   r   zbot-rY   rZ   r[   r\   r]   r^   r_   r`   rn   r  rn  z--cron-removerq  rr  rO   rW  r~  cron_removedu"   : cron 제거 완료 (schedule_id=rq  cron_removeu   : cron 제거 실패: u+   : schedule_id 없음 — cron 제거 스킵u   : schedule_id 조회 실패: COKACDIR_KEY_u    작업이 즉시 중단되었습니다. 진행 중인 모든 작업을 멈추고 .cancelled 마커를 생성한 뒤 종료하세요. finish-task.sh를 호출하지 마세요.ro  rp  r  rs  bot_notifiedu   : 봇(u    ) 중단 메시지 발송 완료
bot_notifyu&   : 봇 중단 메시지 발송 실패: u(   ) 키 없음 — 중단 메시지 스킵u6   : 봇 이름 파악 불가 — 중단 메시지 스킵r   r  z--qc-result	CANCELLEDtimer_endedu!   : task-timer end CANCELLED 완료u"   : task-timer end 실패 (무시): rt  )r   r   actionsfailed_actions)$r5  r   r   r   r  rN  r'  r   r   r   r   r!   r   r   r   r  rd  r   r$   r%   r   r   r  r  r)   r   r"   r  r   r  r  r   rO  r  rG  r  )r   r(  rt  ru  rz  rj  r~   r   
events_dircancelled_pathcancelled_datar1   r   bot_namer.  r   r  bot_letter_map
cmd_remover   bot_key
cancel_msg
cmd_notifyresult_dicts                           r2   cancel_taskr    s    G%'N 99]G,!.?@@ H$w.G9C@IDK	^))7);G=D{'"4"4"6q"99$$$}D	%BW$U23iy0WXY
 	7)+OPQ X%0JTD1WIZ"88NT$LLN446'

 .#8 	GAIInaeAF	G()iy(IJK X%(::JH"	Rj#8 *A!YYq\
*#488"EJ$..;K!~~eR0QJNN9b4QH""6* $fvPV#fvPV" *--h;""7+#++GR8gRZZ^^,>C

 $
4d\^_$$)NN>2KK)G94VWbVccd ef4:MM4G4G4IN=1NNYwi7MfmmNaNaNcMd#efiy0[\] **..=1A0B!CRH_i  t u 
 *-b1gW
 $
4d\^_$$)NN>2KK)G9F8*Dd ef39==3F3F3HN<0NNYwi7]^d^k^k^q^q^s]t#uv
 NNYwivhZ?ghi7)+abcSJT	
 !NN=)KK)G9,MNONNYwi/QRXR_R_ReReRgQhij
 K
 (6$%m  	^,/FN=)NNYwi/XYZX[\]]	^ 	G 	G  T,/F()7)+NqcRSST* *>  	R,/FN=)NNYwi/LQCPQQ	R6  _/21v|,7)3YZ[Y\]^^_*  S7)+MaSQRRSs   A-T4 44V (U42V W "W8F!W CX BY 4	U1=)U,,U14U>9V 	V>
)V99V>WW 	X)XX	Y)YY	Y=Y88Y=c            
      P   ddl m}   |         t        j                  d      }|j	                  dddd	       |j	                  d
dd d       |j                  d      }|j	                  dg dd       |j	                  dd       |j                  d      }|j	                  dd       |j	                  dd       |j	                  ddg dd       |j	                  dd g d!d"       |j	                  d#d d$%       |j	                  d&d d'%       |j	                  d(d d)%       |j	                  d*t        j                  d+d,	       |j	                  d-d d.%       |j	                  d/d d0%       |j	                  d1t        d d23       |j	                  d4ddd5	       |j	                  d6d d7d89       |j	                  d:d;gd d<=       |j	                  d>ddd?	       |j	                  d@dddA	       |j	                  dBdCdDgdDdE=       |j	                  dFd dG%       |j	                  dHdddIdJK       |j	                  dLd dMdN9       |j	                  dOdddPdQK       |j	                  dRd dS%       |j	                  dTdddUdVK       |j                         }|j                  r,t               }t        t        j                  |ddWX             y |j                  r7t        |j                        }t        t        j                  |ddWX             y |j                  s|j                   s|j#                  dY       t%        |dZd       r_|j                  s|j#                  d[       t'        |j(                  |j                        }t        t        j                  |ddWX             y |j*                  s6|j,                  s*t%        |dZd       s|j.                  s|j#                  d\       d]}|j*                  r|j*                  }n2|j,                  r&	 t1        |j,                        j3                  d^_      }d`|v sda|v r8t6        j9                  db       t        t        j                  dcdddedf             |j,                  rt1        |j,                        }|j;                         sEt        t        j                  dgdh|j,                   dedf             t=        j>                  di       |j3                  d^_      jA                         st        t        j                  dgdj|j,                   dedf             t=        j>                  di       n@|j*                  xs d]tC              dkkD  r"t6        j9                  dltC               dm       |jD                  d;k(  rM	 ddnl#m$}  ||jJ                  xs dop      }	|	 dq |jL                  sdr|_&        t6        jO                  ds       |jD                  eg du}tS        fdv|D              rMt%        |dwd      rt6        j9                  dx       n*t6        j#                  dy       t=        j>                  di       |jT                  jt1        |jT                        }|j;                         sEt        t        j                  dgdz|jT                   dedf             t=        j>                  di       d }|j                   r	 tW        |j                         }|j.                  r |jJ                  r8t        t        j                  dgd{dedf             t=        j>                  di       |j                  s8t        t        j                  dgd|dedf             t=        j>                  di       |j*                  s|j,                  st]        j^                  d}d]|j.                        }t1        t`              d~z  dz  | dz  }|j;                         r!|j3                  d^_      jA                         n;t        t        j                  dgd| dedf             t=        j>                  di       tc        |j.                  |j                  dte               v rnd]|jf                        }|d   dgk(  r5t        t        j                  |df             t=        j>                  di       |d   |_%        t6        jO                  d|j.                   d|jJ                   d|d    d       |jJ                  r[t]        jh                  d      }|jk                  |jJ                        s+t6        j9                  d|jJ                  |jl                         rto              nd }|Ft%        |dUd      s9t        t        j                  dgddeddWX             t=        j>                  di       tq        di d|j                  dd|jr                  d|d|jt                  d|jv                  d|jx                  d|jz                  d|j|                  d|jJ                  d|j~                  d|jf                  d7|jT                  d|j                  d|jL                  dI|j                  dM|j                  d|j                  dP|j                  d|dUt%        |dUd      }t        t        j                  |ddWX             y # t4        $ r Y w xY w# tP        $ rF}
t        t        j                  dgdt|
 dedf             t=        j>                  di       Y d }
~
Od }
~
ww xY w# tX        $ rL}
t        t        j                  dgt[        |
      dedf             t=        j>                  di       Y d }
~
d }
~
ww xY w)Nr   r	   u   작업 위임 디스패처)r>  z--check-sessions
store_trueFu0   모든 running 세션의 토큰 사용량 체크)actionr  helpz--cancelTASK_IDuc   진행 중인 task를 강제 취소 (STOP 마커 + .cancelled + cron 제거 + 봇 중단 메시지))metavarr  r  )requiredrU  )rf   rg   rh   ri   rj   rk   rl   rm   r   r   r   r|   r~   u   위임할 팀)choicesr  z--compositeuE   쉼표 구분 논리적 팀 ID 목록 (2~3개, 예: marketing,design))r  z--tasku%   작업 설명 (짧은 한 줄 용도)z--task-fileuG   작업 설명 파일 경로 (권장: 긴 내용은 반드시 파일로)z--levelr  r  u(   검증 레벨 (normal/critical/security))r  r  r  r|  r  )r  r  rK  u[   작업 유형 - coding: QC 검증 포함, research/check: QC 검증 제외 (기본: coding)z	--sessionu9   아누 세션 ID (followup을 현재 대화에서 실행))r  r  r  u2   프로젝트 ID (projects/ 하위 디렉토리명)z--chainu   체인 ID (chain.py 연동)z--refresh-mapTuQ   프로젝트 맵 자동 갱신 (24시간 이상 오래된 경우, 기본: 자동)z	--task-idu8   태스크 ID 직접 지정 (미지정 시 자동 생성)z--resumeu[   재시도할 base task ID (예: task-2133). 자동 채번 후 기존 task 파일 재활용.z--phasesuf   한정승인(scoped delegation) Phase 수. 지정 시 chain_manager.py create로 체인 자동 생성.)r  r  r  z--forceuW   동일 팀에 running 태스크가 있어도 강제로 dispatch 허용 (기본: 거부)z--resume-fromr  uj   이전 세션 요약 파일 경로. 지정 시 task_desc 앞에 요약을 prepend하여 새 세션 시작.)r  destr  z
--workflowzimage-qc-gateuS   워크플로우 적용 (image-qc-gate: 이미지 QC 게이트 5Phase 자동 적용))r  r  r  z--skip-qc-gateua   이미지/광고 작업의 QC 게이트를 의도적으로 스킵 (제이회장님 승인 필수)z--skip-meetinguH   Lv.4 Agent 미팅 체크를 의도적으로 스킵 (로그에 기록됨)z--agent-typereadrb  uo   에이전트 유형: read(읽기 전용, worktree 미생성) | write(쓰기, worktree 생성). 기본값: writez--modeluW   모델 강제 지정 (예: claude-opus-4-6). 지정 시 해당 모델의 봇만 선택.z--override-routingr<  u?   라우팅 경고를 무시하고 지정 팀으로 강제 위임)r  r  r  r  z
--batch-idr  uY   배치 ID. 병렬 위임 시 동일 batch_id를 부여하여 전팀 완료 추적 가능.z--skip-brainstormingr  u?   Lv.3+ UX 작업의 brainstorming 사전 실행 체크를 스킵z--prduW   PRD 파일 경로. Phase별 task 파일 자동 생성 (위임 없음, 파일 생성만)z--allow-no-scoper  u_   task 파일에 allowed_resources 미명시 시 통과 (legacy 호환). audit log 자동 생성.r   r   uK   --check-sessions이 없으면 --team 또는 --composite이 필수입니다.r  u+   --prd 사용 시 --team이 필수입니다.u]   --check-sessions이 없으면 --task, --task-file, --prd, 또는 --resume이 필수입니다.r   r   r   u   레벨: Lv.0u   ## 레벨: Lv.0um   ⚠️ Lv.0 마이크로 수정은 dispatch 불필요! 아누가 Task tool (haiku)로 직접 실행하세요.r   uF   Lv.0은 dispatch 불필요. Task tool (haiku)로 직접 실행 권장.rm  r  r   u3   작업 설명 파일이 존재하지 않습니다: rB  u,   작업 설명 파일이 비어있습니다: r  u   --task 직접 전달 u;   자 — 긴 내용은 --task-file 사용을 권장합니다)build_workflow_overview_promptauto)r   r  r  u3   [workflow] image-qc-gate: opus 모델 강제 적용u&   워크플로우 모듈 import 실패: )r  u   광고r  r  r  r  c              3   &   K   | ]  }|v  
 y wr<   r=   r  s     r2   r  zmain.<locals>.<genexpr>  s     92rY9r  skip_qc_gateuV   ⚠️ --skip-qc-gate로 이미지 QC 게이트 우회. 제이회장님 승인 필수.u   ❌ 이미지/광고 작업에 --workflow image-qc-gate가 필수입니다. 의도적 스킵 시 --skip-qc-gate 플래그를 추가하세요.r  u-   --resume과 --task-id는 동시 사용 불가u.   --resume 사용 시 --team이 필수입니다.ro  rJ   r   r_  u"   base task 파일이 없습니다: r1  )rk  r   ru  z	[resume] r   rs  rv  rq  z'^task-\d+(_\d+\.\d+)?(_[a-z])?(\+\d+)?$uV   [task-id-format] --task-id '%s'가 포맷 v2 규칙에 맞지 않습니다. 패턴: %su   task 파일에 allowed_resources YAML 블록이 없습니다. memory/specs/bot-capability-model.md 참조하여 추가하거나, legacy 호환이 필요하면 --allow-no-scope 플래그를 사용하세요.r   r  rF  r  r  r  r  r  r   r  rk  r  r   r  r4  r=   )Eutils.env_loaderr
   argparseArgumentParseradd_argumentadd_mutually_exclusive_groupBooleanOptionalActionrP  
parse_argsr?  r  r$   r  cancelr  r   r1  r   getattrr  r  r  rz  resumer   r  r   r   r   r   r  r  rO  r  workflowprompts.image_workflowr  r   r   r   ImportErrorr  r  rE  r%  r!   r5  r   r   r  r   rk  r  r   r  r?  ra  r  sessionprojectchainr  r  r  r  r<  r  r  r  )r
   parserteam_or_composite
task_groupargsr   task_contenttask_file_pathr  workflow_promptr   _image_keywordsr)  composite_teams_list
base_cleanbase_task_fileresolve_resultTASK_ID_V2_PATTERN_allowed_resourcesr1  s                      @r2   mainr  ~  sk   .O$$1MNF ?	   r	   ;;U;K""
 # # & ""T #  44e4DJH+RSM0yz
27	   /j	   T8st
T8lm
	46ST
--`	   T8rs

D  8U  V
u	   f	   y	    !b	   p	   W	   !~	   f  
 N   h	   !N   f  
 '  	 D !djjeA>? {{T[[)djjeA>? 99T^^bc tUD!yyLLFGDHHdii0djjeA>? 99T^^GD%4NW[WbWbtu Lyyyy		/9979KL %):l)J  G  	HJJ$1yz"	
 ~~dnn-$$&

&5himiwiwhx3yz!& HHQK",,g,>DDF	

&5abfbpbpaq3rs!& HHQKIIO	y>CNN23y>2BB}~ }}'	M<.O ++;ykBI::.
QR }}[999t^U3wxX  #4++,!!#

")%VW[WgWgVh#i "' HHQK  ~~	#<T^^#L  {{<<$**<kl  |A  B  CHHQKyy$**<lm  }B  C  DHHQKyy	2t{{;J!)_x7'AzlRUDVVN$$&*44g4FLLN	djjGBdesdt@u!v  FK  L  M(diik]`]bNbhjrvr|r|}(#w.$**^%@AHHQK%m4i}E$,,yXeIfHgghij||ZZ(RS!''5NNh"** AJ1)<t!'$8H%*Pdjj!c 
 	 	  		 jj -	
 << <<  $$ ))  {{ jj $$ ?? jj  ..!" #$ &&%&  22'( -)* t%5u=+F. 
$**V%
:;g  		d  	

&5[\][^3_`!& HHQKK	T  	$**CFCRWXYHHQKK	sD   %k. -Ak> /m .	k;:k;>	m;mm	n%An  n%__main__r<   )rW  )r   N)F)r  )r  NNr  )i  )r  NFN)Nr   r  NNNNTr  NNFNrb  NFNFFNF)__doc__r  r   re  r$   r"   r5  r   r  r   r   pathlibr   typingr   r   r   r   r  r!   __file__rM  r   r  r
   r   utils.atomic_writer   config.loaderr3   get_instancer+  r*  r  r4   r5   r  utils.loggerr6   r  utils.composite_constantsr7   r8   __name__r   utils.redactr9   r>   r  utils.injection_guardr@   r  r  utils.approvalrA   r  r  utils.audit_loggerrB   r  r  utils.model_routerrC   r  r  utils.sanitize_gaterD   r  r  utils.bot_statusrE   r   r   utils.session_resiliencerF   r<  r;  importlib.utilutil_iluspec_from_file_location	_isr_specmodule_from_spec_isr_modloaderexec_moduleget_skill_recommendationr(  r'  get_path_ws_fallbackr  r   r   r   r  r,  _chat_fallbackr   r  r   r  r   utils.org_loaderra   rb   rc   r   r  r   
_teams_map_team_to_bot_mapr;  r   CROSS_FUNCTIONALr@  r   r   r   rc  r   r   r   r   rP  r   r   r$  r(  r  r0  r?  r<  rn  rw  r  r  r  r  r  r  r  r  r  r  r  r  tupler  r  r  r5  r?  rC  rG  rP  rZ  rf  ri  r  r  r  r  r  r  r  r  r  r  r  r  r'  r/  r;  rB  rE  r  r  r  r  r  ra  r  r  )r   r   kvs   0000r2   <module>r     s  ,     	 	  
 (  & &	HHOOAs4>113::AABC.O
42+%=%%'D(.G'
 
H		B'C!%
 >
$L"
$>"
%G#
"F 
*P$(!
*!,,,DN!!G+.EEFI %t$$Y/H  * ( A A$(! 4Et}}./Sh 0,?@	x"??!O3
 2Ct""9-Q]
**..+^
<$  DIIK)+j8;bbemm  ::>>,-JJNN./JJNN./JJNN./JJNN./JJNN./JJNN./JJNN./JJNN./
-
cc!#H%'J-/NX '
?'1CD*4IJ*H=	  S  )7(<(<(>?1q!t? 't 'tCcSVhDW?X 'T)`d
 )`c )`Xd3S#X3F.G DQUVY[^V^Q_L` 0 "&ff$Jf 	fR %, c S#X 0 >E !S ! !s !t !Hec e# ec e4 e*B B BJf`R3 C D <c s t 2   t  FC?s C?td{ C?T #	      t	 
 
 P+S +T +\<D <# <RV <~T d  !$ ! !QU !HJT Jd J*C S ( [ *3 * * *Zc 3 3 6<$ <4 <~'ZS 'ZT 'ZV C  % D  6# 6# 6c 6QU 6bjknbo 6tBs B3 B3C BJ >S T 8+S +# + 6%T %c %PD# DNS T :SX# SX SX SXD SX]a SXl,3 ,HSM ,c ,f  $"  	
  sm  	$! !s !S !T !H23 24 2j
S 
T 

3 
 
C 
TX 
"(5C (5HSM (5V" " "JT c T .Bc Bhsm BJ)
s )
s )
t )
XM
3 M
3 M
4 M
`#s #s #3 #\` #mq #LT @#1S #1Xc] #1L'C 'C '4 '\deh\i 'T c 8 !Lu#YLuLu Lu c]	Lu
 Lu C=Lu 
Luh9# 9$t* 9x; ;# ;$t* ;|g gs gt g^ GGG G 	G
 G d^GV "+/ $ $"! !%""$(, +euc]eueu eu d3i(	eu
 eu eu smeu eu eu c]eu SMeu eu #eu eu C=eu  !eu" sm#eu$ %eu& 'eu(  ~)eu* +eu, 
-euZT T Tnx<v zF ]F  		   3 s t :  D  (.G''("  3 3    'M!&'   N   $#$  $L#$  %$%  "!"  *$)!*  * $$)!*P  '
T&&w/
,,];
#./CSCYCYC[\<4fj..\\
  	
 	

 !       	
='
j @s  AZ2 Z> [ 5[& \ !\ *\* 3\: <]
 ] ]* ]:  A0^
 ^ "`,2Z;:Z;>[[	[#"[#&\ \\\	\'&\'*	\76\7:	]]
	]]	]'&]'*	]76]7:	^^
	^^A`)(_98-`)(`)