
    (<il&              	       $   d 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  ed      Z	e	dz  dz  Z
e	dz  dz  Zd	ed
efdZdeded
dfdZd
efdZded
efdZd
efdZ	 ddedededz  d
efdZd
ej(                  fdZddZedk(  r e        yy)u  todo_sync.py

task-637.1 TDD GREEN Phase — utils/todo_sync.py 구현 (불칸)

기능:
- sync_task_completion(task_id): todo.json의 sub_item을 완료 처리
- retroactive_sync(): task-timers.json에서 완료된 태스크를 일괄 동기화
- link_task_to_issue(task_id, issue_id, sub_item_title): 태스크↔이슈 연결

안전장치:
- 수정 전 .bak 백업
- fcntl 파일 락 (동시 수정 방지)
- done=True → done=False 불가 (단방향)
- 변경 시 last_synced 필드 기록
    N)datetimetimezone)Pathz/home/jay/workspacememoryz	todo.jsonztask-timers.jsonpathreturnc                 p    | j                         si S t        j                  | j                  d            S )uJ   JSON 파일을 로드한다. 파일이 없으면 빈 dict를 반환한다.utf-8encoding)existsjsonloads	read_text)r   s    @/home/jay/workspace/.worktrees/task-2057-dev2/utils/todo_sync.py
_load_jsonr   '   s*    ;;=	::dnngn677    data	todo_filec                 D   |j                   |j                  dz   z  }|j                         r|j                  |j	                                |j                   j                  dd       t        t        |      dd      5 }t        j                  |t        j                         	 t        j                  | |dd	       t        j                  |t        j                         	 d
d
d
       y
# t        j                  |t        j                         w xY w# 1 sw Y   y
xY w)uY   todo.json을 .bak 백업 후 저장한다. fcntl 락으로 동시 수정을 방지한다.z.bakT)parentsexist_okwr
   r   F   ensure_asciiindentN)parentnamer   write_bytes
read_bytesmkdiropenstrfcntlflockLOCK_EXr   dumpLOCK_UN)r   r   bak_filefs       r   
_save_todor,   .   s    9>>F#:;H Y1134 4$7	c)ncG	4 *Au}}%	*IIdAE!<KK5==)* *
 KK5==)* *s$    %D&C-?$D-&DDDc                      t        j                  t        j                        j	                         j                         S )u4   현재 시각을 ISO 8601 형식으로 반환한다.)r   nowr   utc
astimezone	isoformat r   r   _now_isor3   @   s'    <<%002<<>>r   task_idc                     t        t              }|sdg dS d}g }d}|j                  dg       }|D ]  }|j                  dd      }|j                  dg       }|D ]b  }	|	j                  d       k(  s|	j                  d	      d
u r,d
|	d	<   |dz  }d
}|j                  d| d|	j                  d       d  d        n |j                  dg       }
 |
v rt	         fd|D              }t	         fd|D              }|si|D ]d  }	|	j                  d      |	j                  d	      du s)d
|	d	<    |	d<   |dz  }d
}|j                  d| d|	j                  d       d  d        n |s<t        d |D              sP|j                  d      d	k7  sfd	|d<   t               |d<   d
}|j                  d| d        |rt               |d<   t        |t               ||dS )u  todo.json에서 task_id에 해당하는 sub_item을 완료 처리한다.

    매칭 전략:
    1. 직접 매칭: sub_item.task_id == task_id → done=True
    2. linked_tasks 매칭: issue.linked_tasks에 task_id 포함
       → 해당 issue의 미완료 sub_items 중 task_id=None인 첫 번째 항목을 done=True + task_id 설정

    Returns:
        {"updated": int, "details": list[str]}
    r   )updateddetailsFissuesid 	sub_itemsr4   doneT   u   [직접매칭] issue=z, title=titlez
