
    ci             /          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ZX	 dd!lYmZZ[ dd#Z\e rerej                  d$      nd%Z^ eej                  j                  d&e^            Zaead'z  d(z  Zbead'z  d)z  Zce rerej                  d*      nd+Zeej                  j                  d,ee      Zfd-Zg ej                         d.z  d/z  d0z  d'z  Ziej                  j                  d1      ej                  j                  d2      ej                  j                  d3      ej                  j                  d4      ej                  j                  d5      ej                  j                  d6      ej                  j                  d7      ej                  j                  d8      ej                  j                  d9      d:	Zj	 dd;lkmlZlmmZmmnZn  em       Zo el       Zp en       ZqdPdQdRdSdTdRdUdVdRdWdXdRdYZvh dZZweqj                         D ci c]  \  }}||
 c}}Zxdd[edz  deteeteef   f   fd\Zydd]edz  defd^Zzd_eteeteef   f   de{eteef      fd`Z|	 ddaed]edz  defdbZ}effd*edeteef   fdcZ~effddedeed*ededz  fdfZdddedeedgeddfdhZdiedetfdjZddkZdledmeddfdnZdledoedefdpZdqedetdz  fdrZdsedetdz  fdtZ	 ddleduetdvedwedz  def
dxZdqede{fdyZdze{d{ede{fd|Zdze{defd}Zdze{d~ede{fdZde{ddfdZdqedefdZh dZdqed{edefdZdqed{edefdZdze{detfdZdedetfdZh dZg dZdqedze{defdZdqedefdZddledqedededee   f
dZdlededdfdZdetfdZdedefdZddedefdZdefdZdedefdZdefdZdleddfdZddediedqededetf
dZdqedee   defdZ	 	 	 	 ddiedqedlededee   dee   dedefdZ#dediedleddfdZdleddfdZdqeddfdZdqedlededdfdZdqedee   fdZdiedee   fdZddze{dede{fdZdqedee   fdZdqededdfdZdiedqeddfdZddledqedededdf
dZdetfdZdqedee   fdZddiedqededee   fdZdedee   fdZ	 	 	 	 ddee   dqededlee   dedeee   detfdZdede{et   fdZdedede{et   fdZdediedetfdZ	 ddedediedlededeet   fdÄZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddiee   dqededeee      dee   dee   dee   dededlee   dee   dedee   dedeee   dedee   dededueet   dedee   detf.d˄Zdledetfd̄Zdd̈́Ze+dk(  r e        yy# e$ r Y 8w xY w# e$ r ded	ed
eddfdZY Iw 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 Jw xY w# e'$ r dedefdZ/dZ0Y Gw xY w# e'$ r dZ3dZ4Y Nw xY w# e'$ r dZ7dZ8Y Uw xY w# e'$ r dZ;dZ<Y \w xY w# e'$ r dZ?dZ@Y cw xY w# e'$ r dZCdZDY jw xY w# e$ r dZGdZHY qw xY w# e'$ r dZKdZLY xw xY w# e$ r dZWdZXY w xY w# e$ r g d"Z[Y  w xY w# e'$ r e rceraej                  d<      Zrej                  d=      Zs eter      Zo etes      Zqesj                         D  ci c]  \  } }|er|     nc c}} w c}} Zpn!d>d?d@dAdBdCdDdEdFZod>d?d@dAdBdCdDdEdGZpdHdIdJdKdLdMdNdOdFZqY 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-2481-dev4/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.py)FALLBACK_DESIGN_KEYWORDS)	   디자인u   배너u	   이미지u	   포스터u   일러스트u   광고bannerimageposterdesignillustrationadc                     t        | dd      }|r|S t        | dd      }|r9	 t        |      j                  }|j                         j	                  d      r|S 	 yy# t
        $ r Y yw xY w)ue   audit JSONL 추적용 task_id 추출. args.task_id 우선, 없으면 task_file basename에서 추출.task_idN	task_filetask-)getattrr   stemlower
startswith	Exception)argsexplicitrS   rV   s       r2   _resolve_audit_task_idr\      sw    tY-Hk40I		?''Dzz|&&w/ 0   		s   5A 	A'&A'z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_botrm   rn   ro   rp   rq   rr   rs   rt   )	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-ir   r   r   r   r   r   r   r   u   마아트 (Ma'at)u   QC 매니저)nameroleu   로키 (Loki)u   레드팀 리더u   비너스 (Venus)u   디자인 디렉터u   야누스 (Janus)DevOps)qcredteamrN   devops>   rN   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   r^   task-timers.jsonrr   r   tasksstatusrunningteam_id )rR   r   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   rR   
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: 조건에 맞는 가용 봇이 없을 때
    r   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   V  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"}, ...]
    r   r   )r   r   )appendr   r   )r   all_bots	availabler   s       r2   _get_available_bots_with_teamsr     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: 조건에 맞는 가용 봇이 없을 때
    r^   r   .task-timers.lockTr   Nr   r   r   r   r   r   r   r   r   r   u-   task-timers.json 읽기 실패 (봇 예약): r   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closerY   )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!   rY   r   debug)
r_   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   rY   error)
r   r   r_   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columnsrx   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   rY   )r   r   r  r1   org_datarx   r   r	  subr  r   r   r   r   r   r   s                   r2   _validate_model_consistencyr  R  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***r^   zbot_settings_sync.jsonTr   r   Fr   r   u&   [bot_settings_sync] 동기화 완료: u/   [bot_settings_sync] 동기화 실패 (무시): configconstants.jsonr_   r   display_nameusernamer   last_sessions_   devrl   )r  r  team_dirr   bots-teamrx   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   rY   r   r   splitlenrX   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_settingsr4    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rR   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 항목에 메타데이터 추가r^   r   r   Tr   Nr   r   r   r   r   u   메타데이터 패치 완료: r   u   메타데이터 패치 실패 (r   )r   r   r   r   r   r   r   r   r   r   rY   r$   r   r   updater   r   r   r   )	rR   r5  r   r   r   r1   r   r   r   s	            r2   _patch_timer_metadatar8     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). 호출자는 후속 작업을 중단해야 함.
    r^   r   r   r   NTr   r   )archived	escalatedz	: status=u    영구 박제 — 'u   ' 갱신 차단F)	r   r   r$   r   r,   r   r   r   r   )rR   r9  timers_filer1   
timer_datacurrents         r2   _set_task_statusr@    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dictrY   _parse_allowed_resources_regex)rA  fenced_blocksblockrH  r   s        r2   _parse_allowed_resourcesrO  7  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.rC  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)
rE  searchgroup
splitlinesstriprX   r  r#  intr   )
rP  r   paths_matchlineinline_matchpfp_match	cmd_matchmp_match	ttl_matchs
             r2   rL  rL  Z  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rD  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 파일 경로
    r^   capabilitiesTr   .jsonrV  zmemory/capabilities/**memory/tasks/.md)rR   captured_atsourcesource_sha256rD  )r   r   r   listr   r   rK  r   r*  	isoformathashlibsha256encode	hexdigestr   )	rR   rD  ri  rj  snapshot_dirsnapshot_file	forbiddenenrichedsnapshot_datas	            r2   _save_capability_snapshotr~    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:NrU  Fz##- r   z\s*\(.*?\)\s*$r   ## affected_filesT)r^  r_  rX   r$  r#  r   rE  r  )
rA  linesrb  strippedvaluer1   
collectingr   r   items
             r2   _parse_affected_filesr    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   r_  r$   loadsr   r   r   r   r   r   r$  TimeoutExpiredr   rY   )r  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!   rX   ).0rd  s     r2   	<genexpr>z+_is_insuro_server_change.<locals>.<genexpr>?  sJ       
SV	#kA(9(9)(DkHaehijekHkks   AA)anyr  s    r2   _is_insuro_server_changer  5  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는 제외(자기 자신).
    겹침 발견 시 경고 메시지 문자열 리스트를 반환한다.
    파일이 없으면 빈 리스트를 반환한다.
    r^   r   r   r   r   Nu@   [_check_affected_files_overlap] task-timers.json 읽기 실패: r   r   r   r  r   u   [파일 충돌 경고] u   (running)와 파일 겹침: )r   r   r   r$   r   rY   r   r   r   r   r   joinsortedr   )r  r  r   r1   r   r   warningsr   affected_setrR   	task_infoother_filesoverlapoverlap_lists                 r2   _check_affected_files_overlapr  E  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)r_   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   rw  requestRequesturlopenr   r$  rY   )r  	bot_tokenmessageurlliburlr   reqr   s           r2   _send_overlap_telegram_warningr  i  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가 기재되지 않았습니다. 영향받는 파일 목록을 추가해 주세요.)r  )rA  r  filess      r2   _warn_missing_affected_filesr  ~  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 미기재 시, 백틱 코드 토큰 기반 자동 탐지r  z`([A-Za-z_]\w*(?:\(\))?)`>   idr  r   r  rs  r   r   typer   eventindexr   propsstater  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)r  Nr=   )r  r1   s     r2   r  z._auto_inject_affected_files.<locals>.<genexpr>  s     GkUV"QCGks   )rE  rF  rstriprW   rs  rK  fromkeysr   r   r  r   r_  r#  r   r"   r   relpathr  r   r   r$  r  r  )
rA  r   tokensCOMMON_FILTERtaffectedr  r   rb  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`
)rE  rF  r#  ALLOWED_COMMANDSr   )rA  r   rW  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   r   r   r  r  r   r   r   r$   r  r   r   extendr  r   rY   rs  rK  r  r$  )r  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 목록
        }
    r^   r   Fr   )completetotaldonependingr   r   r   Nu9   [check_batch_completion] task-timers.json 읽기 실패: r   r  r   r   )r  	completedrR  )r   r   r   r$   r   rY   r   r   r   r   r$  r   )r  r   r1   r   r   r   rR   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 이상 권장)rR  r   )_HIGH_LEVEL_KEYWORDSr   r   _CORE_FILESr$  )rA  r  keywordr1   r  ns         r2   _estimate_task_levelr  ]  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+)rR  )rE  r\  r`  r]  )rA  matchs     r2   _parse_task_levelr    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 플래그로 미팅 체크 스킵: r^   meetingszagent-meeting-ro  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   rE  compile
IGNORECASEr\  r   r   r   r   r   r   	read_textr  rW   r,   )rR   rA  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)    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._]+$): r^   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] 파일 쓰기 실패: )rE  shutilr   r+  r  r   r   r   r   r"   r   realpathr!   relative_to
ValueErrortodayrt  r   r,   r   r   r   r   r)   
write_text)rR   r  _re_shutil_date
tasks_root
target_dirreal_target	real_roottemplate_dirr.  r5  exc	tmpl_nameout_nameout_path	tmpl_pathr   s                     r2   _create_task_docsrD    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=%drG  r  rH  )	_SESSION_RESILIENCE_AVAILABLE_SessionResiliencer!   r   check_all_sessionsr   r   r   r$  )
resiliencer   s     r2   check_sessionsrN  	  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  rY   )rO  r   s     r2   _check_bot_processrR  (  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+  )rS  r  s     r2   get_dispatch_timerV  D  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   r_  r   is_absoluteresolver   rY   )r   common_dir_str
common_dirs      r2   _resolve_main_workspacer_  J  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 계산 (이상치 필터링 적용)rR  r   Nu5   task-timers.json이 손상됨. 수동 복구 필요: r   rT   r   z^(\d{4})(?:[+_]|$)u   variant task ID 무시: r   i  )r   r   r$   r   r   r,   r   rs  r   r   r)   rE  r  r   r`  r]  r   r   r  r   ranger$  )r   r1   r   r   r  numsr  basemsorted_numsfiltered_maxis               r2   _compute_next_id_from_timersrh  e  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 생성 (카운터 파일 기반, 파일 락으로 중복 방지).

    워크트리에서 호출되어도 항상 메인 워크스페이스의 카운터/타이머 파일 사용.
    r^   r   .task-counterr   Tr   r   Fr   uB   카운터 파일 값 비정상 (%d), task-timers.json에서 복구u6   카운터 파일 손상, task-timers.json에서 복구u9   카운터(%d) < timers 최대(%d), 위로 보정합니다rT   rR  r   Nu.   task-timers.json 재읽기 실패, 초기화: r   reserved)r   reserved_atr   r   u    task-timers.json 쓰기 실패: )r_  r   r   r   r   r   r   r   r`  r   r_  r   r   rh  r4  r,   r6  r!   r$   r   r   r   r*  rt  r%   r   r   r   )main_workspacer   counter_filer   	lock_filefrom_counternext_num
timers_maxnext_idr1   r>  r   s               r2   generate_task_idrt    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가 카운터보다 크면 카운터를 업데이트한다.r^   rj  r   rT   r   z^(\d+)NrR  r   r   u2   카운터 동기화: %d → %d (외부 task_id=%s))r   r)   rE  r  r`  r]  r   r   r   r   r   r   r_  r4  r,   r6  r!   r   r   r   r   )rR   rn  r   rc  r  	given_numro  r?  s           r2   _sync_counter_if_neededrw    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": "..."}
    rT   r   u#   잘못된 task ID 형식입니다: u    (task- 접두사 필요)r   r  r^   r   ro  u*   task 파일이 존재하지 않습니다: eventsz.doneu=   이미 완료된 작업입니다. 새 task로 위임하세요\+\d+$r   z.retry_countr   r   r   ^z\+(\d+)\.md$rR  r  uE   3회 이상 재시도. 계속하려면 --force 추가 (현재 retry #)+u   > **재시도**: r   	 (retry #u7   )
