
    nj%             	         d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	m
Z
mZ ddlmZmZ ddlmZ ddlmZ ddlmZmZmZ  ee      j/                         j0                  j0                  Z ee      ej6                  vr"ej6                  j9                  d ee             dd	lmZmZ dd
l m!Z!m"Z"m#Z#m$Z$ ddl%m&Z'  ejP                  e)      Z*edejV                  f   Z, ed      Z- ed      Z.dZ/dZ0dZ1dZ2 G d dee      Z3 G d dee      Z4dZ5dZ6e
 G d d             Z7e
 G d d             Z8e
 G d d             Z9dd	 	 	 	 	 dTdZ:ddd 	 	 	 	 	 	 	 dUd!Z;dVd"Z<dWd#Z=dXd$Z>dYd%Z?dd&dZd'Z@dddd(	 	 	 	 	 	 	 	 	 d[d)ZAddd*	 	 	 	 	 	 	 d\d+ZBddd*	 	 	 	 	 	 	 d]d,ZCddd-	 	 	 	 	 	 	 d^d.ZDdd/	 	 	 	 	 d_d0ZEd`d1ZFdd2	 	 	 	 	 dad3ZGddd*	 	 	 	 	 	 	 dbd4ZHdddddd5	 	 	 	 	 	 	 	 	 	 	 	 	 dcd6ZIddd7ZJded8ZKdd/	 	 	 	 	 	 	 dfd9ZL	 	 	 	 	 	 	 	 dgd:ZMdhd;ZNdid<ZOdd=ddd>	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 djd?ZPdkd@ZQ	 	 	 	 	 	 	 	 dldAZRdmdBZSdmdCZT	 	 	 	 	 	 	 	 	 	 dndDZUdd=ddE	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dodFZVd=ddddddddG	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dpdHZWddd=ddI	 	 	 	 	 	 	 	 	 dqdJZXdrdKZYdsdLZZddM	 	 	 	 	 dtdNZ[dudOZ\dvdwdPZ]e)dQk(  r& ej                  ej                  dRS        e]        yy)xu]  utils/lifecycle_reconciliation_manager.py — task-2518 P0.

회장 명시 요구사항:
  - bot session ↔ task lifecycle 결합 제거
  - GitHub/CI/smoke evidence를 source-of-truth로 사용하는 idempotent reconcile state machine
  - manual .done 위장 차단 (evidence 없으면 RuntimeError)
  - dry-run 기본 (--apply 없이 side effect 0)

7 Lifecycle States:
  RUNNING                 — task-timer running, 작업 진행 중
  PR_OPEN                 — PR 생성됨, 머지 대기
  MERGED_PENDING_RECONCILE — PR merged, finalize 누락 (회장 §1 사례)
  RECONCILING             — reconcile 진행 중
  FINALIZED               — .done.acked + .merge-done + timer end 모두 정상
  STUCK_NEEDS_RECONCILE   — stuck 자동 감지
  ESCALATED               — Critical 7종 발동

8 Stuck Cases (회장 §1~7 + Telegram cut-off):
  TIMER_RUNNING_BUT_PR_MERGED          — 회장 §2
  PR_MERGED_BUT_DONE_MISSING           — 회장 §1
  MERGE_COMMIT_BUT_MERGE_DONE_MISSING  — 회장 §1
  CI_PASS_BUT_NOT_FINALIZED
  TELEGRAM_REPLY_CUT_OFF               — cron history truncation
  BOT_SESSION_ENDED_BUT_TASK_OK        — 회장 §7
  FINISH_TASK_INTERRUPTED              — 회장 §3
  STALE_ESCALATE_MARKER                — 회장 §4

task-2521 §3 — bot session cancelled 격상 4종 (BOT_CANCELLED_*)
task-2529 §5 — AUTO_FINALIZE_CHAIN_MISSING 4종:
  CODE_DONE_BUT_NO_COMMIT              — 자체 검증 PASS 후 commit 단계 미진입
  COMMIT_DONE_BUT_NO_PR                — commit push 후 PR 생성 단계 미진입
  PR_OPEN_BUT_NO_MERGE_ATTEMPT         — PR open + CI SUCCESS 후 merge 시도 미발생
  SELF_VERIFIED_BUT_NOT_FINALIZED      — 본 사건 fixture (task-2524+1, dev5 사라스와티)

Evidence priority (회장 명시):
  PR state > mergeCommit > origin/main 포함 > CI > smoke > timer > file marker
    )annotationsN)	dataclassfield)datetimetimezone)Enum)Path)AnyCallableOptional)CanonicalWorkspaceresolve_canonical_workspace)AutomationDecisionCriticalEscalationTypeEscalationPacketSmokeResult) SCHEDULE_FRESHNESS_THRESHOLD_MIN.z/home/jay/workspacez$/home/jay/.cokacdir/schedule_historyzmemory/eventszmemory/task-timers.jsonz memory/task-timers-archived.jsoni  c                  ,    e Zd ZdZdZdZdZdZdZdZ	dZ
y	)
LifecycleStateu1   7 lifecycle states (회장 명시 정확 매칭).RUNNINGPR_OPENMERGED_PENDING_RECONCILERECONCILING	FINALIZEDSTUCK_NEEDS_RECONCILE	ESCALATEDN)__name__
__module____qualname____doc__r   r   r   r   r   r   r        =/home/jay/workspace/utils/lifecycle_reconciliation_manager.pyr   r   ]   s*    ;GG9KI3Ir"   r   c                  T    e Zd ZdZdZdZdZdZdZdZ	dZ
d	Zd
ZdZdZdZdZdZdZdZdZy)StuckReasonuY   기존 8 + task-2521 §3 BOT_CANCELLED_* 4 + task-2529 §5 AUTO_FINALIZE_CHAIN_MISSING 4.TIMER_RUNNING_BUT_PR_MERGEDPR_MERGED_BUT_DONE_MISSING#MERGE_COMMIT_BUT_MERGE_DONE_MISSINGCI_PASS_BUT_NOT_FINALIZEDTELEGRAM_REPLY_CUT_OFFBOT_SESSION_ENDED_BUT_TASK_OKFINISH_TASK_INTERRUPTEDSTALE_ESCALATE_MARKER&BOT_CANCELLED_TIMER_RUNNING_PR_MISSING"BOT_CANCELLED_WITH_ACTIVE_WORKTREE$BOT_CANCELLED_AFTER_COMMIT_BEFORE_PR&BOT_CANCELLED_AFTER_PR_BEFORE_FINALIZECODE_DONE_BUT_NO_COMMITCOMMIT_DONE_BUT_NO_PRPR_OPEN_BUT_NO_MERGE_ATTEMPTSELF_VERIFIED_BUT_NOT_FINALIZEDSTALE_SCHEDULE_IDN)r   r   r   r    r&   r'   r(   r)   r*   r+   r,   r-   r.   r/   r0   r1   r2   r3   r4   r5   r6   r!   r"   r#   r%   r%   i   sr    c"?!=*O' ;5$C!73 .V*)M&+Q(-U*
 83#A &G#
 ,r"   r%   g     r@c                  X   e Zd ZU dZded<   ded<   ded<   ded<   d	ed
