
    jS                       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_ROOT/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        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(   r1   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*결과rC   u   ^#+\s*회장\s*(결정|승인)rD   u   ^#+\s*머지\s*(필요|판단)?rE   u#   ^#+\s*(미해결|follow.?up|남은)rF   u,   ^#+\s*(다음\s*단계|next\s*steps?|후속)rG   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_DIRr6   r9   r'   r;   r`   r(   fprV   s      r-   _read_followup_filerf      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^(#+)rI   c      z\*\*(.+?)\*\*z\1z	`([^`]+)`)rJ   rK   joinrL   splitrQ   searchlenrS   
_BULLET_REr;   subrR   )contentheader_patternsr]   linesitems
in_sectionr+   level_match	cur_levelr_   rV   cleaneds               r-   _extract_section_bulletsry      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%-&. Lra   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*r0   rI   Nz---z^\s*\*\*[A-Z]\*\*rH   Fz^#{2,4}\s*Answer\bT z## )rl   rJ   rK   	enumeraterQ   rp   r;   rP   rR   rk   rL   )rq   rs   a_patir+   tail	collectednxtrV   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ra   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   rB   rC   u%   ^#+\s*회장\s*(결정|승인|결재)z^#+\s*decisions?\s*neededrD   u   ^#+\s*머지\s*(필요|판단)u   ^#+\s*merge\s*(needed|판단)rE   )u!   ^#+\s*(범위\s*내\s*)?미해결z^#+\s*follow.?upu   ^#+\s*발견\s*이슈rF   )u   ^#+\s*다음\s*단계u   ^#+\s*후속\s*taskz^#+\s*next\s*steps?rG   )REPORTS_DIRr6   r9   r'   r   ry   )r(   re   rq   rW   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   )rc   r6   r9   r;   r'   rd   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)sourcerC   rD   rE   rF   rG   rB   followup_txtreportc              3      K   | ]  }|  y w)N ).0vs     r-   	<genexpr>z#extract_followup.<locals>.<genexpr>a  s     -!-s   

rI   r   rC   
completionr   rD   rE   rF   rG   +r   )rf   r   r:   listrR   rT   rU   r   rl   r;   rn   rk   )
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Jra   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   )rn   rR   )rt   budgetaccepteduseditemr+   s         r-   _truncate_bulletsr   u  sy    HD D6}#d)f$SZ#h-777D	 Q;ra   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, )r{   zmemory/reports/r   u   

상세 보고서: u   핵심 결과rC   u   —u   ★ 회장 결정 필요rD   u   ★ 머지 필요rE   u   ★ 미해결rF   u   ★ 다음 단계 권장rG   r0   :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   (생략 — 보고서 참조))rR   rk   r:   
isinstancestrrO   rn   TELEGRAM_LIMITMIN_HEADROOMr;   maxr   )r(   r2   r4   rt   title_partssuffix_piecesheader
report_relfooterrW   
body_lineslabelvalr   fullr   rebuiltpriority_orderr   
seg_headerrV   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--ra   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Yra   c                   t         |  dz  }|j                         ry|xs' t        j                  j	                  d      xs t
        }|xs' t        j                  j	                  d      xs t        }	 ddlm}m	}m
}	m}
m} t        j                  j	                  dd      } |d	| ||t        |      d
|  d| dd|dd
      }|j                  |k7  r:|j                  |
