
    ZiM                       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	Z	ddl
m
Z
mZmZ ddlmZ ej                  j!                  dd      Z ee      dz  d	z  Z ee      dz  d
z  Z ee      dz  dz  Z ee      dz  dz  Z ee      dz  dz  Z e ed            ZdZdZdZdZd)dZd*dZd+dZd,dZ ej@                  d      Z!d-dZ"d.dZ#d,dZ$d/dZ%d0dZ&d1dZ'	 	 	 	 	 	 	 	 	 	 d2d Z(d3d!Z)	 	 	 	 d4	 	 	 	 	 	 	 	 	 	 	 	 	 d5d"Z*d6d#Z+d7d$Z,d7d%Z-d8d&Z.d9d'Z/e0d(k(  r e	jb                   e/              yy):u  봇 → 아누 후속 알림 메시지 추출 + 포맷 + 발송 헬퍼 (task-2360).

3단 우선순위로 메시지 항목을 수집한다:
1. memory/events/<task>.followup.txt — 봇이 명시적으로 작성한 강조 메모
2. memory/reports/<task>.md — 보고서에서 정규식으로 자동 추출
3. memory/events/<task>.completion.txt — 폴백 1줄 요약

표준 5섹션 메시지 포맷 (아누 chat에 cron 발송):
- 핵심 결과
- 회장 결정 필요
- 머지 필요
- 미해결
- 다음 단계 권장

CLI 사용법:
    python3 extract_followup.py extract <task_id>           # 항목 dict JSON 출력
    python3 extract_followup.py format <task_id>            # 메시지 텍스트 출력
    python3 extract_followup.py send <task_id> [--dry-run]  # 메시지 cron 발송
    )annotationsN)datetime	timedeltatimezone)PathWORKSPACE_ROOTz/home/jay/workspacememoryeventsreportstasksztask-timers.jsonlogszanu-notify.log	   )hours
6937032012c119085addb0f8b7i     c                <   t        j                  t              j                         }d| d|  d| d}	 t        j
                  j                  dd       t        t        dd	      5 }|j                  |       d
d
d
       y
# 1 sw Y   y
xY w# t        $ r Y y
w xY w)u   logs/anu-notify.log 추가.[z] [extract-followup] : 
Tparentsexist_okautf-8encodingN)
r   nowKST	isoformatLOG_FILEparentmkdiropenwriteOSError)task_idmessagetslinefs        I/home/jay/workspace/.worktrees/task-2520-dev4/scripts/extract_followup.py_logr-   0   s    	c		$	$	&Brd'y7)2>DdT:(C'2 	aGGDM	 	 	 s/   3B (B:B BB B 	BBc                   d}d}	 t         j                         rt        j                  t         j	                  d            }|j                  d|      }|j                  | i       xs i }|j                  d      xs |j                  d      xs dj                         }|j                  d      xs |j                  d      xs dj                         }||fS # t        t        j                  f$ r Y ||fS w xY w)	uJ   task-timers.json에서 (team_id, bot_persona) 조회. 실패 시 ('', ''). r   r   r   team_idteampersonabot)	TIMERS_FILEexistsjsonloads	read_textgetstripr&   JSONDecodeError)r'   r0   bot_personadatar   ts         r,   _load_team_and_botr?   <   s    GK::k33W3EFDHHWd+E		'2&,"AuuY'>155=>BEEGG55+AquuU|ArHHJK K T))* Ks   CC C:9C:c                .   g g g g g d}t        j                  dt         j                        dft        j                  dt         j                        dft        j                  dt         j                        dft        j                  dt         j                        d	ft        j                  d
t         j                        dfg}d}g }| j                         D ]  }|j	                         }|j                         j                  d      r2d}|D ](  \  }}	|j                  |j                               s&|	} n |}e|0|j                         r|j                  |j                                t        j                  d|      }
|
r2||   j                  |
j                  d      j                                |j                         s|dk(  s||   j                  |j                                 t        |j                               s|r||d<   |S )us  followup.txt를 마크다운 섹션 단위로 파싱.

    예상 헤더 (대소문자/공백 관용):
    - ## 회장 결정 필요
    - ## 머지 필요
    - ## 미해결
    - ## 다음 단계 권장
    - ## 핵심 결과 (있으면 우선)

    각 섹션의 bullet(`- ` 또는 `* `)을 항목 리스트로 수집한다.
    헤더 외 본문은 무시한다.
    core_resultdecisions_neededmerges_needed