<   ded<   ded<   ded<   ded<   d	ed<   d	ed<   d	ed<   d	ed<   d	ed<   d	ed<   ded<   d	ed<   ded<   d	ed<   d	ed<   dZded<   dZd	ed<   dZd	ed<   dZd ed!<   dZ	ded"<   dZ
ded#<   dZded$<   dZded%<   dZded&<   y)'LifecycleEvidenceu9   모든 source-of-truth 증거를 모은 unified envelope.strtask_idzOptional[int]	pr_numberOptional[str]pr_statemerge_commitboolmerged_into_main	ci_statussmoke_statustimer_statustimer_end_timehas_donehas_done_ackedhas_merge_donehas_qc_resulthas_followuphas_escalate_markerzOptional[float]escalate_marker_age_minutestelegram_reply_truncatedbot_session_statusworktree_existsbranch_pushed_to_remoteNworktree_mtime_seconds_agoFhas_pushed_commitsreport_artifact_presentzOptional[bool]process_alivepr_open_age_secondsreport_artifact_age_secondsschedule_idschedule_id_freshnessschedule_id_age_seconds)r   r   r   r    __annotations__rP   rQ   rR   rS   rT   rU   rV   rW   rX   r!   r"   r#   r8   r8      s    CL!!N!00""%%!!266$$$)T)$(M>( ,0/ 487
 "&K%+/=//3_3r"   r8   c                  "    e Zd ZU ded<   ded<   y)	StuckCaser%   reasonr9   detailN)r   r   r   rY   r!   r"   r#   r[   r[      s    Kr"   r[   c                      e Zd ZU dZded<   ded<    ee      Zded<   d	Zd
ed<    ee      Z	ded<    ee      Z
ded<   dZded<   dZded<   dZded<    ee      Zded<   ddZddZddZy	)LifecycleReportu   reconcile 결과 envelope.r9   r:   r   state)default_factorylist[StuckCase]stuck_casesNzOptional[LifecycleEvidence]evidence	list[str]actions_takenactions_plannedTr?   dry_run 	timestampreconcile_run_idzdict[str, Any]backfill_metadatac                    | j                   i S | j                   j                  j                         D ci c]  \  }}||
 c}}S c c}}w )uI   evidence를 dict로 직렬화 (EscalationPacket 연동 등에서 사용).)rd   __dict__items)selfkvs      r#   evidence_to_dictz LifecycleReport.evidence_to_dict   s@    == I!%!7!7!=!=!?@A1@@@s   Ac                h   | j                   | j                  j                  | j                  D cg c]%  }|j                  j                  |j
                  d' c}| j                         | j                  | j                  | j                  | j                  | j                  | j                  d
S c c}w )Nr\   r]   
r:   r`   rc   rd   rf   rg   rh   rj   rk   rl   )r:   r`   valuerc   r\   r]   rs   rf   rg   rh   rj   rk   rl   )rp   scs     r#   to_dictzLifecycleReport.to_dict   s    ||ZZ%% ** 99??bii@ --/!//#33|| $ 5 5!%!7!7
 	
s   *B/c                N    t        j                  | j                         dd      S )N   Findentensure_ascii)jsondumpsry   )rp   s    r#   to_jsonzLifecycleReport.to_json   s    zz$,,.GGr"   )returndictr   r9   )r   r   r   r    rY   r   listrc   rd   rf   rg   rh   rj   rk   r   rl   rs   ry   r   r!   r"   r#   r_   r_      s    $L#(#>K>,0H)0$T:M9:!&t!<OY<GTIsc(-d(C~CA
"Hr"   r_   cwdc               6    t        j                  | |ddd      S )NT   )r   capture_outputtexttimeout)
subprocessrun)argsr   s     r#   _default_runnerr     s$    
 >> r"   r   runnerc               N    ||nt         } || |t        |            S d       S )Nr   )r   r9   )r   r   r   fns       r#   _runr     s-     %?BdCOC>>>>r"   c                 d    t        j                  t        j                        j	                         S N)r   nowr   utc	isoformatr!   r"   r#   _now_isor     s    <<%//11r"   c                    | t         z  S r   )_EVENTS_DIR_NAMEworkspace_roots    r#   _events_dirr   #  s    ,,,r"   c                (    t        |       | d| z  S N.)r   )r   r:   suffixs      r#   _marker_pathr   '  s    ~&G9AfX)>>>r"   c                     t         S r   )_DEFAULT_WORKSPACEr!   r"   r#   _default_workspace_rootr   +  s    r"   r   c                   	 t        | d|      S # t        $ r, t        | t        t        dz  | z  d|  ddt        dd	      cY S w xY w)zUResolve canonical workspace for the task (delegates to canonical_workspace_resolver).F)fetchr   
.worktreestask/ri   T)	r:   r   worktree_pathbranch_namemain_head_shabase_shar   is_mainis_clean)r   	Exceptionr   r   )r:   r   s     r#   _resolve_workspacer   /  s^    
*7%OO 
!-,|;gEy)"

 
	

s    2AAr   r   	pr_lookupc                   |		  ||       S t        |xs
 t	                     }	 t        ddddd|  dd	d
dddg||      }|j                  dk7  s|j                  j                         si S t        j                  |j                        }|si S |D ]  }|j                  d      dk(  s|c S  |d   S # t         $ r#}t        j                  d| |       i cY d}~S d}~ww xY w# t         $ r#}t        j                  d| |       i cY d}~S d}~ww xY w)z/Fetch PR info via gh CLI or injected pr_lookup.Nzpr_lookup failed for %s: %sghprr   z--searchz
head:task/z--stateall--jsonz"number,state,mergeCommit,createdAtz--limit5r   r   r`   MERGEDzgh pr list failed for %s: %s)r   loggerwarningr9   r   r   
returncodestdoutstripr   loadsget)	r:   r   r   r   excr   rro   items	            r#   _gather_pr_infor   F  s'    	W%%
 n9 7 9
:CdFj	25>3 
 <<1AHHNN$4I

188$I 	Dxx H,	 Qx7  	NN8'3GI	8  5wD	sS   B2 AC! +"C! C! (C! ,C! 2	C;CCC!	D*DDDr   r   c                   | syt        |xs
 t                     }	 t        ddd| dg||      }|j                  dk(  S # t        $ r!}t
        j                  d| |       Y d	}~yd	}~ww xY w)
z4Check if merge_commit is an ancestor of origin/main.Fgitz
merge-basez--is-ancestorzorigin/mainr   r   z"merge-base check failed for %s: %sN)r9   r   r   r   r   r   r   )r>   r   r   r   r   r   s         r#   _check_merged_into_mainr   r  su     
n9 7 9
:C	L/<O

 ||q   ;\3Os   !> 	A(A##A(c               6   t        |xs
 t                     }	 t        dddt        |       ddg||      }|j                  dk7  s|j                  j                         syt        j                  |j                        }|j                  d      xs g }|sy|D ch c]*  }|j                  d	      xs |j                  d
      xs d, }}d|v sd|v ryt        d |D              ryyc c}w # t        $ r!}	t        j                  d| |	       Y d}	~	yd}	~	ww xY w)z?Fetch CI rollup status via gh pr view --json statusCheckRollup.r   r   viewr   statusCheckRollupr   r   Nr`   