> **재시도 사유**: 이전 세션 실패/중단

u   task 파일 복사 실패: NTr   u"   retry_count 파일 쓰기 실패: ok)r   new_task_idretry_count)rX   r   r   r   rE  r  r`  r   r_  r4  r,   r  escapeiterdirr  r   r]  maxr6  r   r   r!   r   r   )rx  r   rA  ry  rS   	done_filebase_without_plusretry_count_filefile_retry_count	tasks_dirmax_sibling_nsibling_patternr1   rd  r  new_retry_countr  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 관련 파일을 찾아 경고 메시지를 주입.
    r^   project-mapsro  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   rY   r^  rW   r_  lstriprX   r   rs  rK  r  r  )rA  r  map_pathmap_contentnav_patterns	nav_filesrb  
line_lowerpatterncleaned
files_listr   s               r2   _inject_project_map_contextr  G  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: rR  r  r  r  )r4   printsysexit_build_team_prompt)r   rA  rR   r  r  r  r  s          r2   r5   r5   v  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 기록r^   chainsrm  u   chain 파일 없음: Nz.lockr   r   r   r   current_phase_idxr   phasesr   r   r   )r  in_progressrR   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*  rt  r%   r   rY   r   r   r   )r  r   rR   
chain_file	lock_pathr   r1   r   current_idxphasetaskr   s               r2   _update_chain_taskr    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 상태도 정리 대상.
    에러 발생 시 로깅만 하고 무시 (본래 에러 반환이 우선).
    r^   r   r   Tr   Nr   r   r   r   r   z_cleanup_task: task_id u    없음, 정리 불필요r   )rk  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   rY   r$   r   r   r   r   r%   r   r   )