, task_id=u    → done=Truelinked_tasksc              3   p   K   | ]-  }|j                  d       k(  xr |j                  d      du  / yw)r4   r<   TNget.0sr4   s     r   	<genexpr>z'sync_task_completion.<locals>.<genexpr>s   s6     #m^_AEE)$4$?$YAEE&MUYDY$Y#ms   36c              3   F   K   | ]  }|j                  d       k(    yw)r4   NrA   rC   s     r   rF   z'sync_task_completion.<locals>.<genexpr>t   s     &VqquuY'77'B&Vs   !u   [linked_tasks매칭] issue=c              3   B   K   | ]  }|j                  d       du   yw)r<   TNrA   )rD   rE   s     r   rF   z'sync_task_completion.<locals>.<genexpr>   s     FqQUU6]d2Fs   statuscompleted_atu   [이슈완료] issue=u%    → status=done, completed_at 기록last_synced)r   	TODO_FILErB   appendanyallr3   r,   )r4   r   r6   r7   changedr8   issueissue_idr;   subr?   direct_match_donedirect_match_updateds   `            r   sync_task_completionrV   J   s.    i D,,GGG(B/F )h		$+ %		+r :	  		Cwwy!W,776?d*"F1!6xjQXIYHZZdeldmm{|}		 #())NB"?l" ##mcl#m m#&&VI&V#V '$ 	Cwwy)1cggfo6N&*F)0I1"&9(8CGGT[L\K]]ghogpp~ 	 FIFFyy"f,"(h(0
n%!6xj@efgS)hV &j]4#733r   c                     t        t              } | j                  di       }d}g }|j                         D ]J  \  }}|j                  d      dk(  st	        |      }|d   dkD  s/||d   z  }|j                  |d          L ||dS )u   task-timers.json에서 status=completed인 모든 task_id를 일괄 동기화한다.

    Returns:
        {"total_synced": int, "details": list[str]}
    tasksr   rI   	completedr6   r7   )total_syncedr7   )r   TIMERS_FILErB   itemsrV   extend)timers_datarX   rZ   all_detailsr4   	task_inforesults          r   retroactive_syncrb      s     [)K//'2.ELK#kkm 6=="k1)'2Fi 1$y 11""6)#456 )[AAr   rR   sub_item_titlec                    t        t              }|sdddS |j                  dg       }d}|D ]  }|j                  d      |k(  s|} n |	dd| ddS d}g }|j                  d	g       }	| |	vr)|	j	                  |        d
}|j	                  d|  d       n|j	                  d|  d       |c|j                  dg       }
|
D ]7  }|j                  d      |k(  s| |d<   d
}|j	                  d| d|  d        n |j	                  d| d       |rt               |d<   t        |t               d
dj                  |      dS )uU  issue의 linked_tasks에 task_id를 추가하고, 선택적으로 sub_item.task_id도 설정한다.

    Args:
        task_id: 연결할 태스크 ID
        issue_id: 연결 대상 이슈 ID
        sub_item_title: 지정 시 해당 title의 sub_item.task_id에도 task_id 설정

    Returns:
        {"linked": bool, "message": str}
    Fu(   todo.json을 로드할 수 없습니다.)linkedmessager8   Nr9   z	issue_id=u   를 찾을 수 없습니다.r?   Tu   linked_tasks에 u    추가u    이미 존재r;   r>   r4   z
sub_item 'z'.task_id = u    설정zWARNING: sub_item_title='u   '을 찾을 수 없습니다.rK   z / )r   rL   rB   
setdefaultrM   r3   r,   join)r4   rR   rc   r   r8   target_issuerQ   rP   messagesr?   r;   rS   s               r   link_task_to_issuerk      s    i D.VXX(B/F $L 99T?h& L
 izA],^__GH +55nbILl"G$*7)7;<*7)>BC ! , 0 0b A	 	gCwww>1!(I*^,<L	QX YZ	g OO77GGdef&j]4#uzz(';<<r   c                  h   t        j                  d      } | j                  d      }|j                  dd      }|j	                  dd	d
       |j                  dd       |j                  dd      }|j	                  dd	d       |j	                  dd	d       |j	                  dd d       | S )Nu.   todo_sync — todo.json 동기화 유틸리티)descriptioncommand)destsyncu!   특정 태스크를 완료 처리)helpz	--task-idTu   태스크 ID (예: task-635.1))requiredrq   retroactiveu$   completed 태스크 일괄 동기화linku   태스크를 이슈에 연결u   태스크 IDz
--issue-idu	   이슈 IDz
--sub-itemu   연결할 sub_item title)defaultrq   )argparseArgumentParseradd_subparsers
add_parseradd_argument)parser
subparserssync_parserlink_parsers       r   _build_parserr      s    $$1abF&&I&6J ''5X'YK[4>^_ -.TU ''5T'UK[4nM\D{K\4>XYMr   c                  >   t               } | j                         }|j                  dk(  r7t        |j                        }t        t        j                  |dd             y |j                  dk(  r,t               }t        t        j                  |dd             y |j                  dk(  rNt        |j                  |j                  |j                        }t        t        j                  |dd             y | j                          t        j                  d       y )	Nrp   Fr   r   rs   rt   )r4   rR   rc   r=   )r   
parse_argsrn   rV   r4   printr   dumpsrb   rk   rR   sub_item
print_helpsysexit)r{   argsra   s      r   mainr      s    _FD||v%dll3djjeA>?		&!#djjeA>?		#LL]]==

 	djjeA>? 	r   __main__)N)r   N)__doc__rv   r%   r   r   r   r   pathlibr   WORKSPACE_ROOTrL   r[   dictr   r,   r$   r3   rV   rb   rk   rw   r   r   __name__r2   r   r   <module>r      s       
 '  +,X%3	x'*<<8T 8d 8*T *d *t *$?# ?D4# D4$ D4NB$ B2 "&:=:=:= $J:= 
	:=Dx.. (2 zF r   