unresolved
next_stepsu   ^#+\s*핵심\s*결과rB   u   ^#+\s*회장\s*(결정|승인)rC   u   ^#+\s*머지\s*(필요|판단)?rD   u#   ^#+\s*(미해결|follow.?up|남은)rE   u,   ^#+\s*(다음\s*단계|next\s*steps?|후속)rF   N#z^\s*[-*]\s+(.+)$   )recompile
IGNORECASE
splitlinesrstriplstrip
startswithmatchr:   appendgroupanyvalues)textsections
header_mapcurrent
free_linesrawr*   matchedpatkeyms              r,   _parse_followup_txtr_   L   s    &H 
,bmm	<mL	5r}}	EGYZ	6	FX	:BMM	JLY	CR]]	SUabJ GJ  3zz|;;=##C(G& S99T[[]+!G G?zz|!!$**,/HH($/W$$QWWQZ%5%5%78ZZ\g6W$$TZZ\2+30 x !j",O    c                    t         |  dz  }|j                         sy	 |j                  d      }|j	                         syt        |      S # t        $ r Y yw xY w)u6   followup.txt가 있으면 dict 반환, 없으면 None.z.followup.txtNr   r   )
EVENTS_DIRr5   r8   r&   r:   r_   r'   fprU   s      r,   _read_followup_filere      sa    		/	/B99;||W|- ::<t$$	  s   A 	AAz^\s*(?:[-*]|\d+\.)\s+(.+)$c                l   t        j                  dj                  |      t         j                        }| j	                  d      }g }d}|D ]  }t        j
                  d|      rR|j                  |      rd}-|r;t        j
                  d|      }|rt        |j                  d            nd}|d	k  rd}k|snt        j                  |      }	|	s|	j                  d      j                         }
t        j                  d
d|
      }t        j                  dd|      }|s|j                  |        |S )u?   보고서에서 헤더 매칭되는 섹션 bullet들을 수집.|r   Fz
^#{1,4}\s+Tz^(#+)rH   c      z\*\*(.+?)\*\*z\1z	`([^`]+)`)rI   rJ   joinrK   splitrP   searchlenrR   
_BULLET_REr:   subrQ   )contentheader_patternsr\   linesitems
in_sectionr*   level_match	cur_levelr^   rU   cleaneds               r,   _extract_section_bulletsrx      s   
**SXXo.
>CMM$EEJ &88M4(zz$!
 hhx69DC 1 1! 45"	>!&JT"771:##%Dff-ud;Gff\5':GW%-&. Lr`   c                ~   | j                  d      }t        j                  d      }t        |      D ]  \  }}|j	                  |      s|j                  d|      j                         }|r|gng }||dz   d D ]Q  }|j                  d      r n>t        j                  d|      r n&|j                  d      r n|j                  |       S dj                  |      j                         }|s|c S  d	}	g }
|D ]t  }t        j                  d
|t        j                        rd}	+|	s.|j                  d      r n6|j                         r |
j                  |j                                q|
st n |
rdj                  |
      j                         S d}t        |      D ]  \  }}|j                  d      s|} n |g }d	}||dz   d D ]N  }|j                  d      r|r n9|j                         r"d}|j                  |j                                K|sN n dj                  |      j                         }|r|S y)u5   SCQA의 A 섹션 또는 첫 본문 한 단락 추출.r   z^\s*\*\*A[.:]?\*\*[.:]?\s*r/   rH   Nz---z^\s*\*\*[A-Z]\*\*rG   Fz^#{2,4}\s*Answer\bT z## )rk   rI   rJ   	enumeraterP   ro   r:   rO   rQ   rj   rK   )rp   rr   a_patir*   tail	collectednxtrU   in_ans	paragraphfirst_section_idxparain_paras                 r,   _extract_core_resultr      s-   MM$E JJ45EU# 4;;t99R&,,.D"&BIQUW~ &>>%(880#6>>#&  %& 99Y'--/D" FI 
88)4?Fs#zz|  .
 xx	"((** %)U# 4??5! ! $+a/12 		Ds#zz|DJJL)		 xx~##%Kr`   c                B   t         |  dz  }|j                         sy	 |j                  d      }g g g g g d}t	        |      }|r|g|d<   t        |ddg      |d	<   t        |d