rR   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   )rA  s    r2   _warn_large_task_descr    s8    
9~1#i.1A By y	
 r?   generated_idc                     ||k7  ryt        j                  d|       rt        j                  d| d|        yt        j                  d| t         j                        rt        j                  d| d|        yy)ur  Phase 작업 감지 시 --task-id 미지정 경고.

    task_desc에서 Phase 패턴(Phase N, phase N, Phase N.N)을 감지하고,
    task_id가 자동 생성(generated_id와 동일)이면 WARNING 로그를 출력한다.

    task-2471: ``+N`` suffix (재시도 작업) 도 동일하게 경고하도록 일관화.
    작업을 차단하지 않는다 (경고만).
    Nz(?<![a-zA-Z])[Pp]hase\s+\duT   ⚠️ Phase 작업 감지됨. --task-id를 수동 지정하세요. (예: --task-id u   _N.N) 자동 생성 ID: u2   (?<![a-zA-Z\-])(retry|재시도|재실행|\+\d+\b)u^   ⚠️ retry/+N suffix 작업 감지됨. --task-id를 수동 지정하세요. (예: --task-id u   +1) 자동 생성 ID: )rE  r\  r   r   r  )rA  rR   r  s      r2   _warn_phase_without_task_idr    s     ,	yy.	:&i (!!(	+	

 		yyF	SUS`S`a&i (!!(	+	
 b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   rR  g      @u    ⚠️ 대용량 참조 파일:  (zKB, ~u#   tok) → offset/limit 사용 필수u   🚨 참조 파일 총 크기 u   KB (한도: ug   KB 초과!) — 전체 읽기 금지. 반드시 offset/limit 분할 읽기 또는 요약 파일 참조.gffffff?u    ⚠️ 참조 파일 총 크기 uD   KB의 70% 초과) — offset/limit 사용을 강력 권장합니다.r  N)rE  r  r!   r   rF  r"   r   isfilegetsizer,   basenameroundr   MAX_REF_FILE_TOTAL_BYTESinsertr  )rA  r  rT  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.
    r   Nr  r  r   rx   r   	verifiersu   ⚠️ QC 환경 경고: u*   의 verifiers 디렉토리가 없습니다uY   의 verifiers가 symlink가 아닙니다. shared/verifiers로의 symlink여야 합니다.shareduP   의 verifiers symlink가 shared/verifiers를 가리키지 않습니다 (실제: r  )rX   r)   r   r   r"   r   islinkr!   r2  r  r  )r   
team_shortverifiers_path	real_pathexpected_suffixs        r2   _check_team_qc_envr  0  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rR  Nr=   )r  r  s     r2   r  z#_get_large_files.<locals>.<genexpr>d  s     qs   N)r   r  )r"   r   isabsr  r!   r   r  r   sumr   r,   )r  r  largefilepathresolvedr1   
line_counts          r2   _get_large_filesr  U  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  kwrA  
task_lowers     r2   r  z)_inject_platform_rules.<locals>.<genexpr>  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)rW   boolrE  r\  r  r   r   r   r   r   r   r,   )rA  _EXCLUDE_KEYWORDS_WRITE_INTENTnaver_blog_detected
rules_pathrules_contentr   r  s   `      @r2   _inject_platform_rulesr  l  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  rA  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   )rA  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 	 d	dlm}  ||ddd      }|j                  dk(  r|j                  ng }t!        |      }|rHd}||vrBt         |z  }|j                         r)	 |j	                  d       |j                  |       |dz  }d	}	 d	dlm}  |       }	 |j-                  |dd      }t/        |      }|j1                          	 t        j3                  dt/        |       d| d| d       | j5                  d      r|rt        j7                  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# t"        $ r\}t        j                  d|        |j%                         }t&        D cg c]	  }||v s| nc c}w }}t!        |      }Y d}~d}~ww xY w# t
        $ r&}t        j                  d| d|        Y d}~d}~ww xY w# |j1                          w xY w# t"        $ r#}t        j                  d|        Y d}~d}~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)\)rR  u,   [memory_check] 링크 파일 읽기 실패: u    – classify_task_routingFrR   rS   write_auditrN   z6[routing] classify_task_routing fallback (substring): 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#  r_  r   rE  rF  r   utils.dispatch_routingr  classificationmatched_keywordsr  rY   rW   _DISPATCH_FALLBACK_DESIGN_KWutils.memory_indexerr  r\  r$  r   r   rX   r   )r   rA  memory_file	memory_mdr   rb  
star_itemsloaded_filesmatched_countr  fnameref_pathr  _routingdetecteddesign_keywords_foundr  r  extra_fname
extra_path	fts_countr  _indexerfts_resultss                           r2   _check_memory_before_dispatchr,    ss   
 $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	/@(DD^cd080G0G80S8,,Y[ $X :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	
 &; I  ?sCD
 S  aLL#OPUwV[\][^!_``a  /MaSQR__&
!=R2zAQBRRR $X	/   jLL#RS^R__defdg!hiij NN QKA3OPPQs   H	 	H7 H7>(H<+:I. (K 2L  L L 		H4H//H4<	I+I&&I+.	K70K'	J61J65KK	LL  LLL 	M	&MM	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UIrJ   u   사용자 경험u   인터페이스u   레이아웃u   화면u	   페이지u   컴포넌트u	   프론트frontendr  zbrainstorming-ro  u5   [brainstorming-gate] brainstorming 파일 확인됨: u   ⚠️ [brainstorming-gate] Lv.u9    UX 작업에 brainstorming 미실행. 감지 키워드: uf   . /brainstorming 스킬 실행 후 재위임하세요. --skip-brainstorming 플래그로 스킵 가능)r   r   r   rW   r  r   r   r   )rR   rA  r  r-  	level_map	level_intux_keywordsr  r  detected_keywordsr   s              r2   _check_brainstorming_gater6  3	  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_constantrK  rY   r   r   r   _load_constantsr   r   r   r$   r   r   r,   )r   mgrr/  r.  r1   r   s         r2   _load_logical_teamsr>  Y	  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  akrA  s     r2   r  z _suggest_team.<locals>.<genexpr>	  s     72rY7r  c              3   ,   K   | ]  }|v sd   ywr  r=   r  s     r2   r  z _suggest_team.<locals>.<genexpr>	  s     <"B)OA<s   	)	r   r   r   suggest_teamr>  r   r   r  r  )	rA  r8  	best_team
best_scorer   r  rA  rB  scores	   `        r2   _suggest_teamrJ  y	  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)   rX   DYNAMIC_BOT_TEAMSrJ  r   r   r>  r   )r   rA  rK  r%  	suggestedr8  rM  s          r2   _validate_team_routingrQ  	  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: 유효하지 않은 입력
    rU  r   u<   composite는 2개 이상의 팀이 필요합니다 (입력: r  u   최대 u   개 팀까지 허용 (입력: u   개)u   알 수 없는 팀 ID: u   . 허용 목록(소문자): u   중복 팀 ID: )r#  r_  r$  r4  r8   r7   r  r   )rR  r  rx   r   s       r2   _validate_composite_teamsrT  	  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 관리, 핸드오프, 품질 검수를 일괄 수행.
    NrU  highr  r   u)   보안 위협 감지 (composite 차단): r{  *   [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--teamr@  --descT   r  r   !   task-timer start 실패 (task_id=r   r^   r   r   r   r   r   r   r   rU  u   겹치는 팀(/   )에 이미 running 태스크가 있습니다: uB   . 완료 후 재시도하거나 --force로 강제 실행하세요.u   [force] 겹치는 팀(&   )에 이미 running 태스크 있음: u   . --force로 강제 진행.!   running 태스크 확인 실패: ^# task-\d+\.\d+:# :rR  countro  r   dispatchwriterR   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_)r   r   rU  r  cokacdir--cron--at--chat--key--once+   cokacdir 호출 타임아웃 (60초 초과)rawset_bot_status
processingr  [u   ] 복합업무 임시팀(   )에게 위임: log--typeu   복합업무 위임 완료: r   
dispatchedu   복합업무 임시팀장u   복합업무 임시팀(u@   )에게 위임 완료. 즉시 독립 세션에서 작업 시작.)	r   rR   r   rU  r	  r  rM  r  cron_responser  rn  )schedule_idr  	max_retryrS   issue_mc   [memory-check] MC 발급: mc_id for *   [memory-check] MC 발급 실패 (무시): u   복합업무 위임 실패: r     ...(생략)r   r  command)Jrt  rw  r  rT  r  _INJECTION_GUARD_AVAILABLE_scan_contentis_safethreatsseverityr  pattern_namerY   r   r   _APPROVAL_AVAILABLE_check_commandr   
risk_levelmatched_patterns_MODEL_ROUTER_AVAILABLE_route_modelr$  r@  r!   
TASK_TIMERr   r  r  r   r   r_  r   r   r   r$   r   r   r   r   r   r,   rE  r  r   r   r6  _AUDIT_LOGGER_AVAILABLE_log_file_operationprompts.team_promptsrv  r   _SANITIZE_GATE_AVAILABLE_should_sanitizer   r   
BOT_TO_KEYr   upperr8  rV  r   r  r  r   utils.bot_activityr  r4  _REDACT_AVAILABLEr>   rJ  rK  utils.memory_checkr  )0rU  rA  r  rR   ry  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   rS   rv  prompt_composite_level_to_int_composite_dispatch_levelr   r   r  _dispatch_delayr  r   responser  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+rR  r   r  r  Nz\*\*DoD\*\*:(.*?)(?=\n##|\Z)z\[([^\]]+)\]rU  )phase_number
phase_typetitledurationbodyfeaturesdod)rE  r  	MULTILINErs  finditer	enumerater]  r`  r_  endr\  rc  r$  rG  r#  r   )r  header_patternnext_section_patternr  matchesrg  rd  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  rY  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   r_  r   r$   r  rJ  rs  rK  r!   r   rE  r\  rG  r]  r  rY   )
r  r  r  
claude_binr   r  outerinnerarr_mr   s
             r2   _parse_prd_clauder  2  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 파일을 찾을 수 없습니다: r{  r   r   regexuE   [handle_prd] 정규식 파싱 결과 없음, claude CLI 폴백 시도claudezprd-r  Nr^   r   Tr   r  r  Phaser  r   r  r  r  r  z	dispatch-z-phasero  r   rR  rU  )timespecr   `u    — u   