conclusionri   FAILUREFAILEDc              3  *   K   | ]  }|s|d v   yw)>   SUCCESS	COMPLETEDNr!   ).0ss     r#   	<genexpr>z$_gather_ci_status.<locals>.<genexpr>  s     Bq,,Bs   	r   PENDINGz$CI status fetch failed for PR %s: %s)r9   r   r   r   r   r   r   r   r   r   r   r   r   )
r;   r   r   r   r   datarollupr   statesr   s
             r#   _gather_ci_statusr     s    n9 7 9
:CdFC	N- 
 <<1AHHNN$4zz!((#-.4"PVW$((7#Ctxx'=CCWW(f"4BfBB X  =y#Ns<   AC.  6C. C. /C)

C. C. )C. .	D7DDr   timer_loaderc                  |		  ||       S |xs
 t               t        z  }	 |j                         si S |j                  d      }t        j                  |      }|j                  di       }|j                  | i       S # t         $ r#}t        j                  d| |       i cY d}~S d}~ww xY w# t         $ r#}t        j                  d| |       i cY d}~S d}~ww xY w)z?Load timer info from task-timers.json or injected timer_loader.Nztimer_loader failed for %s: %sutf-8encodingtasksz'task-timers.json read failed for %s: %s)
r   r   r   r   _TIMERS_FILE_NAMEexists	read_textr   r   r   )r:   r   r   r   timers_filerawr   r   s           r#   _gather_timer_infor     s     	((
 ">%<%>BSSK	!!#I##W#5zz#"%yy"%%  	NN;WcJI	  @'3O	s@   A? B. A
B. ?	B+B& B+&B+.	C7CCCr   c                   |xs
 t               }t        |      d fd} |d      }d}|rZ	   dz  j                         j                  }t	        j
                  t        j                        j                         |z
  }|dz  } |d       |d       |d       |d	       |d
      ||dS # t        $ r d}Y 5w xY w)z%Check file markers in memory/events/.c                4     d|  z  j                         S r   )r   )r   evr:   s    r#   r   z$_gather_file_markers.<locals>.exists  s"    y&**2244r"   zdone.escalatedNz.done.escalatedg      N@done
done.acked
merge-donez	qc-resultzfollowup.txt)rE   rF   rG   rH   rI   rJ   rK   )r   r9   r   r?   )
r   r   statst_mtimer   r   r   r   rj   r   )	r:   r   wdr   has_escalateescalate_agemtimeage_secsr   s	   `       @r#   _gather_file_markersr     s     
	424B	RB5 *+L$(L	 WI_55;;=FFE||HLL1;;=EH#d?L
 6N . .,~.+'3   	 L	 s   AB1 1B?>B?c                    | sy| j                         }t        | j                  d            t        k\  ry|rd|d   cxk  rdk  ry d|v r|j	                  d      }|dz  d	k7  ryy)
z<Heuristic: response is likely truncated if it ends abruptly.Fr   Tu   가u   힣z```r{   r   )rstriplenencode$_TELEGRAM_TRUNCATION_THRESHOLD_BYTEScount)r   strippedr   s      r#   _is_truncated_responser    sq    {{}H
4;;w $HHEXb\2U2 3 u%19>r"   cron_history_dirc                  |xs t         }ddd}	 |j                         s|S d}t        |j                  d      d d      D ]  }	 |j	                  dd	
      j                         }t        |      D ]I  }|j                         }|s	 t        j                  |      }|j                  dd      xs d}	| |	v sG|} n | n ||S |j                  dd      xs d}
t        |
      |d<   |j                  dd      }|t        |      |d<   |S # t        j                  $ r Y w xY w# t        $ r Y w xY w# t        $ r"}t        j!                  d| |       Y d}~|S d}~ww xY w)zIScan cron history logs to detect Telegram cut-off and bot session status.FN)rL   rM   z*.logc                6    | j                         j                  S r   )r   r   )ps    r#   <lambda>z&_gather_cron_history.<locals>.<lambda>  s    HYHY r"   T)keyreverser   replace)r   errorspromptri   responserL   statusrM   z#cron history scan failed for %s: %s)_DEFAULT_CRON_HISTORY_DIRr   sortedglobr   
splitlinesreversedr   r   r   JSONDecodeErrorr   r   r  r9   r   r   )r:   r  history_dirresultmatching_last_recordlog_filelineslinerecordr  r  r  r   s                r#   _gather_cron_historyr    s    #?&?K$)"F
(L!!#M 04{//8>Ycgh 	H **GI*NYY[$UO D::<D !!%D!1 $ZZ"5;F&(/5, (3 4!	*  'M (++J;Ar-CH-M)* &))(D9+.v;F'(
 M5  // ! !    L<gsKKMLsr   D9 #D9 AD*
DD*:D*D9 	AD9 D'$D*&D''D**	D63D9 5D66D9 9	E$EE$c          	        |xs
 t               }|dz  }dddddd}	 ddl}t        ||  dz        }|j                  |      }t        |      |d<   |rL	 t	        d |D              }	t        j                  t        j                        j                         |	z
  }
|
|d	<   	 t        d
dddd|  dgt        |      |      }|j                  dk(  r$|j                  j                         r
d|d<   d|d<   	 |dz  dz  }|j#                         rrt%        |j                  |  d            }|rSd|d<   	 t	        d |D              }t        j                  t        j                        j                         |z
  }||d<   |S |S # t        $ r Y w xY w# t        $ r Y w xY w# t        $ r!}t        j!                  d| |       Y d}~d}~ww xY w# t        $ r	 d|d<   Y |S w xY w# t        $ r Y |S w xY w)uL   Check worktree + branch + (task-2521 §3) mtime / pushed commits / artefact.r   FN)rN   rO   rP   rQ   rR   r   z-*rN   c              3     K   | ]A  }t        |      j                         st        |      j                         j                   C y wr   r	   r   r   r   )r   cs     r#   r   z(_gather_worktree_info.<locals>.<genexpr>L  s2      #0147>>CSDGLLN++#
   A	(A	rP   r   z	ls-remotez--headsoriginr   *r   TrO   rQ   zls-remote failed for %s: %smemoryreportsrR   c              3     K   | ]A  }t        |      j                         st        |      j                         j                   C y wr   r   )r   ms     r#   r   z(_gather_worktree_info.<locals>.<genexpr>l  s2      !45DGNNDTQ//!r"  rU   )r   r  r9   r?   maxr   r   r   r   rj   r   r   r   r   r   r   r   r   r   )r:   r   r   r   worktrees_rootr  _globpattern
candidateslatest_mtimer   r   r   reports_dirmatcheslatestages                    r#   _gather_worktree_infor3  3  s?    
	424B,&N #(&*##(Fn'"~56ZZ(
$($4 !" #5?#   $<<5??ALP7?34
DKHgYa6HIB

 <<1!104F,-+/F'(8mi/;++wiqM:;G4801A  !9@! F #,,x||4>>@6IC<?F89 M6MK     D4gsCCD" ! A<@F89 MA Msz   6F AF   AF 4<G  1AG  	F	F FF 	FF	G'GGGG  GG   	G-,G-r   r   r  r   r   c               t   |	 t        | |      }|j                  }n|}t	        | |||      }|j                  d      }	|j                  d      }