dg      |d<   t        |g d      |d<   t        |g d      |d<   |S # t        $ r Y yw xY w)u   보고서에서 자동 추출..mdNr   r   rA   rB   u%   ^#+\s*회장\s*(결정|승인|결재)z^#+\s*decisions?\s*neededrC   u   ^#+\s*머지\s*(필요|판단)u   ^#+\s*merge\s*(needed|판단)rD   )u!   ^#+\s*(범위\s*내\s*)?미해결z^#+\s*follow.?upu   ^#+\s*발견\s*이슈rE   )u   ^#+\s*다음\s*단계u   ^#+\s*후속\s*taskz^#+\s*next\s*steps?rF   )REPORTS_DIRr5   r8   r&   r   rx   )r'   rd   rp   rV   cores        r,   _read_reportr      s    	'#	&B99;,,,0
 H  (D#'&#;:<XY$H  !935UV!H_ 6	
H\ 6	
H\ OI  s   B 	BBc                    t         |  dz  }|j                         sy 	 |j                  d      j                         }|xs d S # t        $ r Y y w xY w)Nz.completion.txtr   r   )rb   r5   r8   r:   r&   rc   s      r,   _read_completionr   (  s]    		1	1B99;||W|-335 <4  s    A 	AAc                6   ddg g g g d}t        |       }t        |       }g g g g g d}g }|r;|D ]%  }|j                  |      st        ||         ||<   ' |j	                  d       |rZ|D ]+  }||   r	|j                  |      st        ||         ||<   - t        |j                               r|j	                  d       t        d |j                         D              }|sGt        |       }|r:|j                  dd	      d
   j                         }	|	g|d<   |j	                  d       |d   r0t        |d         d	k(  r|d   d
   ndj                  |d         |d<   |d   |d<   |d   |d<   |d   |d<   |d   |d<   |rdj                  |      nd|d<   |S )uT  3단 우선순위로 followup 항목 추출.

    Returns:
        {
          "source": "followup_txt" | "report" | "completion" | "none",
          "core_result": str | None,
          "decisions_needed": list[str],
          "merges_needed": list[str],
          "unresolved": list[str],
          "next_steps": list[str],
        }
    noneN)sourcerB   rC   rD   rE   rF   rA   followup_txtreportc              3      K   | ]  }|  y w)N ).0vs     r,   	<genexpr>z#extract_followup.<locals>.<genexpr>a  s     -!-s   

rH   r   rB   
completionr   rC   rD   rE   rF   +r   )re   r   r9   listrQ   rS   rT   r   rk   r:   rm   rj   )
r'   outfurepmergedused_sourceskhas_anycomp
first_paras
             r,   extract_followupr   3  s    C 
W	%B
w
C F !L	 	(Avvay AKq		( 	N+
 	)A!9 QLq		) szz|)-V]]_--G(FA.q1779J%/LF=!-m9<VM=R9SWX9XVM215^b^g^ghno|h}^~M$%78C!/2C|,C|,C.:CHH\*CMJr`   c                    g }d}| D ]U  }d| d}|t        |      z   |kD  r|r|t        |       t        |      z
  fc S |j                  |       |t        |      z  }W |dfS )uY   문자 budget 안에서 bullet들을 자른다. 반환 (수용된 항목, 잘린 개수).r   - r   )rm   rQ   )rs   budgetaccepteduseditemr*   s         r,   _truncate_bulletsr   u  sy    HD D6}#d)f$SZ#h-777D	 Q;r`   c                   d|  dg}g }|r|j                  |       |r|j                  |       |r$|j                  ddj                  |       d       dj                  |      }d|  d}d	| }d
|j                  d      xs dfd|j                  d      xs g fd|j                  d      xs g fd|j                  d      xs g fd|j                  d      xs g fg}	g }
|	D ]{  \  }}|
j                  d       |
j                  |dz          t        |t              r|
j                  |       M|r|D ]  }|
j                  d|         k|
j                  d       } |dz   dj                  |
      j                  d      z   |z   }t        |      t        k  r|S t        t        |      z
  t        |      z
  t        z
  }|dk  r| | j                         S g }d
|j                  d      xs dfd|j                  d      xs g fd|j                  d      xs g fd|j                  d      xs g fd|j                  d      xs g fg}d}|D ]  \  }}d| d}t        |t              rC|t        |      z   t        |      z   |k  r|n|dt        d||z
  t        |      z
         }||z   }nV|rOt        |t        d||z
  t        |      z
              \  }}|dj                  d |D              z   }|r|d| d z  }n|dz   }|t        |      z   |kD  r|j                  |d!z           n!|j                  |       |t        |      z  } |dj                  |      z   |z   S )"u   표준 5섹션 메시지 생성.

    Telegram 4096자 제한 → 4000자 안전 마진. 초과 시 보고서 경로만 안내.
    u   ★ u    작업 완료 보고(z, )rz   zmemory/reports/r   u   