## 완료 조건 (DoD)
r  z---
task_id: "z	"
team: "z$"
level: 2
priority: P2
depends_on: z
created_at: "z"
deadline: null
---

# r  r0  u   

## PRD 참조 (필수)


---

u   [handle_prd] 파일 생성: r  )r   prdr   methodcreatedskippedtotal_phases)r   r   r   r   r!   r\  r  r   r   r  rV   rX   r   r  r   r   r   r*  rt  r  reprr6  r$  )r  r   prd_filer  prd_absr  r  rV   prd_stemr  r  r  idxr  	phase_numr  r  r  r  r  r  rR   	file_path
depends_onprev_phase_num
created_atfeatures_strprd_ref_linedepends_on_yamldod_sectionr   s                                  r2   
handle_prdr  p  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=r  r}  r~  r  r  r  r  Tr  r  u   : 대체 키 u    타임아웃r   r  u   로 재전송 성공)r  fallback_keyu	    실패: u   : 모든 대체 키 실패)r   r   r   r   r   r   r   r   r  r   rV  r   r   r  r  r  r$   r  r   r   r_  r   r   )r  r  r   rR   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_scopesource_task_filec                 +   |=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        |	       d|	v sd|	j                  ddd      v rt        j                  d|	        |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|j0                   d|  d| d|j2                          |} | 4t4        r.t6        (	 t7        |      } t        j                  d|  d|         n| rt        j                  d |  d|         t        j                  d!|  d"|	        t8        rt:        	 t;        t=        t>              #      }!|!jA                         }"|!jC                         }#|"jE                         D ]o  \  }$}%|%jG                  d$      | k7  r|#jG                  |$i       }&|!jI                  |$|%|&      }'|'d%   d&k(  sIt        j%                  d'|$|'jG                  d(d)             q 	 dd+l%m&}(  |(       })|)jO                  d,      r|}*nd-}*d}+d},|
0|	r.tQ        jR                  d.|	      }-|-r|-jU                  d      },d/|, }+|xs | tV        v }.t>        d0z  d1z  }/|/j                         r	 tY        |/d2d      5 }0t[        j\                  |0      }1ddd       1jG                  d3i       jE                         D ]  \  }2}3|3jG                  d4      d5k(  s|3jG                  d$      | k(  s0|2|	k7  s6|.s/t_        |	       dd6|  d7|2 d8|3jG                  d9d      dd:  d;dc S t        j%                  d<|  d=|2 d8|3jG                  d9d      dd:  d>| tV        v rd?nd@ dA	        n |ddC te        |      dCkD  rdDndz   }5|	}6tg        |6d5      s	ddE|6 dFdS dGt=        th              dH|6dI| dJ|5g}7|r|7jk                  dK|g       |r|7jk                  dL|g       tm        jn                  |7dMdMd:N      }8|8jp                  dk7  r3t        j%                  dO|6 dP|8jr                  ju                                 |/t>        dQz  |z  }9|9j                         st_        |6       ddR|9 dS |r|rt>        d0z  dSz  | dTz  }:dU};|:j                         sdM};nI|:jw                         jx                  }<t{        j|                         j                         |<z
  dVz  }=|=dWkD  rdM};|;rqt>        dQz  |z  }9|9j                         rUt>        dXz  dYz  }>|>j                         r9tm        jn                  dGt=        |>      t=        |9      dZt=        |:      gdMdMd:N       tQ        j                  d[d\|	 d]|d^      }t        | |       t	        |	|||       t        ||       t        |       t        |      }?|?rt        j%                  d_|?        t        |t=        t>                    }t        ||      }|d`k(  rt        |t=        t>                    }t>        d0z  d3z  |	 dTz  }@|@j                  j                  dMdMa       dU}A|n@j                         r^t        |      j                         @j                         k(  r4	 @j                  d      }BBB|k(  rdM}At        j                  db@        As@j                  |d       |E	 t        |	||t=        @j                  t>                    c      }Ct        j                  dd|C        n|r	 t>        d0z  dfz  }D|Dj                  dMdMa       |D|	 dgz  }E|	t{        j|                         j                         t=        @j                  t>                    dhdi}Ft        |E|F       t        j                  dj|E        t        rt        	 t        |	t=        @      dld-m       t        |       }G|Grt        j%                  doG        t        | ||	||||p      }Hdqdrdsdt}I|IjG                  |dq      }Jt        r/t        )t        J      rHduz  }Ht        j                  dvJ dw       t        |      }Kt        |Kt=        t>                    }Kt        |      }L|KrCt        K|	      }M|MD ]  }Nt        j%                  dx|N         t        M       t        |6Ky       t        |L      }O|Ort        j%                  dxO        t        |K      \  }P}Qdqdrdsdt}I|IjG                  |dq      }R|R|Pk  r$Qr"t        j%                  dzR d{| d|Q d}P d~	       |rt        |6|       |?r	Hd|? dz  }HKr.t        K      }S|Sr!dj                  d SD              }THd|T dz  }HKr)t        K      rHdz  }Ht        j                  d|	 d       t        |      }U|UrHdU z  }H| dk(  r9t        r3t        -	 t        |      }VHd|V dz  }Ht        j                  d|V        t        |    }W| tV        v r|	 t        |6|      }Xt        X   }Yt        jG                  |Y      }Z|ZHt        j%                  dYj                          d       t_        |6       dd|Yj                          dS | }[X}\nt        jG                  |       }]|]r]t        vrt_        |6       dd|  ddS t        ]   }Z|ZHt        j%                  d]j                          d       t_        |6       dd|]j                          dS | j                  dd      }[t        jG                  | d      }\|\rt        |6\       \rt        |	      }^t        j                  d\ d|  dt        |^j                                       |\|^v r^\   }_|_d   }`|_d$   }a|`|	k7  ra| k7  r|sHt_        |6       t        ^      }bdj                  d |bD              }cdd\ da d` d|brdc ndz   bdS t        j%                  d\ da d` d       nt        j                  d\ d       \rt        |6[\| xs dd|	 dT       dqdrdsdt}d|djG                  |dq      }e|edrk\  r&t        |	e      }f|frt        j                  df        t        |	|||      }g|gr	Hdg dz  }Ht	        |	|||       dU}hd}iZ}j|dv r3jr1t        jd      }i|ir#idk7  rdM}ht        j                  d| di d       d}kddHdt        |k      dt        dZdg
}l	 tm        jn                  ldMdMdCN      }mmjp                  dk(  r	 t[        j                  mj                        }nddlxmy}o  |o| d       t        |       }pt        j                  d|  d|pd    d|pd   xs d dpd   xs d        t                hrirt        jid:ū       |ddC te        |      dCkD  rdDndz   }5t        r	 t        |5      }5d|	 dWd    d{|  d|5 }qtm        jn                  dGt=        th              d|qddlgdMdMd:N       |rt        || |	       |
|+|,g }rt        d|
dz         D ]'  }sd|, d|s }td|t dT}urj                  |s|u| |tdΜ       ) dGt=        t>        dz        dd|+dt[        j                  rdUӫ      d@g	}vtm        jn                  |vdMdMd:N      }w|wjp                  dk7  r4t        j%                  d|+ dPwjr                  ju                                 nt        j                  d|+        Wd   }xt        r	 t        x      }xt        j                  d|	 dx        d|	| Wd   |||Wd    dڝndۜ}y|+|+yd<   t        nt              rnjG                  dݫ      nd}z|zrzddqdޜ}{@r@{d<   t        |	fi { 	 ddlm}|  |||	|      }}|}r>t        j                  d}jG                  d       d|	        |}jG                  d      yd<   yS t        j                  dmjr                  ju                                 t        HZ| |	      }|rd   }nt        j                  d|d    d       ddlxmy}o  |o| d       t                hrirt        jid:ū       |ddC te        |      dCkD  rdDndz   }5t        r	 t        |5      }5d|	 dWd    d{|  d|5 }qtm        jn                  dGt=        th              d|qddlgdMdMd:N       d|	| |Wd   ||dd dd    dndMd	}yt        |nt              rnjG                  dݫ      nd}z|zrzddqdޜ}{@r@{d<   t        |	fi { yS hr&ir$t        ji       t        j                  d|j        t_        |6       dmjr                  ju                         dj                  ldds dgz         dS c c}w c c}w c c}w # t(        $ r#}t        j+                  d|        Y d}~kd}~ww xY w# t(        $ r#}t        j+                  d|        Y d}~Fd}~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}~id}~ww xY w# 1 sw Y   xY w# tZ        j`                  tb        f$ r#}4t        j%                  dB|4        Y d}4~4>d}4~4ww xY w# tb        $ r d}BY ;w xY w# t(        $ r#}t        j%                  de|        Y d}~Xd}~ww xY w# t(        $ r#}t        j%                  dk|        Y d}~d}~ww xY w# t(        $ r#}t        j+                  dn|        Y d}~d}~ww xY w# t(        $ r#}4t        j%                  d|4        Y d}4~4	d}4~4ww xY w# t        $ r#}4t_        |6       dt=        |4      dcY d}4~4S d}4~4ww xY w# tl        j                  $ r( t        j                  d       t_        |6       dddcY S w xY w# tZ        j`                  $ r  dmj                  ju                         i}nY w xY w# t(        $ r#}t        j+                  d|        Y d}~wd}~ww xY w# t(        $ r	 Wd   }xY Fw xY w# t(        $ r#}~t        j%                  d~        Y d}~~~yS d}~~~ww xY w# t(        $ r Y w xY w)u  작업을 독립 세션으로 디스패치

    목차→요약→상세 원칙:
    - 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 생성.
    - source_task_file: --task-file로 전달된 원본 파일 경로. 동일 경로면 재생성 skip.
    Nr   1   resume_from 파일이 존재하지 않습니다: r{  r   r   uL   ## 이전 세션 요약
아래 요약을 읽고 이어서 작업하세요.

r  u0   team_id 또는 composite_teams 중 하나 필수u2   team_id와 composite_teams는 동시 사용 불가r   r   )rR   ry  r   r  r  rT   r   rR  z2[task-id] external task_id with suffix preserved: rW  uh   [injection_guard] task_desc에서 위험 패턴 감지 (차단 없음, 워크플로우 호환): threats=uG   [injection_guard] task_desc에서 낮은 위험 패턴 감지: threats=rY  r  rZ  r  z, task_type=r[  r\  r]  r^  r_  u   위임 시작: team=r  r   r   r  r  u>   [session-health] CRITICAL 세션 감지: task=%s, usage=%.1f%%	usage_pctg        u2   [session-health] 세션 체크 실패 (무시): %s)FeatureFlagLoaderrw_isolation_enabledrq  ztask-(\d+)\.zscoped-r^   r   r   r   r   r   u   같은 팀(rh  z ('rM  rf  uN   '). 완료 후 재시도하거나 --force 플래그로 강제 실행하세요.u   [force] 같은 팀(ri  z'). u   논리적 팀 자동 --forceu   --force 플래그로u    강제 진행.rj  r  r`  ra  rb  r   rc  rd  re  	--projectz--work-levelTr  rg  r   rb   u8   프로젝트 디렉토리가 존재하지 않습니다: r  ro  Fi  rZ  r  zproject-map.pyz--outputrk  rl  rm  rn  z[file-size-check] codingr   uC   [task-file-guard] 기존 task file과 동일하여 재 write skip: )rR   rD  ri  rj  u   [capability] snapshot 저장: u.   [capability] snapshot 저장 실패 (무시): r|  z.allow-no-scope.logu2   --allow-no-scope 플래그로 legacy 호환 통과)rR   
allowed_atrq  reasonz)[capability] --allow-no-scope audit log: u7   [capability] allow-no-scope audit log 실패 (무시): rp  rr  ru  z	[qc-env] r  r   r  r  r  rw  rx  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  r  u   줄)Nr=   )r  lfs     r2   r  zdispatch.<locals>.<genexpr>  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 안내 삽입