|
rt        |
      j                         nd}|j                  d      }t        |t              r%|j                  d      xs |j                  d      }nt        |t              r|r|}nd}d}|j                  d	      }t        |t              rx|rv	 |j                  d
d      }t        j                  |      }t        j                  t        j                        j!                         }t#        d||j!                         z
        }d}|rt%        |||      }d}|	t'        |	||      }d}t)        |      }||  dz  }|j+                         r1	 |j-                  d      j                         }d|v rd}n	d|v rd}nd}t/        | ||      }|j                  d      }|j                  d      }t1        | |      }t3        | |      }t5        | ||      }t7        d4i d| d|	d|d|d|d|d |d!|d"|d#|d#   d$|d$   d%|d%   d&|d&   d'|d'   d(|d(   d)|d)   d*|d*   d+|d+   d,|d,   d-|d-   d.|j                  d.      d/t9        |j                  d/d            d0t9        |j                  d0d            d1dd2|d3|j                  d3      S # t        $ r
 t        }Y w xY w# t        $ r d}Y w xY w# t        $ r d}Y Vw xY w)5u  모든 evidence source 수집 — gh/git/file/timer/cron history.

    workspace_root가 None이면 resolve_canonical_workspace(task_id, fetch=False)로
    자동 결정 (§6 CanonicalWorkspace 연동). 실패 시 fallback Path('/home/jay/workspace').
    Nr   r   numberr`   mergeCommitoidsha	createdAtZ+00:00g        Fr   z
.qc-resultr   r   PASSFAILSKIPPEDr   r  end_timer   r  r:   r;   r=   r>   r@   rA   rB   rC   rD   rE   rF   rG   rH   rI   rJ   rK   rL   rM   rN   rO   rP   rQ   rR   rS   rT   rU   r!   )r   r   r   r   r   r   r9   upper
isinstancer   r  r   fromisoformatr   r   r   rj   r)  r   r   r   r   r   r   r   r  r3  r8   r?   )r:   r   r   r  r   r   cwsr   pr_infor;   	raw_stater=   raw_mcr>   rT   raw_creatediso
created_dtnow_tsr@   rA   rB   ev_dirqc_filecontent
timer_inforC   rD   markerscronwt_infos                                  r#   gather_evidencerS  }  s    	$&8&PC##B  	G  '{{84IG$I8Ac)n224tH [['F&$&,jj&7&L6::e;L	FC	 V ,0++k*K+s#	'%%c84C!//4J\\(,,/99;F"%c6J4H4H4J+J"K
 2
  $I%irR	 #'L_F'*--G~~		 '''9??AG %7"%(
 $GB\ZJ",..":L$.NN:$>N #72>G  :JKD $GF2NG   "	
 *  " " & $ /0 /0 o. ^, $$9:  %,,I$J!" "&&@!A#$   45%&   12'( !((A B), $+;;/K#L-.  ,@% HI/0 !%W[[1JE%R S12 36 078 %,KK0M$N9 e  	$#B	$F  	'"&	'<  	 L	 s6   L  1A5L 00L(  LLL%$L%(L76L7c                t   g }| j                   dk(  rT| j                  dk(  rE|j                  t        t        j
                  d| j                   d| j                   d             | j                  dk(  rD| j                  s8|j                  t        t        j                  d| j                   d             | j                  rD| j                  s8|j                  t        t        j                  d| j                   d	             | j                  d
k(  rz| j                  dk(  rk| j                  dk(  r\| j                  r| j                  sD|j                  t        t        j                  d| j                   d| j                                | j                   r8|j                  t        t        j"                  d| j$                   d             | j$                  dv rD| j&                  r8|j                  t        t        j(                  d| j$                   d             | j*                  rB| j,                  r6| j                  s*|j                  t        t        j.                  d             | j$                  dk(  r| j0                  duxr | j0                  t2        k  }| j                  dk(  }| j                  du }| j                   dk(  }|rk| j                  r| j                  sS|j                  t        t        j4                  d| j                   d| j                   d| j                   d             n|rE| j6                  r9|j                  t        t        j8                  d| j,                   d             n|r;|r9|j                  t        t        j:                  d| j*                   d             n\|rZ|j                  t        t        j<                  dt2        dd| j0                  dd| j                    d| j                                | j                  dk7  r| j>                  }| j0                  }|duxr	 |t@        k\  }|duxr	 |t@        k\  }	|xs |	}
| jB                  rK| j6                  s?| j,                  s3|
r1|j                  t        t        jD                  d | d!| d"             | j6                  rK| j,                  r?| j                  3|
r1|j                  t        t        jF                  d#| d$| d%             | j                  dk(  r|| jH                  }|duxr	 |t@        k\  }| j                  d
k(  rR|rP| j                  sD|j                  t        t        jJ                  d&| j                   d'|dd(t@        dd)             | jB                  rd| j                  | j                  d*v rJ|
rH|j                  t        t        jL                  d | d+| j                   d,| j                   d-             | jN                  d.k(  r| j                   dk(  r{| j                  dk7  rl| jP                  }tS        |tT        tV        f      r|dd/nd0}|j                  t        t        jX                  d1| jZ                   d2| d3t\         d4             | j^                  r|| j`                  xs d5d6kD  ri| j                  d7v xs | j                  dk(  xr | j&                   }|s9|j                  t        t        jb                  d8| j`                  dd9             |S ):u   8 케이스 자동 감지.runningr   z4timer_status=running but pr_state=MERGED (pr_number=, merge_commit=)ru   z4pr_state=MERGED but .done marker missing (pr_number=merge_commit=z  present but .merge-done missingr   r=  z5ci=SUCCESS, smoke=PASS, pr=MERGED but has_done_acked=z, has_merge_done=zAcron history last response appears truncated (bot_session_status=>   error	cancelledzbot_session_status=z0 but merged_into_main=True (task evidence is OK)uo   worktree exists + branch pushed to remote but no PR found — finish-task likely interrupted before PR creationrZ  NOPENz*bot_session=cancelled + pr_state=OPEN (pr=u$   ) + finalize 미완 (has_done_acked=zMbot_session=cancelled + pr_missing + commits_pushed (branch_pushed_to_remote=zKbot_session=cancelled + timer_status=running + pr_missing (worktree_exists=u1   bot_session=cancelled + worktree active (mtime≤z.0fzs ago: z.1fzs); timer_status=z, pr_state=z"report_artifact_present=True (age=z&) but no commits pushed; worktree_age=uM   ; AUTO_FINALIZE_CHAIN_MISSING — commit 단계로 자동 진입하지 않음z>branch_pushed_to_remote=True but pr_number=None; worktree_age=z, report_age=uP   ; AUTO_FINALIZE_CHAIN_MISSING — PR 생성 단계로 자동 진입하지 않음z-pr_state=OPEN + ci_status=SUCCESS (pr_number=z, pr_open_age=u   s ≥ uo   s) but no merge_commit; AUTO_FINALIZE_CHAIN_MISSING — bot identity merge 단계로 자동 진입하지 않음)r[  CLOSEDz) + pr_state=z (pr_number=uP   ); AUTO_FINALIZE_CHAIN_MISSING — 자체 검증 PASS 후 finalize 단계 미완STALEr   unknownzschedule_id=z freshness=STALE (age=u    ≥ uY   min) but timer_status=running — cron 응답 없는 schedule_id 로 활성 판정 차단r   r   >   r   zescalate marker exists for z# min but no Critical evidence found)2rC   r=   appendr[   r%   r&   r;   r>   rE   r'   rG   r(   rA   rB   rF   r)   rL   r*   rM   r@   r+   rN   rO   r,   rP   ._BOT_CANCELLED_ACTIVE_WORKTREE_MAX_AGE_SECONDSr1   rQ   r0   r.   r/   rU    _AUTO_FINALIZE_STUCK_AGE_SECONDSrR   r2   r3   rT   r4   r5   rW   rX   rB  intfloatr6   rV   !_SCHEDULE_FRESHNESS_THRESHOLD_MINrJ   rK   r-   )rd   cases	wt_activepr_open
pr_missingtimer_running
report_ageworktree_agereport_staleworktree_staleis_stalepr_agepr_age_staler   age_reprhas_active_criticals                   r#   detect_stuck_casesrs    si   E 	)h.?.?8.KY::&001AVAV@WWXZ
 	 H$X->->Y99&0014
 	 X%<%<YBB 5 566VW
 	 	i'!!V+)((0G0GY88""*"9"9!::KHLcLcKdf
 	 ((Y55''/'B'B&C1F
 	 ""&<<AZAZY<<%h&A&A%B C> ?
 	 	  ,,""Y66D
 	 ""k1 //t; >33=> 	
 ##v-''4/
 --: ''x/F/FLL"II@ASAS@T U77?7N7N6O P&&.&=&=%>aA  H77LL"GG0080P0P/QQRT  MLL"II((0(@(@'AD  LL"EE  NsSSZ::3? @$$,$9$9#:+hFWFWEXZ , H$99
:: d"Uz5U'U 	 $Y9Y)Y 	  1> ,,//44LL"::8 E$$0> 2bc  ''00""*LL"88$$0>zl Kef  &11Fd"Qv1Q'Q  ""i/  --Y&CCGHZHZG[ \''-cl&9YZ]8^ _RS  ,,""*$$(::LL"BB8 E  ( 1 12,x?Q?Q>R Sde * 	&&'1!!Y.)33",XU|"DxnA) 	 	Y00x334 5 z'H&I Jcd
 	 ##)M)M)RQRVX(X +- N(*L83L3L/L 	 #LL"881;;C@@ce  Lr"   c                0   t        |       }| j                  dk(  rR| j                  rF| j                  dv r8| j                  r,| j
                  r | j                  dv rt        j                  g fS | j                  dk(  r|| j                  rpg }| j                  s|j                  d       | j
                  s|j                  d       | j                  dk(  r|j                  d       |rt        j                  |fS | j                  dk(  r | j                  s|rt        j                  |fS | j                  dk(  r5| j                  dk(  r&|rt        j                  |fS t        j                  g fS | j                  dk(  r5| j                  dk7  r&|rt        j                  |fS t        j                  g fS | j                  r'| j                  xs d	}|d
k  rt        j                   |fS |rt        j                  |fS | j                  | j                  t        j                  g fS | j                  dk(  r| j                  t        j                  g fS | j                  dk(  r*| j                  r| j
                  rt        j                  g fS t        j                  g fS )u   evidence priority 규칙으로 state + stuck cases 결정.

    우선순위 (회장 명시):
    PR state > mergeCommit > origin/main 포함 > CI > smoke > timer > file marker
    r   >   Nr   >   N	completedz.done.ackedz.merge-donerU  z	timer-endr[  r   r   ru  )rs  r=   r@   rA   rF   rG   rC   r   r   r_  r   r   r   r   rJ   rK   r   )rd   rc   missingr2  s       r#   determine_staterw  0  sp    %X.K 	X%%%"33####!!%88''++ H$)B)B&&NN=)&&NN=)  I-NN;'!::KGG H$X-F-F!77DD F"x'<'<	'I!77DD%%r)) F"x'<'<	'I!77DD%%r)) ##227a"9!++[88 33[@@  X%:%:%B%%r)) 	)h.?.?.G%%r)) +0G0GHLcLc''++ !!2%%r"   c                  |xs
 t               }t        || d      }|j                  dk(  xr |j                  }t	        |j
                        xr. |j                  xr  |j                  dk(  xs |j                  dk(  }|xs |}|sRt        d|  d| d|j                   d|j                   d	|j
                   d
|j                   d|j                         y)u  evidence가 부족한데 강제로 .done을 만들려고 하면 RuntimeError.

    충분한 evidence:
    - pr_state == MERGED and merged_into_main, OR
    - merge_commit not None and merged_into_main and (ci_status == SUCCESS or smoke_status == PASS)
    r   r   r   r=  z"MANUAL_DONE_FORGERY_BLOCKED: task=uB    — insufficient evidence to create .done. Target path would be: zt. Requires (pr_state=MERGED + merged_into_main) OR (merge_commit + merged_into_main + ci/smoke PASS). Got: pr_state=z, merged_into_main=rV  z, ci_status=z, smoke_status=N)	r   r   r=   r@   r?   r>   rA   rB   RuntimeError)r:   rd   r   r   	done_pathcond_acond_bhas_sufficients           r#   assert_no_manual_done_forgeryr~  ~  s
    
	424BR&1I(*Hx/H/HFX""# 	Q%%	Q9,O0E0E0O 
 %vN0	 :%%.K 0 &..//B8C\C\B] ^$112,x?Q?Q>R S$1124
 	
 r"   c                   g }|j                   r|j                  d|j                           |j                  r|j                  d|j                          |j                  r|j                  d|j                          |j                  r|j                  d|j                          |j
                  r|j                  d       | d|t               ||j                  ddS )	z,Build JSON body for backfilled marker files.z	pr_state=rX  z
ci_status=zsmoke_status=zmerged_into_main=True lifecycle_reconciliation_managerz,evidence-based backfill (not manual forgery))r:   reconciled_byrk   rj   evidence_sourcer>   note)r=   r_  r>   rA   rB   r@   r   )r:   rk   rd   r  s       r#   _build_backfill_metadatar    s     "$O8+<+<*=>?x/D/D.EFGH,>,>+?@Ax/D/D.EFG  67 ;,Z* --> r"   c                d    | j                   j                  dd       | j                  |d       y )NTparentsexist_okr   r   )parentmkdir
write_text)pathrN  s     r#   _default_file_writerr    s)    KKdT2OOGgO.r"   c                <   t               t        z  }	 |j                         r|j                  d      nd}t	        j
                  |      }|j                  di       }||| <   |j                  t	        j                  |dd      d       y# t        $ r i }Y Nw xY w)	z2Write updated timer data back to task-timers.json.r   r   z{}r   r{   Fr|   N)
r   r   r   r   r   r   r   
setdefaultr  r   )r:   
timer_datar   r   r   r   s         r#   _default_timer_writerr    s    )+.??K9D9K9K9Mk##W#5SWzz# OOGR(EE'N4::d15IT[\	  s   9B BBF)r   applyfile_writertimer_writerc                  	 |xs
 t               }
||nt        ||nt        t         ||      }t	        |t
              r|j                  n
t        |      |d<   t        j                  |dd      d	fd}	 t         ||
       |j                  st!        |
 d      } |d|ffd	       |j"                  st!        |
 d      } |d|ffd	       |j$                  st!        |
 d      } |d|ffd	       |j&                  dk(  r t)               }d|d|d} |d|f fd	       |S # t        $ rJ}t        j                  d|       rj                  d	|        n	j                  d
       |cY d}~S d}~ww xY w)zBackfill missing markers based on evidence. Returns backfill_metadata.

    `state` is recorded in metadata so reconcile audit trails preserve which
    LifecycleState authorised the backfill.
    Nauthorising_stater{   Fr|   c                    r	  |        j                  |        y j                  |        y # t        $ r!}t        j                  d| |       Y d }~y d }~ww xY w)Nzbackfill action %s failed: %s)r_  r   r   rY  )action_namer   r   rg   rf   r  s      r#   
do_or_planz%_backfill_markers.<locals>.do_or_plan  sW    P$$[1 "";/  P<k3OOPs   0 	AAAr   zBackfill blocked: %szBLOCKED:zBLOCKED (insufficient evidence)r   created_donec                     |       S r   r!   r  fw	meta_jsons    r#   r  z#_backfill_markers.<locals>.<lambda>  s    "Q	2B r"   r   created_done_ackedc                     |       S r   r!   r  s    r#   r  z#_backfill_markers.<locals>.<lambda>  s    1i8H r"   r   wrote_merge_donec                     |       S r   r!   r  s    r#   r  z#_backfill_markers.<locals>.<lambda>  s    bI6F r"   rU  ru  r  )r  r@  ended_byrk   ended_timerc                     |       S r   r!   )tpr:   tws    r#   r  z#_backfill_markers.<locals>.<lambda>  s    2gr? r"   )r  r9   r   zCallable[[], None]r   None)r   r  r  r  rB  r   rw   r9   r   r   r~  ry  r   r   r_  rE   r   rF   rG   rC   r   )r:   rk   rd   r`   r   r  r  r  rf   rg   r   metar  r   r  timer_end_isotimer_payloadr  r  r  s   `    `  ``       @@@r#   _backfill_markersr    s   & 
	424B#/5IB%17LB#G-=xHD/9%/PVYZ_V`D	