상세 보고서: u   핵심 결과rB   u   —u   ★ 회장 결정 필요rC   u   ★ 머지 필요rD   u   ★ 미해결rE   u   ★ 다음 단계 권장rF   r/   :r   u   - 없음r   r   r   z:
Nc              3  &   K   | ]	  }d |   yw)r   Nr   )r   xs     r,   r   z%format_anu_message.<locals>.<genexpr>  s     (@a2aS(@s   u   
- (외 u   건은 보고서 참조)u   (생략 — 보고서 참조))rQ   rj   r9   
isinstancestrrN   rm   TELEGRAM_LIMITMIN_HEADROOMr:   maxr   )r'   r1   r3   rs   title_partssuffix_piecesheader
report_relfooterrV   
body_lineslabelvalr   fullr   rebuiltpriority_orderr   
seg_headerrU   segkeptdroppeds                           r,   format_anu_messager     s    '"789K!M
S!T"Qtyy78:;XXk"F"7)3/J%j\2F 
%))M2;e<	#UYY/A%B%HbI	eii8>B?	%))L17R8	#UYY|%<%BC3H J 	*
s"%#+&c3c" ,!!Bqc(+, j)	* D=499Z077==FD
4yN" c&k)CK7,FFz&"((**G	%))M2;e<	#UYY/A%B%HbI	eii8>B?	%))L17R8	#UYY|%<%BCN D$ 
sE7#&
c3Z03s8;vE33OxQTUVX^aeXehklvhwXwQxKyDt#C-c3q&4-#j/:Y3Z[MD'tyy(@4(@@@C7)+CDDz)C#c(?V#NN:(GGHsC!$ BGGG$$v--r`   c                l    t        j                  t              t        |       z   j	                  d      S )u   현재 시각 + N초를 cokacdir --at 형식으로.

    cokacdir는 ISO T-구분자를 거부한다. 'YYYY-MM-DD HH:MM:SS' (공백 구분)이 표준.
    secondsz%Y-%m-%d %H:%M:%S)r   r   r   r   strftimer   s    r,   _at_time_secondsr     s)    
 LL	' ::DDEXYYr`   c           
     X   t         |  dz  }|j                         ry|xs' t        j                  j	                  d      xs t
        }|xs' t        j                  j	                  d      xs t        }t        j                  d      xs d}|d|dt        |      d	|d
|dg
}|rdddj                  |dd       z   dz   fS 	 t        j                  |ddd      }	|	j                   dk7  rft        | d|	j                    d|	j"                  j%                         dd         dd|	j                    d|	j"                  j%                         dd  fS 	 t         j'                  dd       t        j(                  t+        |      t        j,                  t        j.                  z  t        j0                  z        }	 t        j2                  |t5        j6                  t8              j;                         j=                                t        j>                  |       	 t        | d| d       y # t        j                  t        f$ r }
t        | d|
        dd|
 fcY d}
~
S d}
~
ww xY w# t        j>                  |       w xY w# t@        $ r Y rt        $ r}
t        | d|
        Y d}
~
d}
~
ww xY w)!u   cokacdir --cron로 아누 chat에 메시지 발송 + .anu-notified 마커 생성.

    중복 방지: .anu-notified 이미 존재 시 발송 스킵.
    z.anu-notified)Falready-notifiedCOKACDIR_CHAT_IDCOKACDIR_KEY_ANUcokacdirz/usr/local/bin/cokacdirz--cronz--atz--chatz--keyz--onceTz	dry-run: rz   N   z ...   )capture_outputrU   timeoutu   cokacdir 실행 예외: Fzexception: r   u   cokacdir 실패 rc=r      zrc=r   u   마커 생성 실패: u!   아누 cron 발송 완료 (delay=zs))Tsent)!rb   r5   osenvironr9   ANU_CHAT_ID_DEFAULTANU_KEY_DEFAULTshutilwhichr   rj   