|	fv r*t!        | d|j                          dd|j                   fS 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 !      }|j4                  dk7  rft!        | d$|j4                   d%|j6                  j9                         dd&         dd'|j4                   d%|j6                  j9                         dd&  fS 	 t         j;                  dd(       t        j<                  t        |      t        j>                  t        j@                  z  t        jB                  z        }	 t        jD                  |tG        jH                  tJ              jM                         jO                                t        jP                  |       	 t!        | d*| d+       y,# t"        $ r Y w xY w# t,        j0                  t2        f$ r }t!        | d"|        dd#| fcY d}~S d}~ww xY w# t        jP                  |       w xY w# tR        $ r Y t2        $ 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_ANUr   )GATE_ROUTE_FINISH_TASKLAUNCH_PASSSTATUS_GATE_BYPASS_FORBIDDENSTATUS_SELF_KEY_FAIL_CLOSEDenforce_callback_fireCOKACDIR_KEY_SELFzexecutor-self-unknownnormalztask_id=z
owner_key=z
collector_role=ANU10mr	   T)
kindr(   executor_key	owner_keychat_idpromptat
gate_routecanonical_rootrequire_envelopeu-   send_anu_cron fail-closed (self-key 차단): Fzself-key gate fail-closed: cokacdirz/usr/local/bin/cokacdirz--cronz--atz--chatz--keyz--oncez	dry-run: r{   N   z ...   )capture_outputrV   timeoutu   cokacdir 실행 예외: zexception: u   cokacdir 실패 rc=r      zrc=r   u   마커 생성 실패: u!   아누 cron 발송 완료 (delay=zs))Tsent)*rc   r6   osenvironr:   ANU_CHAT_ID_DEFAULTANU_KEY_DEFAULT(dispatch.normal_fallback_callback_helperr   r   r   r   r   r   verdictstatusr.   ImportErrorshutilwhichr   rk   
subprocessrunTimeoutExpiredr'   
returncodestderrr;   r$   r%   O_CREATO_EXCLO_WRONLYr&   r   r   r    r!   encodecloseFileExistsError)r(   r)   r   anu_keydelay_secondsdry_runnotified_markerr   r   r   r   r   _executor_self_gatecokacdir_bincmdresultefds                      r-   send_anu_cronr
    s8    !gYm#<<O(R(:;R?RGN(:;NG
	
 	
 (;=TU%'LgYl7);OP-0!
 ==K'ELL'(=
 -
 I%,,XY7~FFF <<
+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Y  , %%w/ (W045A3'''( HHRL  4W.qc2334si   5BK K *A&L' A	L L' 	KKL
*L?L
L
L$$L' '	M2M:MMc                `    t        |       }t        |       \  }}t        | |||      }||||dfS )N)rt   r1   r4   )r   r@   r   )r(   rt   r1   r4   msgs        r-   build_message_for_taskr  =  s<    W%E%g.LGS
WgsE
:C%GC@@@ra   c                \    t        |       }t        t        j                  |dd             y)NFrj   )ensure_asciiindentr   )r   printr7   dumps)r(   rt   s     r-   _cmd_extractr  D  s$    W%E	$**Uq
9:ra   c                6    t        |       \  }}t        |       y)Nr   )r  r  )r(   r  _s      r-   _cmd_formatr  J  s    #G,FC	#Jra   c           	         t        |       \  }}t        | |||      \  }}| |||d   |d   |d   d   t        |      |d}t        t	        j
                  |d             |s|d	k(  rd
S dS )N)r   r  r1   r4   rt   r   )r(   okr   r1   r4   r   message_lenr  F)r  r   r   rI   )r  r
  rn   r  r7   r  )r(   r  delayr  metar  r   r   s           r-   	_cmd_sendr  P  s    &w/ICw5'RJB	?E{w-)3x	C 
$**Su
-.f 22199ra   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"  rj   )argparseArgumentParseradd_subparsers
add_parseradd_argumentint
parse_argsr  r  r(   r  r  r  r  )parserrp   p_extp_fmtp_sendargss         r-   mainr6  a  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@@ra   __main__)r(   r   r)   r   returnNone)r(   r   r8  ztuple[str, str])rV   r   r8  dict)r(   r   r8  zdict | None)rq   r   rr   	list[str]r8  r;  )rq   r   r8  
str | None)r(   r   r8  r<  )r(   r   r8  r:  )rt   r;  r   r/  r8  ztuple[list[str], int])
r(   r   r2   r   r4   r   rt   r:  r8  r   )r   r/  r8  r   )NNr'  F)r(   r   r)   r   r   r<  r   r<  r   r/  r  boolr8  ztuple[bool, str])r(   r   r8  ztuple[str, dict])r(   r   r8  r/  )r(   r   r  r=  r  r/  r8  r/  )r8  r/  )2__doc__
__future__r   r*  r7   r   rJ   r   r   sysr   r   r   pathlibr   r   r:   r   rc   r   	TASKS_DIRr5   r"   r    r   r   r   r   r.   r@   r`   rf   rK   ro   ry   r   r   r   r   r   r   r   r
  r  r  r  r  r6  __name__exitr   ra   r-   <module>rE     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 ^^^ ^ 	^
 ^ ^ ^BA:"2 zCHHTV ra   