4>I0%gxK B0>D#BC ""B6'$)HI ""B6%'FG 	) 
!%: 0	
 	=M"JKKE  -s3  8C5!12""%DFs   D9 9	F?FFFc                    	 | j                         si S | j                  d      }|j                         si S t        j                  |      }t        |t              si S |S # t        $ r#}t        j                  d| |       i cY d}~S d}~ww xY w)z7task-timers.json / task-timers-archived.json safe read.r   r   z timers file read failed (%s): %sN)
r   r   r   r   r   rB  r   r   r   r   )r  r   r   r   s       r#   _read_timers_filer  4  sz    {{}Inngn.yy{Izz#$%I 94E	s-   A! #A! &A! A! !	B*BBBc                   t        |      }t        |j                  d      t              r| |d   v ryt        |      }t        |j                  d      t              r| |d   v ryy)uB   Return (present, source) where source ∈ {"active","archive",""}.r   )Tactive)Tarchive)Fri   )r  rB  r   r   )r:   active_patharchive_pathr  r  s        r#   _timer_entry_presentr  E  s^     {+F&**W%t,F7O1K-G'++g&-'WW=M2Mr"   c                :   t        | |d      }|j                         si S 	 |j                  d      j                         }|si S t	        j
                  |      }t        |t              si S |S # t        $ r#}t        j                  d||       i cY d}~S d}~ww xY w)z<Read memory/events/<task_id>.done JSON if present (else {}).r   r   r   z done JSON read failed for %s: %sNr   r   r   r   r   r   rB  r   r   r   r   r   r:   r  r   r   r   s         r#   _read_done_jsonr  U  s    ^Wf5A88:	