subprocessrunTimeoutExpiredr&   r-   
returncodestderrr:   r#   r$   r   O_CREATO_EXCLO_WRONLYr%   r   r   r   r    encodecloseFileExistsError)r'   r(   chat_idanu_keydelay_secondsdry_runnotified_markercokacdir_bincmdresultefds               r,   send_anu_cronr     sl    !gYm#<<O(R(:;R?RGN(:;NG<<
+H/HL'C [388CG#44v===(DtRP
 AW+F,=,=+>bATATAVW[X[A\@]^_F--.b1D1D1Ft1L0MNNN
45WWS)2::		+ABKK+OP	HHRc*446==?@HHRL 	5m_BGH- %%w/ (W045A3'''( HHRL  4W.qc2334sV   H' A&I= 7A	I#  I= 'I  II I #I::I= =	J)J)J$$J)c                `    t        |       }t        |       \  }}t        | |||      }||||dfS )N)rs   r0   r3   )r   r?   r   )r'   rs   r0   r3   msgs        r,   build_message_for_taskr     s<    W%E%g.LGS
WgsE
:C%GC@@@r`   c                \    t        |       }t        t        j                  |dd             y)NFri   )ensure_asciiindentr   )r   printr6   dumps)r'   rs   s     r,   _cmd_extractr   "  s$    W%E	$**Uq
9:r`   c                6    t        |       \  }}t        |       y)Nr   )r   r   )r'   r   _s      r,   _cmd_formatr   (  s    #G,FC	#Jr`   c           	         t        |       \  }}t        | |||      \  }}| |||d   |d   |d   d   t        |      |d}t        t	        j
                  |d             |s|d	k(  rd
S dS )N)r   r   r0   r3   rs   r   )r'   okstatusr0   r3   r   message_lenr   F)r   r   r   rH   )r   r   rm   r   r6   r   )r'   r   delayr   metar  r  r   s           r,   	_cmd_sendr  .  s    &w/ICw5'RJB	?E{w-)3x	C 
$**Su
-.f 22199r`   c                    t        j                  d      } | j                  dd      }|j                  dd      }|j	                  d	       |j                  d
d      }|j	                  d	       |j                  dd      }|j	                  d	       |j	                  dd       |j	                  dt
        dd       | j                         }|j                  dk(  rt        |j                        S |j                  d
k(  rt        |j                        S |j                  dk(  r+t        |j                  |j                  |j                        S y)Nu;   봇 → 아누 후속 알림 메시지 추출/포맷/발송)descriptionr   T)destrequiredextractu    followup 항목 dict JSON 출력)helpr'   formatu   cron 메시지 텍스트 출력sendu2   cokacdir cron 발송 + .anu-notified 마커 생성z	--dry-run
store_true)actionz--delay
   u   발송 지연 초 (default: 10))typedefaultr  ri   )argparseArgumentParseradd_subparsers
add_parseradd_argumentint
parse_argsr   r   r'   r   r  r   r  )parserro   p_extp_fmtp_sendargss         r,   mainr   ?  s$   $$1noF


UT

:CNN9+MNNE	y!NN8*KNLE	y!^^F)]^^F
	"
L9
	R>_`Dxx9DLL))xx84<<((xx6t||TZZ@@r`   __main__)r'   r   r(   r   returnNone)r'   r   r"  ztuple[str, str])rU   r   r"  dict)r'   r   r"  zdict | None)rp   r   rq   	list[str]r"  r%  )rp   r   r"  
str | None)r'   r   r"  r&  )r'   r   r"  r$  )rs   r%  r   r  r"  ztuple[list[str], int])
r'   r   r1   r   r3   r   rs   r$  r"  r   )r   r  r"  r   )NNr  F)r'   r   r(   r   r   r&  r   r&  r   r  r   boolr"  ztuple[bool, str])r'   r   r"  ztuple[str, dict])r'   r   r"  r  )r'   r   r   r'  r  r  r"  r  )r"  r  )2__doc__
__future__r   r  r6   r   rI   r   r   sysr   r   r   pathlibr   r   r9   r   rb   r   	TASKS_DIRr4   r!   r   r   r   r   r   r-   r?   r_   re   rJ   rn   rx   r   r   r   r   r   r   r   r   r   r   r   r  r   __name__exitr   r`   r,   <module>r/     s  ( #   	 	   
 2 2  02GH.!H,x7
>"X-	9 8+g5	>"X-0BB&(+;;yq!"" $	  :z% RZZ56
@=@+\?D
O.O.
O. 
O. 	O.
 	O.dZ <<< < 	<
 < < <~A:"2 zCHHTV r`   