rN   u?   

## 🎨 이미지 스킬 라우터 추천
- 추천 스킬: **u   **
- 이 추천은 image-skill-router.py의 `get_skill_recommendation()` 결과입니다.
- 추천 스킬을 우선 사용하되, 작업 특성상 다른 스킬이 적합하면 사유를 명시하세요.
u/   [image-skill-router] design 팀 추천 스킬: u-   [image-skill-router] 추천 실패 (무시): ry  rz  r{  r|  u   팀 uH   에 할당된 봇이 없습니다. 봇 토큰을 먼저 등록하세요.r  )r   r   u   [봇 충돌 검사] bot=z, busy_bots=rR   r   c              3   8   K   | ]  }|d     d|d    d  yw)r   r  r   r  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    가용defaultrn  )r   r   r   rS   u&   [3docs] 작업 3문서 생성 완료: u    

## ⚠️ Agent 미팅 경고
)r  r  claude-opus-4-6z[opus-upgrade] level=u   , 봇 모델 Opus 승격: u    → claude-opus-4-6r  r}  r~  r  r  r  r  r  r  r  r  u   [모델 검증 결과] z: consistent=r  z, org=r  r  r  r  )r   u$   [redact] 마스킹 실패 (무시): r  z] leaderr  r  r  r  )orderrS   r   rR   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   rR   r   r	  r  rM  r  r  r  r  )r  r  r  rS   r  r  r  r  r  u   위임 실패: )r  r  r   rR   r  u   [fallback] 대체 키(r	  u   )로 위임 성공u   )에게 fallback 위임: d   u#   원래 키 실패 → fallback 키()	r   rR   r   r(  r  rM  r  r  fallback_usedu<   [opus-upgrade] 디스패치 실패로 모델 즉시 복원: r  r  )r   r   r   r,  r6  r  rQ  rt  rw  r)   r   r   r  r  r  r  r  r  r   r  rY   r   r  r  r  r  r  r  rJ  rK  r!   r   _load_running_tasks_load_ledger_tasksr   r   check_sessionutils.feature_flagsr  
is_enabledrE  r\  r]  rO  r   r$   r   r  r   r,   r$  r@  r  r  r   r  r  r   r_  statst_mtimer   r*  	timestampr  r  r  r  r  r  r  r   r   r\  r6  r~  r3  rt  r   r  r  r  r5   r  r  r  r  r  r  r  r8  r  r  r  r  r  r  _IMAGE_SKILL_ROUTER_AVAILABLE_get_skill_recommendationr4   r   r   r  r   r  r   r   r   rs  r   r   rD  r)  r   rV  r   r  r   r  r   r  r  r  r4  r  r  r>   r  ra  r   r  rJ  rK  r  r  r  )r   rA  r  rU  r  r  r  r  r  rR   r  ry  r  r  r   rK  r  r  r-  rD  r  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_warningsrS   _skip_task_file_write_existing_content
_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  r   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  r  consistencyr  _tasks_json_i_t_id
_task_file_chain_create_cmd_chain_result_log_leaderr  r  r  r  r  r  fallback_results                                                                                                                                   r2   rp  rp  $  s   J ;'!!#!N{m\  &///AE  k	 	 ?!.`aa?!.bcc %oa&8)D!'"6Y	5J\]"?Iug]bjopp  -WiAQRO!o>>L"$ 	 ('>SGOOGR$CCKKDWIN
 #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!$!"**,	0A0A0CC	% ) 3 3W 3 E
 (->)-K$(!KK]^g]hij Y9 $		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t !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p  	% $	%(  	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@ ! ss  %AK7 ,AK( AK(AK7 AK-
/(AK7 AK2
*	AK7  AAL& &AM BAN )AN AN> AN1&AAN> )AN> >AN> 0AN> 5AAN> >AO= 	AAP BAP> +AQ- ,AR AS AS: -AT8 AU. AAV CAAV2 GAW! K(AK7 K7	AL#L ALLAL#L&	AML/AMMAMM	ANMAM<M<ANN	AN.NAN)N)AN.N1AN;N6AN> N>AO:OAO5O5AO:O=APPAPP	AP;PAP6P6AP;P>	AQ*QAQ%Q%AQ*Q-	ARQ6ARRARR	ASR%ASSASS	AS7SAS2S,AS7S2AS7S:8AT5T4AT5T8/AU+U*AU+U.	AVU7AVVAVVAV/V.AV/V2	AWV;AWWAWW!	AW.W-AW.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_idr{  r^   r   ro  u0   ★★★ 작업 취소됨 (CANCELLED) ★★★r   r   Tr"  stop_marker_addedz	[cancel] u'   : task 파일 STOP 마커 추가 완료stop_markeru)   : task 파일 STOP 마커 추가 실패: u$   : task 파일 없음 (마커 스킵)r|  r   z
.cancelledzmanual --cancel)rR   cancelled_atr  r   Fr   r   cancelled_eventu!   : .cancelled 마커 생성 완료u#   : .cancelled 마커 생성 실패: r   r   r   r  r   r   zbot-rm   rn   ro   rp   rq   rr   rs   rt   r   r  r}  z--cron-remover  r  rc   rf  r  cron_removedu"   : cron 제거 완료 (schedule_id=r  cron_removeu   : cron 제거 실패: u+   : schedule_id 없음 — cron 제거 스킵u   : schedule_id 조회 실패: COKACDIR_KEY_u    작업이 즉시 중단되었습니다. 진행 중인 모든 작업을 멈추고 .cancelled 마커를 생성한 뒤 종료하세요. finish-task.sh를 호출하지 마세요.r~  r  r  r  bot_notifiedu   : 봇(u    ) 중단 메시지 발송 완료
bot_notifyu&   : 봇 중단 메시지 발송 실패: u(   ) 키 없음 — 중단 메시지 스킵u6   : 봇 이름 파악 불가 — 중단 메시지 스킵r   r  z--qc-result	CANCELLEDtimer_endedu!   : task-timer end CANCELLED 완료u"   : task-timer end 실패 (무시): r  )r   rR   actionsfailed_actions)$rE  r  r   r   r   r^  r6  r   r   r   rY   r!   r   r   r   r*  rt  r   r$   r%   r   r   rX   r  r)   r   r"   r  r   r  r  r   r_  r  rV  r  )rR   r7  r  r  rS   r{  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                     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?d@A       |j	                  dBdddC	       |j	                  dDdddE	       |j	                  dFdGdHgdHdI=       |j	                  dJd dK%       |j	                  dLdddMdNA       |j	                  dOd dPdQ9       |j	                  dRdddSdTA       |j	                  dUd dV%       |j	                  dWdddXdYA       |j                         }|j                  r,t               }t        t        j                  |ddZ[             y |j                  r7t        |j                        }t        t        j                  |ddZ[             y |j                  s)|j                   s|j"                  s|j%                  d\       t'        |d]d       r_|j                  s|j%                  d^       t)        |j*                  |j                        }t        t        j                  |ddZ[             y |j,                  s6|j.                  s*t'        |d]d       s|j0                  s|j%                  d_       d`}|j,                  r|j,                  }n2|j.                  r&	 t3        |j.                        j5                  dab      }dc|v sdd|v r8t8        j;                  de       t        t        j                  dfdgdhdi             |j.                  r&t3        |j.                        }|j=                         sEt        t        j                  djdk|j.                   dhdi             t?        j@                  dl       |j5                  dab      jC                         sEt        t        j                  djdm|j.                   dhdi             t?        j@                  dl       |jD                  s	 ddnl#m$}  ||j.                        }	|	r-|	|_"        t8        jK                  do|	 dp|j.                   dq       n@|j,                  xs d`tO              drkD  r"t8        j;                  dstO               dt       |jP                  d;k(  rM	 ddul)m*}
  |
|jD                  xs dvw      }| dx |jV                  sdy|_+        t8        jK                  dz       d }d }	 dd|l,m-} |j"                  r|	  |t_        |      t'        |d~d       d      }t        t        j                  dd+|j`                  |jb                  |jd                  |jf                  |jh                  |j`                  dk(  xr |jj                   ddi             t?        j@                  d       n<t        t        j                  djd+d| ddi             t?        j@                  dl       |jP                  |d u }|/	  |t_        |      t'        |d~d             }|j`                  dk(  }|rtm        fdtn        D              }rL|jj                  rt8        j;                  d       n*t8        j%                  d       t?        j@                  dl       |jp                  jt3        |jp                        }|j=                         sEt        t        j                  djd|jp                   dhdi             t?        j@                  dl       d }|j                   r	 ts        |j                         }|j0                  r |jD                  r8t        t        j                  djddhdi             t?        j@                  dl       |j                  s8t        t        j                  djddhdi             t?        j@                  dl       |j,                  s|j.                  sty        jz                  dd`|j0                        }t3        t|              dz  dz  | dz  }|j=                         r!|j5                  dab      jC                         n;t        t        j                  djd| dhdi             t?        j@                  dl       t        |j0                  |j                  dt               v rnd`|j                        }|d   djk(  r5t        t        j                  |di             t?        j@                  dl       |d   |_"        t8        jK                  d|j0                   d|jD                   d|d    dq       |jD                  r[ty        j                  d      }|j                  |jD                        s+t8        j;                  d|jD                  |j                         rt              nd }|Ft'        |dXd      s9t        t        j                  djddhddZ[             t?        j@                  dl       t8        jK                  d|jD                          t        di d|j                  dd|j                  d|d|j                  d|j                  d|j                  d|j                  d|j                  d|jD                  d|j                  d|j                  d7|jp                  d|j                  d|jV                  dM|j                  dP|j                  d|j                  dS|j                  d|dXt'        |dXd      dt'        |d~d       }t        t        j                  |ddZ[             y # t6        $ r Y {w xY w# tL        $ r Y w xY w# tL        $ rF}t        t        j                  djd{| dhdi             t?        j@                  dl       Y d }~d }~ww xY w# t6        $ r%}|}t8        j]                  d}|        Y d }~d }~ww xY w# t6        $ rG}t        t        j                  djd+d| ddi             t?        j@                  dl       Y d }~Kd }~ww xY w# t6        $ r%}t8        j]                  d|        d+}Y d }~;d }~ww xY w# tt        $ rL}t        t        j                  djtw        |      dhdi             t?        j@                  dl       Y d }~d }~ww xY w)Nr   r	   u   작업 위임 디스패처)rM  z--check-sessions
store_trueFu0   모든 running 세션의 토큰 사용량 체크)actionr&  helpz--cancelTASK_IDuc   진행 중인 task를 강제 취소 (STOP 마커 + .cancelled + cron 제거 + 봇 중단 메시지))metavarr&  r  )requiredrd  )rz   r{   r|   r}   r~   r   r   r   r   r   r   rN   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   rZ  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	--dry-rundry_runut   실제 dispatch는 하지 않고 routing 분류 결과만 JSON으로 출력 (qc-gate 트리거 검증용, task-2473))r  r&  r  r  z--skip-qc-gateua   이미지/광고 작업의 QC 게이트를 의도적으로 스킵 (제이회장님 승인 필수)z--skip-meetinguH   Lv.4 Agent 미팅 체크를 의도적으로 스킵 (로그에 기록됨)z--agent-typereadrq  uo   에이전트 유형: read(읽기 전용, worktree 미생성) | write(쓰기, worktree 생성). 기본값: writez--modeluW   모델 강제 지정 (예: claude-opus-4-6). 지정 시 해당 모델의 봇만 선택.z--override-routingrK  u?   라우팅 경고를 무시하고 지정 팀으로 강제 위임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)로 직접 실행 권장.r{  r+  r   u3   작업 설명 파일이 존재하지 않습니다: rR  u,   작업 설명 파일이 비어있습니다: )extract_task_id_from_filenamez+[task-id] auto-extracted from --task-file: z (file=r  r,  u   --task 직접 전달 u;   자 — 긴 내용은 --task-file 사용을 권장합니다)build_workflow_overview_promptauto)rR   r  r'  u3   [workflow] image-qc-gate: opus 모델 강제 적용u&   워크플로우 모듈 import 실패: r  uJ   [routing] classify_task_routing import 실패, fallback substring 검사: rS   r  r  rN   )r   r  r  r   r  context_signalsrI  qc_gate_triggeredu   dry-run 분류 실패: )r   r  r  u4   dry-run 분류 실패: routing module import error: )rR   rS   uJ   [routing] classify_task_routing 호출 실패, fallback substring 검사: c              3   &   K   | ]  }|v  
 y wr<   r=   r  s     r2   r  zmain.<locals>.<genexpr>  s     !Yb"	/!Yr  uV   ⚠️ --skip-qc-gate로 이미지 QC 게이트 우회. 제이회장님 승인 필수.u   ❌ 이미지/광고 작업에 --workflow image-qc-gate가 필수입니다. 의도적 스킵 시 --skip-qc-gate 플래그를 추가하세요.r  u-   --resume과 --task-id는 동시 사용 불가u.   --resume 사용 시 --team이 필수입니다.r}  r^   r   ro  u"   base task 파일이 없습니다: rA  )ry  r   r  z	[resume] r   r  r  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 플래그를 사용하세요.z[task-id] resolved: r   r  rU  r  r  r  r  r  rR   r  ry  r  r   r  rD  r  r=   )Sutils.env_loaderr
   argparseArgumentParseradd_argumentadd_mutually_exclusive_groupBooleanOptionalActionr`  
parse_argsrN  r  r$   r  cancelr  r   r@  r  r   rU   r  r  r  rS   resumer   r   rY   r   r   r   r  r  r_  rR   utils.task_id_parserr  r   ImportErrorr$  workflowprompts.image_workflowr  r   r  r  r   r\   r  r   r  r  rI  skip_qc_gater  r  r  rT  r4  r!   rE  r  r   r  r   ry  r  r  r  rO  rp  r  sessionprojectchainr  r  r  r  rK  r  r  r-  )r
   parserteam_or_composite
task_grouprZ   r   task_contenttask_file_pathr  
_extractedr  workflow_promptr   _classify_routing_fn_routing_import_errorr  _dry_decision_use_fallback_routing_decision_is_design_taskr8  composite_teams_list
base_cleanbase_task_fileresolve_resultTASK_ID_V2_PATTERN_allowed_resourcesrA  s                              @r2   mainr    s,   .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	    D   p	   W	   !~	   f  
 N   h	   !N   f  
 '  	 D !djjeA>? {{T[[)djjeA>? 99T^^DLL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 ||
N:4>>J
#-DLKKEj\ R!!% 03 IIO	y>CNN23y>2BB}~ }}'	M<.O ++;ykBI::.
QR   hX ||+ 4248%dK> %	! djj"#&3&B&B+22(5(F(F'4'D'D*00*7*F*F(*R*l[_[l[lWl	" !&	' 	(  $**!QRgQhi "	# $
 HHQK }},4+	%$8248%dK>%!
 #4"B"Bh"N
 !!Y<X!YYO  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 
 	 	 KK&t||n56 		 jj -	
 << <<  $$ ))  {{ jj $$ ?? jj  ..!" #$ &&%&  22'( -)* t%5u=+, !{D9-F0 
$**V%
:;G  		X  *  	

&5[\][^3_`!& HHQKK	  h "abdaefggh2  djj%#!8=" !&	' (
 6  %ijlimno $%J  	$**CFCRWXYHHQKK	s   <%s As  $As0 5u 