kk7k+113Izz#$%I 97CH	)   #A. &A. ,A. .	B7BBBc                :   t        | |d      }|j                         si S 	 |j                  d      j                         }|si S t	        j
                  |      }t        |t              si S |S # t        $ r#}t        j                  d||       i cY d}~S d}~ww xY w)zBRead memory/events/<task_id>.merge-done JSON if present (else {}).r   r   r   z&merge-done JSON read failed for %s: %sNr  r  s         r#   _read_merge_done_jsonr  g  s    ^Wl;A88:	
kk7k+113Izz#$%I ?#N	r  c               v   t        ||       }t        ||       }|j                  d      xs7 |j                  d      xs$ |j                  d      xs |j                  d      }|j                  d      xs |j                  d      }|j                  d      xs |j                  d      }|j                  d      xs |j                  d      }	|rdn|rdnd}
|sm|j                  ra	 t	        || d	      }t        j                  |j                         j                  t        j                  
      }|j                         }|
xs d}
|sm|j                  ra	 t	        || d      }t        j                  |j                         j                  t        j                  
      }|j                         }|
xs d}
|syd}|rQ	 ddlm} t        j                  |j!                  dd            }| |t#        |            z
  }|j                         }||}| |||dd||
xs dt%               dd
}|	 t#        |      |d<   |	r|	|d<   |j&                  r|j&                  |d<   |j(                  |j(                  |d<   |S # t        $ r Y @w xY w# t        $ r Y w xY w# t        $ r d}Y w xY w# t        $ r Y zw xY w)uA  Construct timer entry from .done / .merge-done evidence.

    Priority:
      1. .done JSON (task-2528 primary source — has end_time/duration/team_id)
      2. .merge-done JSON
      3. .done file mtime (last resort; flagged in metadata)
    Returns None if no completion evidence available (caller decides no-op).
    r@  completed_atteam_idduration_seconds	qc_result	done_jsonmerge_done_jsonNr   )tz
done_mtimer   merge_done_mtimer   )	timedeltar;  r<  )secondsru  r  rd   u<   task-2528: worktree completion → task-timers.json backfill)
r:   r  
start_timer@  r  r  rk   reconciled_fromreconciled_atr  r>   r;   )r  r  r   rE   r   r   fromtimestampr   r   r   r   r   r   rG   r  rC  r  rc  r   r>   r;   )r:   rk   rd   r   	done_datamerge_done_datar@  r  r  r  sourcerz  r   md_pathr  r  end_dtstart_dtentrys                      r#   _build_reconciled_timer_entryr  y  s     8I+NGDO 	j! 	/==(	/z*	/ ~.	  	i  	*y) 
 	() 	312 
 	k" 	,{+ 
 &[,=VZF ))	$^WfEI**9>>+;+D+DVE(H+|F //	">7LIG**7<<>+B+Bx||TE(H11F  !%J	*++H,<,<S(,KLF	%8H2I JJH!++-J 
  6,!/Z!NE #	(-.>(?E$% &k ( 5 5n%%//kLk  		  		  	J	(  		sK   (A I< A J ?AJ /J, <	J	J		JJJ)(J),	J87J8)r   r  active_timer_inserterc               l   |xs
 t               }|t        z  }	|t        z  }
|j                  xs |j                  xs |j
                  dk(  }| |ddddd}|sd|d<   d|d<   |S t        | |	|
	      \  }}|rd|d<   d
| |d<   |S t        | |||      }|d|d<   d|d<   |S |j                  d      |d<   ||d<   |}||fdd}|}d}|r"	  || |       d|d<   |j                  |       |S |j                  |       |S # t        $ r*}t        j                  d| |       d| |d<   Y d}~|S d}~ww xY w)uB  Bridge worktree-completion evidence into task-timers.json (task-2528 fix).

    Trigger conditions (ALL required):
      - Has completion evidence: has_done OR has_merge_done OR pr_state == MERGED
      - Active task-timers.json has no entry for task_id
      - Archived task-timers-archived.json has no entry for task_id

    On match: build entry from .done JSON / .merge-done JSON / mtime fallback,
    insert into active timers (idempotent — no-op if any subsequent run finds entry).

    Returns metadata dict (caller stores it in LifecycleReport.backfill_metadata).
    r   FN)r:   rk   skippedr\   entry_insertedr  Tr  no_completion_evidencer\   )r  r  timer_entry_present_in_r   %no_usable_evidence_for_reconstructionr  r  proposed_entryc                B   |t         z  }t        |      }t        |t              si }|j	                  di       }t        |t              si }||d<   | |v ry ||| <   |j
                  j                  dd       |j                  t        j                  |dd      d       y )	Nr   Tr  r{   Fr|   r   r   )
r   r  rB  r   r  r  r  r  r   r   )tident_wdr   r   r   s         r#   _scoped_inserterzB_reconcile_worktree_completion_to_timers.<locals>._scoped_inserter)  s     11K$[1DdD)OOGR0EeT* %We|E#J$$TD$A""

4>  # r"   reconciled_worktree_timerr  z1worktree timer reconcile insert failed for %s: %szinsert_failed: )r  r9   r  r   r  r	   r   r  )r   r   _TIMERS_ARCHIVE_FILE_NAMErE   rG   r=   r  r  r   r_  r   r   rY  )r:   rk   rd   r   r  r  rf   rg   r   r  r  has_completion_evidencer  presentr  r  inserterr  action_labelr   s                       r#   (_reconcile_worktree_completion_to_timersr    s   . 
	424B((K11L 	 	)""	)(  ,D #Y1X*!OGV
 Y26(;X)	E }Y@XYY01DN"D	$H>@ 	$ $.L	5We$%)D!"  . K 	|,K  	5LLLgWZ[.se4DN K	5s   D   	D3	D..D3)r  r   r   r  r   r   r  r  c                  t        j                         j                  }	t               }
t	        | |||||      }t        |      \  }}g }g }i }|t        j                  k(  rt        j                  d|        nY|t        j                  t        j                  hv rt        | |	||||||||
      }n!t        j                  d| |j                         t        | |	|||||      }|j!                  d      s|j#                  d|       n7|j!                  d      r&|d   j%                  d	      r|j#                  d|       t'        | |||||| |
|	|

      S )uo  idempotent reconcile.

    - state == FINALIZED → no-op
    - state == MERGED_PENDING_RECONCILE + apply=True → backfill with evidence metadata
    - state == STUCK_NEEDS_RECONCILE + apply=True → reason별 backfill
    - apply=False → actions_planned에만 기록, 실제 변경 없음
    - 동일 reconcile 반복 호출 → 동일 state, no-op (멱등)
    r4  z task=%s already FINALIZED, no-op)r   r  r  r  rf   rg   z$task=%s state=%s, no backfill needed)r   r  rf   rg   r  worktree_timer_reconciler\   r  rv   )uuiduuid4hexr   rS  rw  r   r   r   infor   r   r  debugrw   r  r   r  