B,u3 .w !w7 	ss 	s-,s-0	t?9;t::t?	u0u++u03	w<<v>>w	w4w//w47	y Ayy__main__)r   z
str | Noner<   )rf  )r   N)F)r  )r  NNr  )i  )r  NFN)Nr   r  NNNNTr  NNFNrq  NFNFFNFN)__doc__r  r   ru  r$   r"   rE  r   r  r   r   pathlibr   typingr   r   r   r   r  r!   __file__r\  r   r  r
   rY   utils.atomic_writer   config.loaderr3   get_instancer:  r9  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   rK  rJ  importlib.utilutil_iluspec_from_file_location	_isr_specmodule_from_spec_isr_modloaderexec_moduleget_skill_recommendationr7  r6  r  rI   r  r\   get_path_ws_fallbackr  r   r   r  r  r;  _chat_fallbackr   r  r   r  r   utils.org_loaderru   rv   rw   r   r  r   
_teams_map_team_to_bot_maprK  r   CROSS_FUNCTIONALrO  r   r   r   rs  r   r   r   r   r`  r  r  r4  r8  r  r@  rO  rL  r~  r  r  r  r  r  r  r  r  r  r  r  r
  r	  tupler  r  r)  rD  rN  rR  rV  r_  rh  rt  rw  r  r  r  r  r  r  r  r  r  r  r  r,  r6  r>  rJ  rQ  rT  r  r  r  r  r  rp  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 
6(5C (5HSM (5V" " "JT c T .Bc Bhsm BJ)
s )
s )
t )
XS
3 S
3 S
4 S
l#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 "+/ $ $"! !%""$(, &*-uc]uu u d3i(	u
 u u smu u u c]u SMu u #u u C=u  !u" sm#u$ %u& 'u(  ~)u* +u, sm-u. 
/uNT T TnP<f zF UK  		   3 s t :  D  (.G''("  3 3    'M!&'   N   $#$  $L#$  %$%  "!"  *$)!*  * $$)!*  $ t  '
T&&w/
,,];
#./CSCYCYC[\<4fj..\\
  	
 	

 !       	
='
j @s"  A[ [ [' 5[7 \ !\+ *\; 3] <] ]+ ]; ^  A0^ ^+ $^; -a[[[$#[$'	[43[47\\\('\(+	\87\8;	]]	]]	]('](+	]87]8;	^^	^^	^('^(+	^87^8;Aa
	`-a
	a