startswithr_   )r:   r  r   r   r  r   r   r  r  rk   rj   rd   r`   rc   rf   rg   rl   timer_reconcile_metas                     r#   	reconciler  P  s~   ( zz|''
I%)!H )2E;!M!#O(*(((6@	//,, 
 .)#%'+
 	;WekkR
 D%#'  ##I.$$%?AUV		!	!(	+0DX0N0Y0YZs0t$$%?AUV#'	)+ r"   )r   r   r  r   c                   | xs
 t               }|t        z  }g }	 |j                         rP|j                  d      }t	        j
                  |      }t        |j                  di       j                               }|st        j                  d       g S g }
|D ]o  }d}|d
d} ||      }	 t        |||||      }|j                  t        j                   t        j"                  t        j$                  hv r|
j'                  |       q |
S # 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)u:   task-timers.json 전체 순회 → STUCK인 것만 보고.r   r   r   z/scan_stuck: failed to load task-timers.json: %sNz.scan_stuck: no tasks found in task-timers.jsonc                     d fd}|S )Nc                V            }|j                  di       j                  | i       S )Nr   )r   )r:   
all_timerstls     r#   _adapterz3scan_stuck.<locals>._make_adapter.<locals>._adapter  s(    !#J%>>'26::7BGGr"   )r:   r9   r   r   r!   )r  r  s   ` r#   _make_adapterz!scan_stuck.<locals>._make_adapter  s    H  r"   )r  r   r   r   z'scan_stuck: reconcile failed for %s: %s)r  zCallable[[], dict]r   zCallable[[str], dict])r   r   r   r   r   r   r   r   keysr   r   r   r  r  r`   r   r   r   r   r_  rY  )r   r   r  r   r   r   task_idsr   r   r   stuck_reportsr  adapted_loaderr  reports                  r#   
scan_stuckr    sp    
	424B((KHO'''9C::c?DDHHWb16689H DE	+-M N:># 
 +<8N	N!+F ||4477((  
 $$V,1N8 K  OH#NNOD  	NLLBCMM	Ns1   A D ,AD8	D5D00D58	E"EE"c                "    | j                   rdS dS )u   automation_contracts.SmokeResult → lifecycle smoke_status string.

    expects post_merge_smoke_runner.SmokeResult.passed →
    smoke_status='PASS' if True else 'FAIL'
    r=  r>  )passed)srs    r#   smoke_result_to_statusr    s     YY6*F*r"   c                   | j                   t        j                  k(  rd}d}d}n\| j                   t        j                  k(  rd}d}t        j
                  }n*| j                   t        j                  k(  rd}d}d}nd}d}d}t        || j                  D cg c]  }|j                  j                   c}|| || j                  | j                  | j                  d	      S c c}w )
u   LifecycleReport를 AutomationDecision으로 매핑 (자동화 의사결정 contract 연동).

    회장 §6: CanonicalWorkspace + automation_contracts 연동 필수.
    NO_OPFNESCALATETBACKFILLMONITOR)r:   rk   rj   )decisionreason_codescritical_escalation_typeauto_handledrequires_chairaudit)r`   r   r   r   r   POST_MERGE_SMOKE_FAILEDr   r   rc   r\   rw   r:   rk   rj   )r  decision_strr  criticalr!  s        r#   build_automation_decisionr    s    
 ||~///59	11	1!)AA	@@	@! .4.@.@AahhnnA!)((%~~ & 7 7))
 As   C!
)r;   c                   | j                   t        j                  k7  rydj                  d | j                  D              xs d}t        | j                  |t        j                  |dg dd| j                               S )	u   ESCALATED state에 대해 EscalationPacket 생성. 그 외 None.

    ⚠️ lifecycle은 일반적으로 ESCALATED를 직접 발동하지 않으므로,
    이 헬퍼는 explicit caller가 호출해야 함.
    Nz; c              3  f   K   | ])  }|j                   j                   d |j                    + yw)z: N)r\   rw   r]   )r   rx   s     r#   r   z*build_escalation_packet.<locals>.<genexpr>%  s-      .0299??
2bii[)s   /1z"lifecycle ESCALATED state detecteduH   lifecycle state is ESCALATED — human review required before proceeding)'Review stuck_cases and resolve manuallyz0Run reconcile --apply after resolving root causez!Escalate to chair if unresolvabler  )r:   r;   escalation_typer\   why_auto_cannot_continuesafe_optionsrecommended_optionrd   )
r`   r   r   joinrc   r   r:   r   r  rs   )r  r;   stuck_detailss      r#   build_escalation_packetr    s     ||~///II 4:4F4F  .	-  .FFV

 E((* r"   c                 J   t        j                  d      } | j                  d      }|j                  ddd       |j                  d	dd
       | j                  dd       | j                  dddd       | j                  ddddd       | j                  dd d       | S )NuW   lifecycle_reconciliation_manager — idempotent task lifecycle reconcile (task-2518 P0))descriptionT)requiredz--reconcile
store_truez,Reconcile a single task (requires --task-id))actionhelpz--scan-stuckz8Scan all tasks in task-timers.json and report stuck onesz	--task-idz/task-NNNN identifier (required for --reconcile))r!  z--applyFz*Actually apply backfill (default: dry-run))r   defaultr!  r   output_jsonzOutput as JSON (default: True))r   destr"  r!  z--workspace-rootzOverride workspace root path)r"  r!  )argparseArgumentParseradd_mutually_exclusive_groupadd_argument)r  groups     r#   _build_cli_parserr*  ?  s    A	A **D*9E	;  
 
G  
 NN;%VNWNN9	   NN-   NN+  
 Hr"   c           	        t               }|j                  |       }d }|j                  rt        |j                        }|j                  rZ|j
                  s|j                  d       	 t	        |j
                  |j                  |      }t        |j                                y |j"                  rW	 t#        ||j                        }|D cg c]  }|j%                          }}t        t        j                  |dd	             y y # t        $ rc}t        t        j                  t        |      |j
                  d      t        j                         t        j                   d       Y d }~y d }~ww xY wc c}w # t        $ rX}t        t        j                  d
t        |      i      t        j                         t        j                   d       Y d }~y d }~ww xY w)Nz--reconcile requires --task-id)r  r   )rY  r:   )file   )r   r  r{   Fr|   rY  )r*  
parse_argsr   r	   r  r:   rY  r  printr   r   r   r   r9   sysstderrexitr  ry   )	argvparserr   r   r  r   r&  r   outputs	            r#   	_cli_mainr6  g  sb    FT"D%)Nd112~~||LL9:		jj-F
 &.."#
 
		 -jjG ,33aaiik3F3$**VAEBC 
	  	$**s3xDLLIJQTQ[Q[\HHQKK	 4 	$**gs3x01

CHHQKK	sD   );D	 2E= E8$#E= 		E5AE00E58E= =	GAGG__main__z#%(levelname)s %(name)s: %(message)s)levelformat)r   re   r   r<   r   subprocess.CompletedProcess)r   re   r   zOptional[str | Path]r   Optional[RunnerType]r   r:  r   )r   r	   r   r	   )r   r	   r:   r9   r   r9   r   r	   )r   r	   )r:   r9   r   r;  r   r   )
r:   r9   r   r;  r   Optional[Path]r   Optional[Callable[[str], dict]]r   r   )r>   r9   r   r;  r   r<  r   r?   )r;   rb  r   r;  r   r<  r   r<   )r:   r9   r   r<  r   r=  r   r   )r:   r9   r   r<  r   r   )r   r9   r   r?   )r:   r9   r  r<  r   r   )r:   r9   r   r;  r   r<  r   r   )r:   r9   r   r<  r   r;  r  r<  r   r=  r   r=  r   r8   )rd   r8   r   rb   )rd   r8   r   z&tuple[LifecycleState, list[StuckCase]])r:   r9   rd   r8   r   r<  r   r  )r:   r9   rk   r9   rd   r8   r   r   )r  r	   rN  r9   r   r  )r:   r9   r  r   r   r  )r:   r9   rk   r9   rd   r8   r`   r   r   r<  r  r?   r  %Optional[Callable[[Path, str], None]]r  %Optional[Callable[[str, dict], None]]rf   re   rg   re   r   r   )r  r	   r   r   )r:   r9   r  r	   r  r	   r   ztuple[bool, str])r   r	   r:   r9   r   r   )
r:   r9   rk   r9   rd   r8   r   r	   r   zOptional[dict])r:   r9   rk   r9   rd   r8   r   r<  r  r?   r  r?  rf   re   rg   re   r   r   )r:   r9   r  r?   r   r<  r   r;  r  r<  r   r=  r   r=  r  r?  r  r>  r   r_   )
r   r<  r   r;  r  r?   r   zOptional[Callable[[], dict]]r   zlist[LifecycleReport])r  r   r   r9   )r  r_   r   r   )r  r_   r;   rb  r   zOptional[EscalationPacket])r   zargparse.ArgumentParserr   )r3  zOptional[list[str]]r   r  )`r    
__future__r   r%  r   loggingr   r0  r  dataclassesr   r   r   r   enumr   pathlibr	   typingr
   r   r   __file__resolver  _HEREr9   r  insert"utils.canonical_workspace_resolverr   r   utils.automation_contractsr   r   r   r   utils.schedule_id_freshnessr   rd  	getLoggerr   r   CompletedProcess
RunnerTyper   r  r   r   r  r   r   r%   r`  ra  r8   r[   r_   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r3  rS  rs  rw  r~  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r*  r6  basicConfigINFOr!   r"   r#   <module>rR     s  $J #     
  ( '   * * 	X ''..u:SXXHHOOAs5z"  
		8	$
 c:6667
/0  !GH " - >  (, $	S$ 	,#t ,D 27 . $)   14 14 14h   
 &H &H &H` 
 
 !	" !%#'	?
? 
? !	?
 !?2-? HL 
4 $(%)15)) !) #	)
 /) 
)^ $(%)	 ! #	
 
2 $(%)	   !  #	 
  L &*48	 # 2	
 
< &* # 
	B, (,66 %6 
	6x $(%)	CC !C #	C
 
CZ &*#''+1548BB #B !	B
 %B /B 2B BRcT	G&d &*	 
 
 
 #	 

 
 
N   
	:/

]& &*9=:>JJJ  J 	J #J J 7J 8J J J 
J@"  	
  $$ccc  c
 c cV &*CGjjj  j
 #j j Aj j j 
jh %)#''+1548:>9=YY Y #	Y
 !Y %Y /Y 2Y 8Y 7Y YD &*#'156"6 !6 	6
 /6 6z+!J 23+.J%P F zGgll3XYK r"   