
    (<i'                       d 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	 ddl
Z
 e	e      j                  j                  Zej                  j                  d ee             edz  Zej$                  j'                  de      ZeJ ej$                  j+                  e      Zej.                  J ej.                  j1                  e       de	ded	e	fd
Zde	ded	e	fdZde	deded	e	fdZde	deded	e	fdZde	ded	e	fdZde	deded	e	fdZde	deded	e	fdZ  G d d      Z! G d d      Z" G d d      Z# G d d      Z$ G d d      Z% G d d       Z& G d! d"      Z' G d# d$      Z( G d% d&      Z) G d' d(      Z* G d) d*      Z+ G d+ d,      Z, G d- d.      Z- G d/ d0      Z. G d1 d2      Z/de	ded	e	fd3Z0dBd4e	ded5ed6e1d	e	f
d7Z2dCde	ded8ed	e	fd9Z3 G d: d;      Z4 G d< d=      Z5 G d> d?      Z6 G d@ dA      Z7y)Du  
test_whisper_compile.py

scripts/whisper-compile.py 단위 테스트 (TDD)

테스트 항목:
1. 빈 상태 (모든 파일 없음) → 빈 브리핑 정상 출력
2. 정상 상태 → 모든 섹션 포함
3. 부분 파일만 존재 → 해당 섹션만 포함
4. 유휴 팀 3시간 이상 → [유휴경고] 출력
5. 질문 있음/없음
6. 보고서 SCQA 추출 정확성
7. bot-activity.json 파일 손상 시 graceful 처리
    N)datetime	timedeltatimezone)Pathzwhisper-compile.pywhisper_compiletmp_pathdatareturnc                     | dz  }|j                  dd       |dz  }|j                  t        j                  |      d       | S )NeventsTparentsexist_okbot-activity.jsonutf-8encodingmkdir
write_textjsondumps)r   r	   
events_dirfs       S/home/jay/workspace/.worktrees/task-2057-dev2/scripts/tests/test_whisper_compile.pymake_bot_activityr   *   sH    H$JTD1((ALLD!GL4O    tasksc                 `    | dz  }|j                  t        j                  d|i      d       | S )Ntask-timers.jsonr   r   r   r   r   r   )r   r   r   s      r   make_task_timersr"   2   s0    %%ALLWe,-L@Or   filenamec                     | dz  }|j                  dd       ||z  }|j                  t        j                  |      d       | S )Nr   Tr   r   r   r   )r   r#   r	   r   r   s        r   make_done_filer%   8   sG    H$JTD1XALLD!GL4Or   contentc                 f    | dz  }|j                  dd       ||z  }|j                  |d       | S )NreportsTr   r   r   r   r   )r   r#   r&   reports_dirr   s        r   make_reportr+   @   s>    Y&KdT2hALL7L+Or   c                     | dz  }|j                  dd       |dz  }|j                  t        j                  |      d       | S )NwhisperTr   zsession-guidance.jsonr   r   r   )r   r	   whisper_dirr   s       r   make_guidancer/   H   sH    Y&KdT2--ALLD!GL4Or   c                     | dz  dz  }|j                  dd       ||z  }|j                  t        j                  |      d       | S )Nr   	questionsTr   r   r   r   )r   r#   r	   q_dirr   s        r   make_questionr3   P   sI    x+-E	KKtK,ALLD!GL4Or   projectc                 l    | dz  |z  }|j                  dd       |dz  }|j                  |d       | S )NprojectsTr   z
context.mdr   r   r)   )r   r4   r&   proj_dirr   s        r   make_project_contextr8   X   s@    *$w.HNN4$N/<ALL7L+Or   c                   p    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	defdZ
defd	Zdefd
Zy)TestEmptyStateu+   모든 파일이 없을 때 graceful 처리r   c                 >    t         j                  |      }|i k(  sJ y Nbase_dir)r   load_bot_activityselfr   results      r   test_load_bot_activity_missingz-TestEmptyState.test_load_bot_activity_missingh   s!     22H2E||r   c                 >    t         j                  |      }|i k(  sJ y r<   r   load_task_timersr@   s      r   test_load_task_timers_missingz,TestEmptyState.test_load_task_timers_missingl   !     1181D||r   c                 >    t         j                  |      }|g k(  sJ y r<   )r   scan_done_filesr@   s      r   test_scan_done_files_missingz+TestEmptyState.test_scan_done_files_missingp   s!     00(0C||r   c                 >    t         j                  |      }|i k(  sJ y r<   r   load_guidancer@   s      r   test_load_guidance_missingz)TestEmptyState.test_load_guidance_missingt   !     ...A||r   c                 >    t         j                  |      }|g k(  sJ y r<   r   load_questionsr@   s      r   test_load_questions_missingz*TestEmptyState.test_load_questions_missingx   !     ///B||r   c                 >    t         j                  |      }|i k(  sJ y r<   )r   load_project_contextr@   s      r   !test_load_project_context_missingz0TestEmptyState.test_load_project_context_missing|   s!     55x5H||r   c                     t         j                  |      d   }|j                  d      sJ |j                         j	                  d      sJ d|v sJ d|v sJ y )Nr=   r   <whisper-briefing></whisper-briefing>   [팀]   [가이던스])r   compile_briefing
startswithstripendswithrA   r   outputs      r   test_compile_briefing_emptyz*TestEmptyState.test_compile_briefing_empty   sf     1181DQG  !5666||~&&'<===&   6)))r   c                     	 t         j                  |      d    y# t        $ r"}t        j                  d|        Y d}~yd}~ww xY w)u/   어떤 파일도 없어도 예외 없이 실행r=   r   u    빈 상태에서 예외 발생: N)r   r^   	Exceptionpytestfail)rA   r   es      r   "test_compile_briefing_no_exceptionz1TestEmptyState.test_compile_briefing_no_exception   sG    	@,,h,?B 	@KK:1#>??	@s    	AAAN)__name__
__module____qualname____doc__r   rC   rG   rK   rO   rT   rX   rd   rj    r   r   r:   r:   e   sg    5t d T 4 D $ *D *@4 @r   r:   c                   L    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	y)	TestBotActivityu.   bot-activity.json 로드 및 팀 상태 파싱r   c                     t        j                  t        j                        j	                         }dd|dd|ddi}t        ||       t        j                  |      }d|v sJ |d   d   dk(  sJ y )	Nbots
processingstatussinceidledev1dev2r=   rz   rv   )r   nowr   utc	isoformatr   r   r?   )rA   r   r|   r	   rB   s        r   test_load_bot_activity_normalz-TestBotActivity.test_load_bot_activity_normal   s    ll8<<(224#/#>#)C8
 	(D) 22H2Ef~h'<777r   c                     t         j                  }|j                  d      dk(  sJ |j                  d      dk(  sJ |j                  d      dk(  sJ |j                  d      dk(  sJ y	)
uA   dev1→1팀, dev2→2팀, dev3→3팀, anu→아누 매핑 확인rz      1팀r{      2팀dev3u   3팀anu   아누Nr   TEAM_NAME_MAPgetrA   r   mappings      r   test_team_name_mappingz&TestBotActivity.test_team_name_mapping   si    !//{{6"f,,,{{6"f,,,{{6"f,,,{{5!X---r   c                     |dz  }|j                  dd       |dz  }|j                  dd       t        j                  |      }|i k(  sJ y	)
u4   손상된 JSON → graceful 처리 (빈 dict 반환)r   Tr   r   { not valid json !!!r   r   r=   N)r   r   r   r?   rA   r   r   r   rB   s        r    test_load_bot_activity_corruptedz0TestBotActivity.test_load_bot_activity_corrupted   sZ    (
5,,	+g> 22H2E||r   c                 V    t        |i        t        j                  |      }|i k(  sJ y)u   빈 JSON 객체 → 빈 dictr=   Nr   r   r?   r@   s      r   !test_load_bot_activity_empty_jsonz1TestBotActivity.test_load_bot_activity_empty_json   s+    (B' 22H2E||r   c                 Z    t        |ddi       t        j                  |      }|i k(  sJ y)u!   bots 키 없는 JSON → 빈 dictotherr	   r=   Nr   r@   s      r   "test_load_bot_activity_no_bots_keyz2TestBotActivity.test_load_bot_activity_no_bots_key   s0    (Wf$56 22H2E||r   N)
rk   rl   rm   rn   r   r   r   r   r   r   ro   r   r   rq   rq      sA    88d 8.t . $ 4 r   rq   c                   @    e Zd ZdZdefdZdefdZdefdZdefdZy)TestTaskTimersu7   task-timers.json 로드 및 running/completed 필터링r   c                 \   t        j                  t        j                        j	                         }t        |dddd|d ddddd	||dd
       t        j                  |      }|j                         D cg c]  }|d   dk(  s| }}t        |      dk(  sJ |d   d   dk(  sJ y c c}w )Ntask-1.1	dev1-teamu   테스트 작업runningtask_idteam_iddescriptionrv   
start_timeend_timetask-2.1	dev2-team   완료된 작업	completed)r   r   r=   rv      r   r   )
r   r|   r   r}   r~   r"   r   rF   valueslen)rA   r   r|   rB   tr   s         r   test_load_running_tasksz&TestTaskTimers.test_load_running_tasks   s    ll8<<(224  **#5'"% $  **#5)"% #	
* !1181D$mmoJ8	1I1JJ7|q   qz)$
222 Ks   7B)B)c                     t        j                  t        j                        t	        d      z
  j                         }t        |ddddd||di       t        j                  |      }d|v sJ y	)
u'   24시간 이내 완료 작업은 포함r   hoursztask-3.1r   u   최근 완료r   r   r=   N	r   r|   r   r}   r   r~   r"   r   rF   )rA   r   one_hour_agorB   s       r   %test_load_recent_completed_within_24hz4TestTaskTimers.test_load_recent_completed_within_24h   st     X\\2YQ5GGRRT)*#2)". ,		
 !1181DV###r   c                     t        j                  t        j                        t	        d      z
  j                         }t        |ddddd||di       t        j                  |      }d|vsJ y	)
u'   25시간 이전 완료 작업은 제외   r   ztask-4.1r   u   오래된 완료r   r   r=   Nr   )rA   r   old_timerB   s       r   test_exclude_old_completedz)TestTaskTimers.test_exclude_old_completed   st    LL.1DDOOQ)*#5)"* (		
 !1181D'''r   c                 >    t         j                  |      }|i k(  sJ y r<   rE   r@   s      r   rG   z,TestTaskTimers.test_load_task_timers_missing  rH   r   N)	rk   rl   rm   rn   r   r   r   r   rG   ro   r   r   r   r      s5    A3 38$d $&(4 (&d r   r   c                   p    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	defdZ
defd	Zdefd
Zy)TestDoneFilesu]   events/*.done 파일 스캔 (.done.acked, .done.escalated 등 확장자 파일 자동 제외)r   c                     t        |dddd       t        j                  |      }t        |      dk(  sJ |d   d   dk(  sJ y )	Nztask-10.1.donez	task-10.1doner   rv   r=   r   r   r   r%   r   rJ   r   r@   s      r   test_scan_done_files_basicz(TestDoneFiles.test_scan_done_files_basic  sV    #v6	

 !00(0C6{aay#{222r   c                 p    t        |dddd       t        j                  |      }t        |      dk(  sJ y)uA   *.done.acked 파일은 제외해야 함 (새 프로토콜 반영)ztask-11.1.done.ackedz	task-11.1r   r   r=   r   Nr   r@   s      r   1test_scan_done_files_excludes_non_done_extensionsz?TestDoneFiles.test_scan_done_files_excludes_non_done_extensions$  s?    "#v6	

 !00(0C6{ar   c                     t        |dddi       t        |dddi       t        j                  |      }|D cg c]  }|j                  d       }}d|v sJ t	        |      dk(  sJ yc c}w )u7   *.done.acked 파일은 스캔에서 제외되어야 함ztask-15.1.doner   z	task-15.1ztask-15.1.done.ackedr=   r   N)r%   r   rJ   r   r   rA   r   rB   rtask_idss        r   (test_scan_done_files_excludes_done_ackedz6TestDoneFiles.test_scan_done_files_excludes_done_acked.  sz    x!1I{3KLx!7)[9QR 00(0C.45AEE)$55h&&&6{a 6s   A*c                 p    t        |dddd       t        j                  |      }t        |      dk(  sJ y)u;   *.done.escalated 파일은 스캔에서 제외되어야 함ztask-16.1.done.escalatedz	task-16.1r   r   r=   r   Nr   r@   s      r   ,test_scan_done_files_excludes_done_escalatedz:TestDoneFiles.test_scan_done_files_excludes_done_escalated7  s?    &#v6	

 !00(0C6{ar   c                 p    t        |dddd       t        j                  |      }t        |      dk(  sJ y)uM   *.done.notified 파일은 스캔에서 제외되어야 함 (레거시 호환)ztask-17.1.done.notifiedz	task-17.1r   r   r=   r   Nr   r@   s      r   +test_scan_done_files_excludes_done_notifiedz9TestDoneFiles.test_scan_done_files_excludes_done_notifiedA  s?    %#v6	

 !00(0C6{ar   c                     t        |dddi       t        |dddi       t        |dddi       t        j                  |      }|D cg c]  }|j                  d       }}d|v sJ d|v sJ d|vsJ y c c}w )	Nztask-12.1.doner   z	task-12.1ztask-13.1.donez	task-13.1ztask-14.1.done.ackedz	task-14.1r=   )r%   r   rJ   r   r   s        r   test_scan_done_files_multiplez+TestDoneFiles.test_scan_done_files_multipleK  s    x!1I{3KLx!1I{3KLx!7)[9QR 00(0C.45AEE)$55h&&&h&&&(*** 6s   A5c                     |dz  }|j                  dd       |dz  }|j                  dd       t        j                  |      }|g k(  sJ y	)
u    손상된 .done 파일은 스킵r   Tr   ztask-bad.1.doneznot jsonr   r   r=   N)r   r   r   rJ   r   s        r   #test_scan_done_files_corrupted_jsonz1TestDoneFiles.test_scan_done_files_corrupted_jsonU  sY    (
5**	Z'2 00(0C||r   c                     t        |dddi       t        |dddi       t        |dddi       t        j                  |      \  }}|d	   d
k(  sJ y)ue   compile_briefing의 status_dict에서 done_pending이 .done.acked를 제외하고 카운트해야 함ztask-20.1.doner   z	task-20.1ztask-21.1.donez	task-21.1ztask-22.1.done.ackedz	task-22.1r=   done_pending   Nr%   r   r^   rA   r   _status_dicts       r   &test_done_pending_count_excludes_ackedz4TestDoneFiles.test_done_pending_count_excludes_acked^  sb    x!1I{3KLx!1I{3KLx!7)[9QR(9989L;>*a///r   N)rk   rl   rm   rn   r   r   r   r   r   r   r   r   r   ro   r   r   r   r     se    g34 3 $      T   D  +d +D 0t 0r   r   c                   P    e Zd ZdZdZdefdZdefdZdefdZdefdZ	defdZ
y	)
TestReportSCQAu7   extract_report_scqa() - 보고서 SCQA 추출 정확성u   # task-564.1 완료 보고서

**S**: done-watcher 개선 사항이 있었다.
**C**: cokacdir가 추가되어 상황이 바뀌었다.
**Q**: 개선 효과를 측정할 수 있는가?
**A**: 테스트 PASS로 확인됨.
r   c                     t        |d| j                         t        j                  d|      }|J d|d   v sJ d|d   v sJ d|d	   v sJ d
|d   v sJ y )Nztask-564.1.mdz
task-564.1r=   zdone-watcherScokacdirCu   개선 효과Q	   테스트A)r+   SAMPLE_REPORTr   extract_report_scqar@   s      r   test_extract_scqa_basicz&TestReportSCQA.test_extract_scqa_basicx  s}    Hot/A/AB 44\H4U!!!,,,VC[(((&+---fSk)))r   c                 :    t         j                  d|      }|J y)u   보고서 없으면 None 반환ztask-nonexistent.1r=   N)r   r   r@   s      r    test_extract_scqa_missing_reportz/TestReportSCQA.test_extract_scqa_missing_report  s#     445IT\4]~~r   c                     d}t        |d|       t        j                  d|      }|J |j                  d      dk(  sJ |j                  d      d	k(  sJ y)
u(   S, C만 있는 경우도 graceful 처리u   **S**: 상황
**C**: 변화
ztask-partial.1.mdztask-partial.1r=   Nr   u   상황r   u   변화)r+   r   r   r   )rA   r   partialrB   s       r   test_extract_scqa_partialz(TestReportSCQA.test_extract_scqa_partial  sc    2H17; 445EPX4Y!!!zz#(***zz#(***r   c                     t        |d| j                         t        |dddi       t        j	                  |      d   }d|v sJ d|v sJ y)	u>   [완료] 섹션에 SCQA 포맷이 올바르게 표시되는지task-100.1.mdtask-100.1.doner   
task-100.1r=   r   zS:N)r+   r   r%   r   r^   rb   s      r   $test_extract_scqa_format_in_briefingz3TestReportSCQA.test_extract_scqa_format_in_briefing  sX    Hot/A/ABx!2Y4MN 1181DQGv%%%v~~r   c                 N    t         j                  |      d   }d|v sJ d|v sJ y)u1   done 파일 없으면 [완료] 섹션은 '없음'r=   r      [완료]   없음Nr   r^   rb   s      r   test_extract_scqa_no_done_filesz.TestReportSCQA.test_extract_scqa_no_done_files  s6     1181DQGV###6!!!r   N)rk   rl   rm   rn   r   r   r   r   r   r   r   ro   r   r   r   r   l  sH    AM* * 
+$ +T " "r   r   c                   @    e Zd ZdZdefdZdefdZdefdZdefdZy)TestGuidanceu   session-guidance.json 로드r   c                 f    t        |ddg g d       t        j                  |      }|d   dk(  sJ y )Nz2026-03-15T00:00:00Zu   이전 세션 키워드)last_sessionguidancepending_dispatches
idle_teamsr=   r   )r/   r   rN   r@   s      r   test_load_guidance_normalz&TestGuidance.test_load_guidance_normal  sG     65&( 		
 !...Aj!%>>>>r   c                 >    t         j                  |      }|i k(  sJ y r<   rM   r@   s      r   rO   z'TestGuidance.test_load_guidance_missing  rP   r   c                 j    t        |ddi       t        j                  |      d   }d|v sJ d|v sJ y )Nr   u   테스트 가이던스r=   r   r]   r/   r   r^   rb   s      r   test_guidance_in_briefingz&TestGuidance.test_guidance_in_briefing  sF    h-E FG 1181DQG6)))'6111r   c                 B    t         j                  |      d   }d|v sJ y)u0   guidance 없으면 '이전 세션 정보 없음'r=   r   u   이전 세션 정보 없음Nr   rb   s      r   "test_guidance_fallback_in_briefingz/TestGuidance.test_guidance_fallback_in_briefing  s(     1181DQG,666r   N)	rk   rl   rm   rn   r   r   rO   r   r   ro   r   r   r   r     s5    &?$ ?4 2$ 274 7r   r   c                   L    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	y)	TestQuestionsu1   events/questions/*.json 로드 (.answered 제외)r   c                     t        |ddddd       t        j                  |      }t        |      dk(  sJ |d   d	   dk(  sJ y )
Nzq-task-565.jsonhermesu   설계 확인 필요ztask-565)fromquestionr   r=   r   r   r  )r3   r   rS   r   r@   s      r   test_load_questions_basicz'TestQuestions.test_load_questions_basic  s\     2%	
 !///B6{aay H,,,r   c                     |dz  dz  }|j                  dd       |dz  }|j                  t        j                  ddi      d	       t        j                  |
      }t        |      dk(  sJ y)u   .answered 파일은 제외r   r1   Tr   zq-answered.json.answeredr  u   이미 답변됨r   r   r=   r   N)r   r   r   r   r   rS   r   )rA   r   r2   r   rB   s        r   %test_load_questions_excludes_answeredz3TestQuestions.test_load_questions_excludes_answered  sp    8#k1D40..	TZZ-? @AGT ///B6{ar   c                 n    t        |dddd       t        j                  |      d   }d|v sJ d|v sJ y )Nzq-test.jsonrz   u   테스트 질문r  r  r=   r      [질문]r3   r   r^   rb   s      r   "test_questions_in_briefing_presentz0TestQuestions.test_questions_in_briefing_present  sO    );<	

 !1181DQGV###!V+++r   c                 N    t         j                  |      d   }d|v sJ d|v sJ y )Nr=   r   r  r   r   rb   s      r   test_questions_in_briefing_nonez-TestQuestions.test_questions_in_briefing_none  s6     1181DQGV###6!!!r   c                 >    t         j                  |      }|g k(  sJ y r<   rR   r@   s      r   test_load_questions_no_dirz(TestQuestions.test_load_questions_no_dir  rU   r   N)
rk   rl   rm   rn   r   r  r  r
  r  r  ro   r   r   r   r     sA    ;-$ - d  ,4 ," "
4 r   r   c                   X    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	defdZ
y	)
TestIdleTeamsu6   detect_idle_teams() - 3시간 이상 유휴 팀 탐지r   c                     t        j                  t        j                        t	        d      z
  j                  d      }dd|di}t        j                  |      }t        |      dk(  sJ |d   d	   dk(  sJ y )
N   r   %Y-%m-%dT%H:%M:%SZr{   rx   ru   r   r   r   	r   r|   r   r}   r   strftimer   detect_idle_teamsr   rA   r   four_hours_agors   rB   s        r   test_detect_idle_over_3hz&TestIdleTeams.test_detect_idle_over_3h  sr    ",,x||4yq7IISSThi6NCD 22486{aay#v---r   c                     t        j                  t        j                        t	        d      z
  j                  d      }dd|di}t        j                  |      }t        |      dk(  sJ y )Nr   r   r  rz   rx   ru   r   r  )rA   r   two_hours_agors   rB   s        r   test_no_idle_under_3hz#TestIdleTeams.test_no_idle_under_3h  s[    !hll3ia6HHRRSgh6MBC 22486{ar   c                     t        j                  t        j                        t	        d      z
  j                  d      }dd|di}t        j                  |      }t        |      dk(  sJ y)	u.   processing 상태 팀은 유휴 경고 안 함r  r   r  r   rt   ru   r   Nr  r  s        r   test_processing_team_not_idlez+TestIdleTeams.test_processing_team_not_idle  s[    ",,x||4yq7IISSThi<.IJ 22486{ar   c                     t        j                  t        j                        t	        d      z
  j                  d      }ddd|dii}t        ||       t        j                  |      d	   }d
|v sJ d|v sJ y )Nr  r   r  rs   r{   rx   ru   r=   r      [유휴경고]r   	r   r|   r   r}   r   r  r   r   r^   )rA   r   r  r	   rc   s        r   test_idle_warning_in_briefingz+TestIdleTeams.test_idle_warning_in_briefing  s|    ",,x||4yq7IISSThiF^!LMN(D) 1181DQG6)))r   c                     t        j                  t        j                        t	        d      z
  j                  d      }ddd|dii}t        ||       t        j                  |      d	   }d
|vsJ y)u?   유휴 팀 없으면 [유휴경고] 줄 자체가 없어야 함r   r   r  rs   rz   rx   ru   r=   r   r   Nr!  )rA   r   r  r	   rc   s        r    test_no_idle_warning_when_activez.TestIdleTeams.test_no_idle_warning_when_active  sn    !hll3ia6HHRRSghF]!KLM(D) 1181DQGv---r   c                     t        j                  t        j                        t	        dd      z
  j                  d      }dd|di}t        j                  |      }t        |      dk(  sJ y)	u2   정확히 3시간(180분) 유휴 → 경고 포함   r   )r   minutesr  rz   rx   ru   Nr  )rA   r   three_hours_agors   rB   s        r   test_exactly_3h_idle_is_warnedz,TestIdleTeams.test_exactly_3h_idle_is_warned"  s^    #<<5	ST8UU__`tu6ODE 22486{ar   N)rk   rl   rm   rn   r   r  r  r  r"  r$  r)  ro   r   r   r  r    sM    @. . d   d   d  . . t  r   r  c                   @    e Zd ZdZdefdZdefdZdefdZdefdZy)TestProjectContextu   프로젝트 context.md 로드r   c                 X    t        |dd       t        j                  d|      }d|v sJ y )Ninsuwikiu   # InsuWiki
- Phase: 미시작
z/home/jay/projects/insuwiki/srccwdr>   r8   r   rW   r@   s      r    test_load_project_context_by_cwdz3TestProjectContext.test_load_project_context_by_cwd2  s4    Xz3UV 55:[fn5oV###r   c                 X    t        |dd       t        j                  d|      }d|v sJ y )N
threadautou   # ThreadAuto
- Remotion 대기
z/home/jay/projects/threadauto/r.  r0  r@   s      r   $test_load_project_context_threadautoz7TestProjectContext.test_load_project_context_threadauto7  s4    X|5XY 55:Zem5nv%%%r   c                 |    t        |dd       t        |dd       t        j                  |      }d|v sJ d|v sJ y )Nr-  u   # InsuWiki 요약
r3  u   # ThreadAuto 요약
r=   r0  r@   s      r   "test_load_all_projects_when_no_cwdz5TestProjectContext.test_load_all_projects_when_no_cwd<  sK    Xz3HIX|5LM 55x5HV###v%%%r   c                 \    t        |dd       t        j                  |      d   }d|v sJ y )Nr-  u   # InsuWiki
- 상태: PENDING
r=   r      [프로젝트])r8   r   r^   rb   s      r    test_project_context_in_briefingz3TestProjectContext.test_project_context_in_briefingC  s5    Xz3TU 1181DQG6)))r   N)	rk   rl   rm   rn   r   r1  r4  r6  r9  ro   r   r   r+  r+  /  s5    ($ $
&T &
&4 &* *r   r+  c                   L    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	y)	TestFullBriefingu!   전체 브리핑 통합 테스트r   c                     t         j                  |      d   }|j                         j                  d      sJ |j                         j	                  d      sJ y )Nr=   r   rZ   r[   )r   r^   r`   r_   ra   rb   s      r   test_xml_structurez#TestFullBriefing.test_xml_structureQ  sP     1181DQG||~(()=>>>||~&&'<===r   c                 2   t        j                  t        j                        j	                  d      }t        j                  t        j                        t        d      z
  j	                  d      }t        |ddd|dii       t        |ddd	d
d|ddi       t        |dddi       t        |dd       t        |ddi       t        |dddd       t        |dd       t        j                  |      d   }d|v sJ d|v sJ d|v sJ d |v sJ d!|v sJ y)"u)   정상 데이터 → 모든 섹션 포함r  r   r   rs   rz   rt   ru   z
task-200.1r   u   통합 테스트 작업r   Nr   r   r   r   r   u8   **S**: 상황
**C**: 변화
**Q**: 질문
**A**: 답변
r   u    테스트 가이던스 키워드z
q-200.jsonr{   u   확인 요청r  r-  z# InsuWiki
r=   r   r\   r   r8  r]   r  )r   r|   r   r}   r  r   r   r"   r%   r+   r/   r3   r8   r   r^   )rA   r   now_isor   rc   s        r   test_all_sections_presentz*TestFullBriefing.test_all_sections_presentV  sE   ,,x||,556JK X\\2YQ5GGQQRfg 	fHIJ	

 	+*#<'") $		
 	x!2Y4MNJ	
 	h-O PQhv?.[\Xz>B 1181DQG&   V###6)))6)))V###r   c                 v    t        |ddi       t        j                  |      d   }d|v sJ d|v sJ d|v sJ y)uB   guidance만 있을 때 나머지 섹션은 빈/없음으로 처리r   u   부분 테스트r=   r   r]   r\   Nr   rb   s      r   test_partial_files_onlyz(TestFullBriefing.test_partial_files_only  sT    h-? @A 1181DQG6)))!V+++&   r   c                     	 t         j                  |      d   }d|v sJ y# t        $ r"}t        j                  d|        Y d}~yd}~wt
        $ r"}t        j                  d|        Y d}~yd}~ww xY w)u0   파일 전혀 없어도 예외 없이 XML 반환r=   r   rZ   u   SystemExit 발생: Nu   예외 발생: )r   r^   
SystemExitrg   rh   rf   )rA   r   rc   ri   s       r    test_no_exception_on_all_missingz1TestFullBriefing.test_no_exception_on_all_missing  ss    	/$55x5HKF'6111 	3KK-aS122 	/KK/!-..	/s    " 	A6AA6A11A6c                 b   t        j                  t        j                        j	                  d      }t        |dd|dd|ddi       t        j                  |      d   }|j                         D cg c]  }|j                  d      s| }}t        |      d	k(  sJ d
|d   v sJ yc c}w )u!   [팀] 줄이 '|' 구분자 포맷r  rs   rx   ru   ry   r=   r   r\   r   |N)r   r|   r   r}   r  r   r   r^   
splitlinesr_   r   )rA   r   r?  rc   l	team_lines         r   test_team_line_formatz&TestFullBriefing.test_team_line_format  s    ,,x||,556JK'-@'-@	
 !1181DQG & 1 1 3M1q||G7LQM	M9~"""il""" Ns   5B,B,N)
rk   rl   rm   rn   r   r=  r@  rB  rE  rK  ro   r   r   r;  r;  N  sB    +>4 >
,$$ ,$\! !/ /#d #r   r;  c                   X    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	defdZ
y	)
TestAnuExclusionuX   아누는 대화 주체이므로 [팀] 섹션 및 유휴경고에서 제외되어야 함r   c                 &   t        j                  t        j                        j	                  d      }t        |dd|dd|ddi       t        j                  |      d   }t        d |j                         D        d	      }d
|vs
J d|        y)uN   아누가 bot-activity에 있어도 [팀] 섹션에 표시되지 않아야 함r  rs   rt   ru   rz   r   r=   r   c              3   D   K   | ]  }|j                  d       s|  ywr\   Nr_   .0rI  s     r   	<genexpr>z@TestAnuExclusion.test_anu_not_in_team_section.<locals>.<genexpr>       RALL<Q!R      r   %   아누가 [팀] 섹션에 포함됨: N
r   r|   r   r}   r  r   r   r^   nextrH  rA   r   r?  rc   rJ  s        r   test_anu_not_in_team_sectionz-TestAnuExclusion.test_anu_not_in_team_section  s    ,,x||,556JK'3gF&2WE	
 !1181DQGRV%6%6%8RTVW	y(],QR[Q\*]](r   c                     t        j                  t        j                        j	                  d      }t        |ddd|dii       t        j                  |      d   }d|v s
J d	|        y
)u4   아누만 있는 bot-activity → [팀] 정보없음r  rs   r   rt   ru   r=   r   u   [팀] 정보없음u*   아누만 있을 때 정보없음 기대: Nr   r|   r   r}   r  r   r   r^   )rA   r   r?  rc   s       r    test_anu_only_team_shows_no_infoz1TestAnuExclusion.test_anu_only_team_shows_no_info  st    ,,x||,556JKewGHI	
 !1181DQG#v-d1[\b[c/dd-r   c                 B   t        j                  t        j                        j	                  d      }t        |dd|dd|ddi       t        j                  |      d   }t        d	 |j                         D        d
      }d|v s
J d|        d|vs
J d|        y)u.   dev1 + anu → anu 제외되고 dev1만 표시r  rs   rx   ru   rt   rO  r=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  zETestAnuExclusion.test_dev1_and_anu_shows_only_dev1.<locals>.<genexpr>  rV  rW  rX  r   u&   dev1(1팀)이 [팀] 섹션에 없음: r   rY  NrZ  r\  s        r   !test_dev1_and_anu_shows_only_dev1z2TestAnuExclusion.test_dev1_and_anu_shows_only_dev1  s    ,,x||,556JK'-@&2WE	
 !1181DQGRV%6%6%8RTVW	"X&LYK$XX"y(],QR[Q\*]](r   c                     t        j                  t        j                        t	        d      z
  j                  d      }dd|di}t        j                  |      }t        |      dk(  s
J d|        y	)
uQ   아누가 장기 유휴 상태여도 [유휴경고]에 포함되지 않아야 함r  r   r  r   rx   ru   r   u&   아누는 유휴경고 대상 아님: Nr  r  s        r   !test_anu_idle_not_in_idle_warningz2TestAnuExclusion.test_anu_idle_not_in_idle_warning  sh    ",,x||4yq7IISSThi&>BC 22486{aR#I&!RRr   c                     t        j                  t        j                        t	        d      z
  j                  d      }t        |ddd|dii       t        j                  |      d	   }d
|vs
J d|        y)uN   아누가 장기 유휴여도 [유휴경고] 줄이 출력되지 않아야 함r  r   r  rs   r   rx   ru   r=   r   r   u,   아누 유휴 시 [유휴경고] 출력됨: Nr!  )rA   r   r  rc   s       r   *test_anu_idle_not_in_briefing_idle_warningz;TestAnuExclusion.test_anu_idle_not_in_briefing_idle_warning  s~    ",,x||4yq7IISSThieHIJ	
 !1181DQGv-f1]^d]e/ff-r   c                 Z    t         j                  }|j                  d      dk(  sJ d       y)u\   TEAM_NAME_MAP에서 anu→아누 매핑은 여전히 존재해야 함 (다른 용도 가능)r   r   u6   TEAM_NAME_MAP에서 anu 매핑이 제거되면 안 됨Nr   r   s      r   'test_team_name_mapping_anu_still_existsz8TestAnuExclusion.test_team_name_mapping_anu_still_exists  s*    !//{{5!X-g/gg-r   N)rk   rl   rm   rn   r   r]  r`  rc  re  rg  ri  ro   r   r   rM  rM    sY    b^T ^ e e^$ ^"S$ Sg4 gh hr   rM  c                       e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	defdZ
defd	Zdefd
ZdefdZdefdZdefdZy)TestSaveStatusu@   save_status() - status.json 기록 및 status_dict 구조 검증r   c           
          dddddddddd	}t         j                  ||       |d	z  d
z  }|j                         sJ d       y)u2   save_status() 호출 시 status.json 파일 생성2026-03-15T04:00:00+00:00oku   1팀:작업중r   r   N	last_runrv   briefing_summaryteams_active
teams_idler   questions_pendingguidance_last_savederrorr=   r-   status.jsonu)   status.json 파일이 생성되지 않음)r   save_statusexists)rA   r   r   status_paths       r   test_save_status_creates_filez,TestSaveStatus.test_save_status_creates_file  sb     4 0!"#'

 	##K(#C*]:!!#P%PP#r   c           
      *   dddddddddd	}t         j                  ||	       |d
z  dz  }t        j                  |j	                  d            }|d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   J y)u0   저장된 status.json 내용이 입력과 일치rm  rn  u   1팀:작업중 | 2팀:유휴r   r   z2026-03-15T03:50:00+00:00Nro  r=   r-   rw  r   r   rv   rr  rs  r   rt  ru  rv  r   rx  r   loads	read_textrA   r   r   rz  saveds        r    test_save_status_content_correctz/TestSaveStatus.test_save_status_content_correct  s     4 >!"#>

 	##K(#C*]:

;00'0BCX$&&&^$)))\"a'''^$)))()Q...*+/JJJJW~%%%r   c           
          dddddddddd	}t         j                  ||       |d	z  d
z  }t        j                  |j	                  d            }|d   dk(  sJ d|d   v sJ y)u   에러 상태 기록 검증rm  rv  rX  r   Nz.FileNotFoundError: bot-activity.json not foundro  r=   r-   rw  r   r   rv   FileNotFoundErrorr}  r  s        r   test_save_status_error_statez+TestSaveStatus.test_save_status_error_state#  s     4 "!"#'E

 	##K(#C*]:

;00'0BCX')))"eGn444r   c                     t         j                  |      }t        |t              sJ d       t	        |      dk(  sJ |\  }}t        |t
              sJ t        |t              sJ y)uB   compile_briefing()이 (str, dict) 튜플을 반환하는지 확인r=   u0   compile_briefing()은 튜플을 반환해야 함r   N)r   r^   
isinstancetupler   strdict)rA   r   rB   rc   r   s        r   )test_compile_briefing_returns_status_dictz8TestSaveStatus.test_compile_briefing_returns_status_dict6  sg     1181D&%(\*\\(6{a$&#&&&+t,,,r   c                 j    t         j                  |      \  }}g d}|D ]  }||v rJ d| d        y)u7   status_dict에 필수 키가 모두 포함되어야 함r=   ro  u   status_dict에 'u   ' 키 없음Nr   )rA   r   r   r   required_keyskeys         r   test_status_dict_required_keysz-TestSaveStatus.test_status_dict_required_keys?  sP    (9989L;

 ! 	LC+%K)9#l'KK%	Lr   c                 X    t         j                  |      \  }}|d   dk(  sJ |d   J y)u   정상 실행 시 status='ok'r=   rv   rn  rv  Nr   r   s       r   test_status_dict_ok_on_successz-TestSaveStatus.test_status_dict_ok_on_successP  s=    (9989L;8$,,,7#+++r   c                 "   t        j                  t        j                        j	                  d      }t        |dd|dd|dd|ddi       t        |dddd	d
|ddi       t        j                  |      \  }}|d   dk(  sJ |d   dk(  sJ y)u-   teams_active / teams_idle 카운트 정확성r  rs   rt   ru   rx   rz   r{   r   ztask-active.1r      활성 작업r   Nr   r=   rr  r   rs  r   	r   r|   r   r}   r  r   r"   r   r^   rA   r   r?  r   r   s        r   test_status_dict_teams_countz+TestSaveStatus.test_status_dict_teams_countV  s    ,,x||,556JK'3gF'-@'-@		
 	.*#2'") $"		
 )9989L;>*a///<(A---r   c                     t        |dddi       t        |dddi       t        j                  |      \  }}|d   dk(  sJ y	)
u0   done_pending: events/*.done 파일 수와 일치ztask-50.1.doner   z	task-50.1ztask-51.1.donez	task-51.1r=   r   r   Nr   r   s       r   #test_status_dict_done_pending_countz2TestSaveStatus.test_status_dict_done_pending_countt  sP    x!1I{3KLx!1I{3KL(9989L;>*a///r   c                     t        |dddd       t        |dddd       t        j                  |      \  }}|d	   d
k(  sJ y)u1   questions_pending: 미응답 질문 수와 일치zq-a.jsonrz   u   질문1r  zq-b.jsonr{   u   질문2r=   rt  r   Nr	  r   s       r   (test_status_dict_questions_pending_countz7TestSaveStatus.test_status_dict_questions_pending_count{  sO    h
V,STh
V,ST(9989L;./1444r   c                 l    d}t        |d|d       t        j                  |      \  }}|d   |k(  sJ y)uG   guidance_last_saved: session-guidance.json의 saved_at 필드와 일치z2026-03-15T03:50:00Zr   )r   saved_atr=   ru  Nr   )rA   r   r  r   r   s        r   $test_status_dict_guidance_last_savedz3TestSaveStatus.test_status_dict_guidance_last_saved  sA    )h[h OP(9989L;01X===r   c           
          |dz  j                         rJ dddddddddd	}t        j                  ||       |dz  d	z  j                         sJ y)
u/   whisper 디렉토리가 없어도 자동 생성r-   rm  rn  rX  r   Nro  r=   rw  )ry  r   rx  )rA   r   r   s      r   #test_save_status_creates_parent_dirz2TestSaveStatus.test_save_status_creates_parent_dir  sl    y(002223 "!"#'

 	##K(#C9$}4<<>>>r   N)rk   rl   rm   rn   r   r{  r  r  r  r  r  r  r  r  r  r  ro   r   r   rk  rk    s    JQd Q"& &05T 5&-$ -Lt L",t ,.T .<0D 05 5>T >?D ?r   rk  c                   L    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	y)	TestProcessingButNoRunningTasksuU   bot-activity가 processing이지만 실제 running task가 없으면 유휴로 표시r   c                 :   t        j                  t        j                        j	                  d      }t        |ddd|dii       t        j                  |      d   }t        d |j                         D        d	      }d
|v s
J d|        d|vs
J d|        y)uN   processing 상태 + running task 없음 → 브리핑에서 '유휴'로 표시r  rs   r{   rt   ru   r=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  zdTestProcessingButNoRunningTasks.test_processing_no_running_shows_idle_in_briefing.<locals>.<genexpr>  rV  rW  rX     유휴u)   processing+no running → 유휴 기대: u    processing이 직접 표시됨: NrZ  r\  s        r   1test_processing_no_running_shows_idle_in_briefingzQTestProcessingButNoRunningTasks.test_processing_no_running_shows_idle_in_briefing  s    ,,x||,556JKfHIJ	

 !1181DQGRV%6%6%8RTVW	9$](QR[Q\&]]$9,\0PQZP[.\\,r   c                 H   t        j                  t        j                        j	                  d      }t        |ddd|dii       t        |ddddd	|d
di       t        j                  |      d   }t        d |j                         D        d      }d|v s
J d|        y
)uD   processing 상태 + running task 있음 → '작업중'으로 표시r  rs   rz   rt   ru   z
task-500.1r   r  r   Nr   r=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  z]TestProcessingButNoRunningTasks.test_processing_with_running_shows_working.<locals>.<genexpr>  rV  rW  rX  	   작업중u)   processing+running → 작업중 기대: r   r|   r   r}   r  r   r"   r   r^   r[  rH  r\  s        r   *test_processing_with_running_shows_workingzJTestProcessingButNoRunningTasks.test_processing_with_running_shows_working  s    ,,x||,556JKfHIJ	
 	+*#2'") $		
 !1181DQGRV%6%6%8RTVW	i'`+TU^T_)``'r   c                    t        j                  t        j                        j	                  d      }t        |dd|dd|ddi       t        j                  |      \  }}|d   dk(  sJ d	|d           |d
   dk(  sJ y)uL   processing + no running → status_dict에서 teams_idle 카운트에 포함r  rs   rt   ru   )r{   r   r=   rs  r   u   teams_idle=2 기대: rr  r   Nr_  r  s        r   1test_processing_no_running_status_dict_idle_countzQTestProcessingButNoRunningTasks.test_processing_no_running_status_dict_idle_count  s    ,,x||,556JK'3gF'3gF	
 )9989L;<(A-b1F{S_G`Fa/bb->*a///r   c                    t        j                  t        j                        j	                  d      }t        |ddd|dii       t        j                  |      d   }t        d |j                         D        d	      }d
|v s
J d|        y)u/   알 수 없는 상태 → '상태불명' 표시r  rs   rz   rv  ru   r=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  zTTestProcessingButNoRunningTasks.test_unknown_status_shows_unknown.<locals>.<genexpr>  rV  rW  rX  u   상태불명u(   unknown status → 상태불명 기대: NrZ  r\  s        r   !test_unknown_status_shows_unknownzATestProcessingButNoRunningTasks.test_unknown_status_shows_unknown  s    ,,x||,556JKf7CDE	
 !1181DQGRV%6%6%8RTVW	*b.VW`Va,bb*r   c                 "   t        j                  t        j                        j	                  d      }t        |dd|dd|dd|ddi       t        |dddd	d
|ddi       t        j                  |      \  }}|d   dk(  sJ |d   dk(  sJ y)ud   혼합 상태: dev1(running), dev2(processing+no running), dev3(idle) → 각각 올바르게 표시r  rs   rt   ru   rx   r  z
task-600.1r   u   활성r   Nr   r=   rr  r   rs  r   r  )rA   r   r?  rc   r   s        r   test_mixed_teams_statusz7TestProcessingButNoRunningTasks.test_mixed_teams_status  s    ,,x||,556JK'3gF'3gF'-@		
 	+*#+'") $		
 .>>>Q>*a///<(A---r   N)
rk   rl   rm   rn   r   r  r  r  r  r  ro   r   r   r  r    sG    _]$ ]a4 a00$ 0 	c$ 	c. .r   r  c                   |    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	defdZ
defd	Zdefd
ZdefdZy)TestGhostTasksuH   detect_ghost_tasks() - 4시간 이상 running 태스크 고스트 감지r   c                 ,   t        j                  t        j                        t	        d      z
  j                         }ddddd|di}t        j                  |      }t        |      dk(  sJ |d	   d
   dk(  sJ |d	   d   dk(  sJ |d	   d   dk\  sJ y)u+   4시간 이상 running → 고스트 감지   r   z
task-700.1r   u   고스트 태스크r   r   r   r   rv   r   r   r   r   	team_namer   r   g      @N	r   r|   r   r}   r   r~   r   detect_ghost_tasksr   rA   r   five_hours_agotask_timersrB   s        r   test_detect_ghost_over_4hz(TestGhostTasks.test_detect_ghost_over_4h
  s    ",,x||4yq7IITTV'&4#,
 !33K@6{aay#|333ay%///ay!S(((r   c                     t        j                  t        j                        t	        d      z
  j                         }ddddd|di}t        j                  |      }t        |      dk(  sJ y	)
u+   4시간 미만 running → 고스트 아님r   r   z
task-701.1r   u   정상 태스크r   r  r   Nr  )rA   r   r  r  rB   s        r   test_no_ghost_under_4hz%TestGhostTasks.test_no_ghost_under_4h  si    !hll3ia6HHSSU'&1#+
 !33K@6{ar   c                     t        j                  t        j                        t	        d      z
  j                         }ddddd|di}t        j                  |      }t        |      dk(  sJ y	)
u+   completed 상태는 고스트 대상 아님r  r   z
task-702.1r   r   r   r  r   Nr  r  s        r   test_completed_task_not_ghostz,TestGhostTasks.test_completed_task_not_ghost+  si    ",,x||4yq7IITTV'&1%,
 !33K@6{ar   c                     t        j                  t        j                        t	        d      z
  j                         }t        |ddddd|ddi       t        j                  |	      d
   }d|v sJ d|v sJ d|v sJ y)u7   고스트 태스크 → [고스트경고] 섹션 출력r  r   z
task-703.1r   u   고스트 의심 작업r   Nr   r=   r      [고스트경고]	   고스트	r   r|   r   r}   r   r~   r"   r   r^   )rA   r   r  rc   s       r   test_ghost_warning_in_briefingz-TestGhostTasks.test_ghost_warning_in_briefing:  s    ",,x||4yq7IITTV+*#<'"0 $		
 !1181DQG"f,,,v%%%f$$$r   c                     t        j                  t        j                        t	        d      z
  j                         }t        |ddddd|ddi       t        j                  |	      d
   }d|vsJ y)u>   최근 태스크만 있으면 [고스트경고] 출력 안 함r   r   z
task-704.1r   u   최근 작업r   Nr   r=   r   r  r  )rA   r   r   rc   s       r   !test_no_ghost_warning_when_recentz0TestGhostTasks.test_no_ghost_warning_when_recentO  sy     X\\2YQ5GGRRT+*#2'". $		
 !1181DQG"&000r   c                    t        j                  t        j                        t	        d      z
  j                         }t        j                  t        j                        j                  d      }t        |ddd|dii       t        |ddd	d
d|ddi       t        j                  |      d   }t        d |j                         D        d      }d|v sJ d|v sJ y)uJ   고스트 태스크는 [팀] 섹션에서 '⚠️고스트?' 마커 표시r  r   r  rs   r{   rt   ru   z
task-705.1r   r  r   Nr   r=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  zKTestGhostTasks.test_ghost_task_team_display_ghost_marker.<locals>.<genexpr>x  rV  rW  rX  u   ⚠️고스트?u   실질유휴r   r|   r   r}   r   r~   r  r   r"   r   r^   r[  rH  rA   r   r  r?  rc   rJ  s         r   )test_ghost_task_team_display_ghost_markerz8TestGhostTasks.test_ghost_task_team_display_ghost_markerb  s    ",,x||4yq7IITTV,,x||,556JKfHIJ	
 	+*#.'"0 $		
 !1181DQGRV%6%6%8RTVW	!Y...***r   c                    t        j                  t        j                        t	        d      z
  j                         }t        j                  t        j                        j                  d      }t        |ddd|dii       t        |ddd	d
d|ddi       t        j                  |      d   }t        d |j                         D        d      }d|v sJ d|vsJ y)u2   고스트만 있는 팀은 '(실질유휴)' 표시r  r   r  rs   rz   rt   ru   z
task-706.1r   r  r   Nr   r=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  zFTestGhostTasks.test_ghost_only_shows_practical_idle.<locals>.<genexpr>  rV  rW  rX  u   (실질유휴)r  r  r  s         r   $test_ghost_only_shows_practical_idlez3TestGhostTasks.test_ghost_only_shows_practical_idle|  s    ",,x||4yq7IITTV,,x||,556JKfHIJ	
 	+*#.'"0 $		
 !1181DQGRV%6%6%8RTVW	9,,,)+++r   c                 @   t        j                  t        j                        t	        d      z
  j                         }t        j                  t        j                        t	        d      z
  j                         }t        j                  t        j                        j                  d      }t        |ddd|dii       t        |d	d
dd|dddd
dd|ddd       t        j                  |      d   }t        d |j                         D        d      }d|v sJ y)u8   고스트+정상 태스크 혼합 → '작업중' 유지r  r   r   r  rs   rz   rt   ru   
task-707.1r   r  r   Nr   
task-708.1u   정상 작업)r  r  r=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  zKTestGhostTasks.test_mixed_ghost_and_normal_shows_working.<locals>.<genexpr>  rV  rW  rX  r  r  )rA   r   r  r   r?  rc   rJ  s          r   )test_mixed_ghost_and_normal_shows_workingz8TestGhostTasks.test_mixed_ghost_and_normal_shows_working  s   ",,x||4yq7IITTV X\\2YQ5GGRRT,,x||,556JKfHIJ	
 	  ,*#.'"0 $  ,*#2'". $	
* !1181DQGRV%6%6%8RTVW	i'''r   c                    t        j                  t        j                        t	        d      z
  j                         }t        |ddddd|ddi       t        j                  |	      d
   }|j                         j                         }d}d}t        |      D ]  \  }}d|v r|}d|v s|} |J d       |J ||k  sJ d       y)u6   [고스트경고]가 </whisper-briefing> 앞에 위치r  r   z
task-709.1r   r  r   Nr   r=   r   r  r[   u   [고스트경고] 줄이 없음u6   [고스트경고]가 </whisper-briefing> 뒤에 위치)r   r|   r   r}   r   r~   r"   r   r^   r`   rH  	enumerate)	rA   r   r  rc   output_lines	ghost_idx	close_idxilines	            r   %test_ghost_warning_before_closing_tagz4TestGhostTasks.test_ghost_warning_before_closing_tag  s    ",,x||4yq7IITTV+*#.'"0 $		
 !1181DQG||~002		 . 	GAt"d*	$,			
 $G&GG$$$$9$^&^^$r   N)rk   rl   rm   rn   r   r  r  r  r  r  r  r  r  r  ro   r   r   r  r    st    R)$ )$ t   d  %t %*1$ 1&+$ +4,T ,4 ($  (D_d _r   r  c                   |    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	defdZ
defd	Zdefd
ZdefdZy)TestBotOccupationuK   논리적 팀이 물리 봇을 점유할 때 dev팀 표시 변경 테스트r   c                 j   t        j                  t        j                        j	                  d      }t        |dd|dd|ddi       t        |dddd	d
|dddi       t        j                  |      d   }t        d |j                         D        d      }d|v s
J d|        d|v sJ d|v sJ y)uL   design이 bot-b 점유 → dev1이 '봇점유(design:task-1446.1)'로 표시r  rs   rx   ru   rt   rz   designtask-1446.1r     디자인 작업r   Nbot-br   r   r   rv   r   r   botr=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  zGTestBotOccupation.test_occupied_bot_shows_occupation.<locals>.<genexpr>  rV  rW  rX  	   봇점유u   봇점유 표시 기대: r  r\  s        r   "test_occupied_bot_shows_occupationz4TestBotOccupation.test_occupied_bot_shows_occupation  s    ,,x||,556JK#)G<%1GD 	
 	,'#5'") $" 
	
 !1181DQGRV%6%6%8RTVW	i'P+DYK)PP'9$$$	)))r   c                    t        j                  t        j                        j	                  d      }t        |dd|dd|dd|dd|dd|ddi       t        |ddd	d
|ddddddd
|ddddddd
|dddd       t        j                  |      d   }t        d |j                         D        d      }|j                  d      dk\  s
J d|        d|v sJ y)uI   design이 bot-b, bot-c, bot-d 점유 → dev1/2/3 모두 봇점유 표시r  rs   rx   ru   rt   )rz   r{   r   dev4r  r  r  u   디자인 작업 1r   Nr  r  task-1447.1u   디자인 작업 2bot-ctask-1448.1u   디자인 작업 3bot-d)r  r  r  r=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  z@TestBotOccupation.test_multiple_bots_occupied.<locals>.<genexpr>*  rV  rW  rX  r  r&  u   3개 봇점유 기대: r  )r   r|   r   r}   r  r   r"   r   r^   r[  rH  countr\  s        r   test_multiple_bots_occupiedz-TestBotOccupation.test_multiple_bots_occupied  s/   ,,x||,556JK#)G<#)G<#)G<#)G<%1GD 		
 	  -'#7'") $"   -'#7'") $"   -'#7'") $" '	
@ !1181DQGRV%6%6%8RTVW	{+q0W4KI;2WW09$$$r   c                 F   t        j                  t        j                        j	                  d      }t        |ddd|dii       t        |ddddd	|d
ddi       t        j                  |      d   }t        d |j                         D        d      }d|v sJ d|vsJ y
)uW   dev팀이 자체 작업 running 중이면 봇 점유와 무관하게 '작업중' 표시r  rs   rz   rt   ru   r   r   u   dev1 자체 작업r   Nr  r  r=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  zMTestBotOccupation.test_dev_team_with_own_task_not_affected.<locals>.<genexpr>H  rV  rW  rX  r  r  r  r\  s        r   (test_dev_team_with_own_task_not_affectedz:TestBotOccupation.test_dev_team_with_own_task_not_affected0  s    ,,x||,556JK<'B 	
 	+*#7'") $"
	
 !1181DQGRV%6%6%8RTVW	i''')+++r   c                 J   t        j                  t        j                        j	                  d      }t        |ddd|dii       t        |ddddd|d	d
di       t        j                  |      d   }t        d |j                         D        d      }d|v s
J d|        y	)u9   논리적 팀 표시에 봇 ID 포함: task-1446.1(bot-b)r  rs   r  rt   ru   r  r  r   Nr  r  r=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  zCTestBotOccupation.test_logical_team_shows_bot_id.<locals>.<genexpr>d  rV  rW  rX  z(bot-b)u   봇 ID 표시 기대: r  r\  s        r   test_logical_team_shows_bot_idz0TestBotOccupation.test_logical_team_shows_bot_idL  s    ,,x||,556JK\GD 	
 	,'#5'") $" 
	
 !1181DQGRV%6%6%8RTVW	I%K)?	{'KK%r   c                 L   t        j                  t        j                        j	                  d      }t        |dd|dd|ddi       t        |dddd	d
|ddi       t        j                  |      d   }t        d |j                         D        d      }d|v sJ d|vsJ y)u9   bot 필드 없는 작업은 봇점유 탐지에서 무시r  rs   rx   ru   rt   r  z
task-old.1r  u   봇 필드 없는 작업r   Nr   r=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  z?TestBotOccupation.test_no_bot_field_graceful.<locals>.<genexpr>  rV  rW  rX  r  r  r  r\  s        r   test_no_bot_field_gracefulz,TestBotOccupation.test_no_bot_field_gracefulg  s    ,,x||,556JK#)G<%1GD 	
 	+'#='") $
	
 !1181DQGRV%6%6%8RTVW	9$$$)+++r   c                 F   t        j                  t        j                        j	                  d      }t        |ddd|dii       t        |ddddd	||d
di       t        j                  |      d   }t        d |j                         D        d      }d|v sJ d|vsJ y)uC   논리적 팀 작업 완료 후 dev팀이 다시 '유휴'로 표시r  rs   rz   rx   ru   r  r  u   완료된 디자인 작업r   r  r  r=   r   c              3   D   K   | ]  }|j                  d       s|  ywrQ  rR  rS  s     r   rU  zPTestBotOccupation.test_occupation_cleared_after_task_complete.<locals>.<genexpr>  rV  rW  rX  r  r  Nr  r\  s        r   +test_occupation_cleared_after_task_completez=TestBotOccupation.test_occupation_cleared_after_task_complete  s    ,,x||,556JK6G< 	
 	,'#?)") '" 
	
 !1181DQGRV%6%6%8RTVW	9$$$)+++r   c                    t        j                  t        j                        t	        d      z
  j                  d      }t        j                  t        j                        j                  d      }t        |dd|dd|ddi       t        |d	d	d
dd|dddi       t        j                  |      d   }d|vs
J d|        y)u7   봇 점유 중인 dev팀은 [유휴경고]에서 제외r  r   r  rs   rx   ru   rt   r  r  r  r  r   Nr  r  r=   r   r   u&   봇점유 팀은 유휴경고 제외: )
r   r|   r   r}   r   r  r   r"   r   r^   )rA   r   r  r?  rc   s        r   %test_bot_occupied_not_in_idle_warningz7TestBotOccupation.test_bot_occupied_not_in_idle_warning  s    ",,x||4yq7IISSThi,,x||,556JK#)NC%1GD 	
 	,'#5'") $" 
	
 !1181DQGv-`1WX^W_/``-r   c                 "   t        j                  t        j                        j	                  d      }t        |dd|dd|dd|ddi       t        |dddd	d
|dddi       t        j                  |      \  }}|d   dk\  sJ d|d   v sJ y)u@   봇 점유 dev팀은 status_dict에서 teams_active로 카운트r  rs   rx   ru   rt   )rz   r{   r  r  r  r  r   Nr  r  r=   rr  r   r  rq  r  r  s        r   .test_status_dict_bot_occupied_counts_as_activez@TestBotOccupation.test_status_dict_bot_occupied_counts_as_active  s    ,,x||,556JK#)G<#)G<%1GD 	
 	,'#5'") $" 
	
 )9989L;>*a///k*<====r   c                    t        j                  t        j                        j	                  d      }dddd|dddd	d
d|ddd}dddd}t
        j                  ||      }d|v sJ |d   d   dk(  sJ |d   d   dk(  sJ d|vsJ y)u-   _build_bot_occupation 함수 직접 테스트r  r  r  u	   디자인r   r  )r   r   r   rv   r   r  r   r   u   dev2 자체 작업r  )r  r   rz   r{   r   )r  r  r  teamr   N)r   r|   r   r}   r  r   _build_bot_occupation)rA   r   r?  r  
bot_to_devrB   s         r   "test_build_bot_occupation_functionz4TestBotOccupation.test_build_bot_occupation_function  s    ,,x||,556JK )#*#% (&3#%
$  &H
 66{JOf~f%111f~i(M999V###r   N)rk   rl   rm   rn   r   r  r  r  r  r  r   r  r  r	  ro   r   r   r  r    sv    U*4 *<2%D 2%h, ,8Lt L6,4 ,<,D ,8ad a:>t ><$4 $r   r  c                 \    | dz  }|j                  t        j                  |      d       | S )Nactive-projects.jsonr   r   r!   )r   r	   r   s      r   make_active_projectsr    s+    ))ALLD!GL4Or   dir_pathnamemtime_offset_daysc                     | j                  dd       d| d}| |z  }|j                  |d       |dkD  r2ddl}|j                         |d	z  z
  }ddl}|j	                  |||f       |S )
uK   feedback_*.md 파일 생성. mtime_offset_days: 0=오늘, 1=하루 전, ...Tr   z
---
name: u-   
description: test
type: feedback
---

내용r   r   r   NiQ )r   r   timeosutime)	r  r#   r  r  r&   r   r  	new_mtimer  s	            r   make_feedback_filer    sw    NN4$N/D6!STG8ALL7L+1IIK#4u#<=	
Y	*+Hr   rv   c                 r    | dz  }|j                  dd       d| d}||z  }|j                  |d       | S )N	learningsTr   z---
status: u   
---

학습 내용r   r   r)   )r   r#   rv   learnings_dirr&   r   s         r   make_learning_filer    sM    {*Mt4fX%;<G ALL7L+Or   c                   X    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	defdZ
y	)
TestProjectProgressuE   load_project_progress() - active-projects.json에서 진행률 로드r   c                     t        |dddddddgi       t        j                  |      }t        |      dk(  sJ |D cg c]  }|d	   	 }}d|v sJ d|v sJ |D ci c]  }|d	   |d
    }}|d   dk(  sJ |d   dk(  sJ yc c}w c c}w )u*   정상 데이터 → name, progress 추출activeProjectAZ   r  progressProjectB(   r=   r   r  r!  N)r  r   load_project_progressr   )rA   r   rB   pnamesprogress_maps         r   !test_load_project_progress_normalz5TestProjectProgress.test_load_project_progress_normal#  s    <z_a>bcd	
 !666I6{a$*+q6++U"""U""":@AQ&	1Z=0AAJ'2---J'2--- , Bs   BBc                 >    t         j                  |      }|g k(  sJ yu   파일 없음 → 빈 리스트r=   N)r   r$  r@   s      r   'test_load_project_progress_missing_filez;TestProjectProgress.test_load_project_progress_missing_file2  s!     666I||r   c                 Z    t        |dg i       t        j                  |      }|g k(  sJ y)u,   active 배열 비어있음 → 빈 리스트r  r=   N)r  r   r$  r@   s      r   'test_load_project_progress_empty_activez;TestProjectProgress.test_load_project_progress_empty_active7  s/    X"~6 666I||r   c                 n    |dz  }|j                  dd       t        j                  |      }|g k(  sJ y)u   손상 JSON → 빈 리스트r  r   r   r   r=   N)r   r   r$  )rA   r   r   rB   s       r   $test_load_project_progress_corruptedz8TestProjectProgress.test_load_project_progress_corrupted=  s=    --	+g> 666I||r   c                 r    t        |ddddgi       t        j                  |      d   }d|v sJ d|v sJ y)	u7   compile_briefing에서 [진행률] 섹션 출력 확인r  r  K   r   r=   r      [진행률]N)r  r   r^   rb   s      r   test_progress_in_briefingz-TestProjectProgress.test_progress_in_briefingD  sS    <=>	
 !1181DQG&&&V###r   c                 B    t         j                  |      d   }d|vsJ y)u2   active-projects.json 없을 때 [진행률] 없음r=   r   r2  Nr   rb   s      r   'test_no_progress_in_briefing_when_emptyz;TestProjectProgress.test_no_progress_in_briefing_when_emptyN  s(     1181DQGF***r   N)rk   rl   rm   rn   r   r(  r+  r-  r/  r3  r5  ro   r   r   r  r     sM    O.$ . 
 T $$ $+ +r   r  c                   L    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	y)	TestStaleTasksuE   load_stale_tasks() - pending 상태 + 3일 이상 경과한 태스크r   c           
      L   t        j                  t        j                        t	        d      z
  j                         }|dz  }|j                  t        j                  ddd|ddii      d	
       t        j                  |      }t        |      dk(  sJ |d   d   dk(  sJ y)u'   pending 상태 + 4일 전 → 반환됨r  daysr    r   ztask-Xpendingu   오래된 대기 작업rv   r   r   r   r   r=   r   r   r   N)r   r|   r   r}   r   r~   r   r   r   r   load_stale_tasksr   rA   r   four_days_agor   rB   s        r   )test_load_stale_tasks_pending_over_3_daysz8TestStaleTasks.test_load_stale_tasks_pending_over_3_days\  s    !hll3iQ6GGRRT))	JJ"+&3'@   	 	
 !1181D6{aay#x///r   c           
          t        j                  t        j                        t	        d      z
  j                         }|dz  }|j                  t        j                  ddd|ddii      d	
       t        j                  |      }|g k(  sJ y)u$   pending + 1일 전 → 빈 리스트r   r9  r    r   ztask-Yr;  u   최근 대기 작업r<  r   r   r=   Nr   r|   r   r}   r   r~   r   r   r   r   r=  )rA   r   one_day_agor   rB   s        r   *test_load_stale_tasks_pending_under_3_daysz9TestStaleTasks.test_load_stale_tasks_pending_under_3_daysp  s    ||HLL1I14EEPPR))	JJ"+&1'=   	 	
 !1181D||r   c           
      *   t        j                  t        j                        t	        d      z
  j                         }|dz  }|j                  t        j                  dd|ddd|d	dd
i      d       t        j                  |      }|g k(  sJ y)u&   running/completed만 → 빈 리스트r  r9  r    r   r   u   실행 중 작업r<  r   r   )ztask-Rztask-Cr   r   r=   NrB  r>  s        r    test_load_stale_tasks_no_pendingz/TestStaleTasks.test_load_stale_tasks_no_pending  s    !hll3iQ6GGRRT))	JJ"+&3': #.&3'9   	 	
" !1181D||r   c                 >    t         j                  |      }|g k(  sJ yr*  )r   r=  r@   s      r   "test_load_stale_tasks_missing_filez1TestStaleTasks.test_load_stale_tasks_missing_file  s!     1181D||r   c           
      0   t        j                  t        j                        t	        d      z
  j                         }|dz  }|j                  t        j                  ddd|ddii      d	
       t        j                  |      d   }d|v sJ d|v sJ y)u7   compile_briefing에서 [미착수] 섹션 출력 확인r  r9  r    r   ztask-stale-1r;  u   미착수 태스크r<  r   r   r=   r   u   [미착수]N)r   r|   r   r}   r   r~   r   r   r   r   r^   )rA   r   r?  r   rc   s        r   test_stale_tasks_in_briefingz+TestStaleTasks.test_stale_tasks_in_briefing  s    !hll3iQ6GGRRT))	JJ""+&3'<%   	 	
 !1181DQG&&&'''r   N)
rk   rl   rm   rn   r   r@  rD  rF  rH  rJ  ro   r   r   r7  r7  Y  sA    O0$ 0(4 & 04 
(T (r   r7  c                   L    e Zd ZdZdefdZdefdZdefdZdefdZdefdZ	y)	TestRecentMistakesuF   load_recent_mistakes() - feedback_*.md 파일에서 최신 3개 추출r   c                 Z   |dz  }t        |ddd       t        |ddd       t        |d	d
d       t        |ddd       t        |ddd       t        j                  |      }t        |      dk(  sJ |D cg c]  }|d   	 }}d|v sJ d|v sJ d
|v sJ d|vsJ d|vsJ yc c}w )u%   5개 파일 중 최신 3개만 반환feedbackzfeedback_01.mdu   실수1r  )r  zfeedback_02.mdu   실수2r&  zfeedback_03.mdu   실수3r   zfeedback_04.mdu   실수4r   zfeedback_05.mdu   실수5r   feedback_dirr  Nr  r   load_recent_mistakesr   )rA   r   rP  rB   r   r&  s         r    test_load_recent_mistakes_normalz3TestRecentMistakes.test_load_recent_mistakes_normal  s    *,<)99XYZ<)99XYZ<)99XYZ<)99XYZ<)99XYZ 55<5P6{a$*+q6++E!!!E!!!E!!!%%%%%% ,s   ;B(c                 H    |dz  }t         j                  |      }|g k(  sJ y)u%   디렉토리 없음 → 빈 리스트nonexistent_feedbackrO  N)r   rR  )rA   r   missing_dirrB   s       r   %test_load_recent_mistakes_missing_dirz8TestRecentMistakes.test_load_recent_mistakes_missing_dir  s,    !77 55;5O||r   c                     |dz  }t        |dd       t        j                  |      }t        |      dk(  sJ |d   d   dk(  sJ d|d   v sJ y	)
u%   frontmatter name 정확 추출 확인rN  zfeedback_test.mdu   정확한이름rO  r   r   r  dateNrQ  rA   r   rP  rB   s       r   )test_load_recent_mistakes_name_extractionz<TestRecentMistakes.test_load_recent_mistakes_name_extraction  si    *,<);=NO 55<5P6{aay $5555"""r   c                 n    |dz  }|j                  dd       t        j                  |      }|g k(  sJ y)u<   디렉토리 존재하지만 파일 없음 → 빈 리스트rN  Tr   rO  N)r   r   rR  rZ  s       r   +test_load_recent_mistakes_no_feedback_filesz>TestRecentMistakes.test_load_recent_mistakes_no_feedback_files  s>    *,4$7 55<5P||r   c                 t    |dz  }t        |dd       t        j                  ||      d   }d|v sJ d|v sJ y)u3   compile_briefing에서 [최근실수] 출력 확인rN  zfeedback_recent.mdu   최근실수항목)r>   rP  r   u   [최근실수]N)r  r   r^   )rA   r   rP  rc   s       r   test_mistakes_in_briefingz,TestRecentMistakes.test_mistakes_in_briefing  sR    *,<)=?ST 118R^1_`ab6)))#v---r   N)
rk   rl   rm   rn   r   rS  rW  r[  r]  r_  ro   r   r   rL  rL    sA    P& &$d #$ #D .$ .r   rL  c                   @    e Zd ZdZdefdZdefdZdefdZdefdZy)TestPendingLearningsuH   load_pending_learnings() - learnings/*.md에서 status=pending 카운트r   c                     t        |dd       t        |dd       t        |dd       t        |dd       t        |dd       t        j                  |	      }|d
k(  sJ y)u'   3개 pending, 2개 applied → 3 반환zlearn_01.mdr;  rv   zlearn_02.mdzlearn_03.mdzlearn_04.mdappliedzlearn_05.mdr=   r&  N)r  r   load_pending_learningsr@   s      r   "test_load_pending_learnings_normalz7TestPendingLearnings.test_load_pending_learnings_normal  s]    8]9E8]9E8]9E8]9E8]9E 777J{{r   c                 >    t         j                  |      }|dk(  sJ y)u   폴더 없음 → 0 반환r=   r   N)r   re  r@   s      r   "test_load_pending_learnings_no_dirz7TestPendingLearnings.test_load_pending_learnings_no_dir   s!     777J{{r   c                 n    |dz  }|j                  dd       t        j                  |      }|dk(  sJ y)u+   폴더 있지만 파일 없음 → 0 반환r  Tr   r=   r   N)r   r   re  )rA   r   r  rB   s       r   %test_load_pending_learnings_empty_dirz:TestPendingLearnings.test_load_pending_learnings_empty_dir  s>     ;.D48 777J{{r   c                 z    t        |dd       t        |dd       t        j                  |      d   }d|v sJ y)	uD   compile_briefing에서 [미처리학습] 섹션 항상 출력 확인z
learn_a.mdr;  rc  z
learn_b.mdr=   r   u   [미처리학습]N)r  r   r^   rb   s      r   test_learnings_in_briefingz/TestPendingLearnings.test_learnings_in_briefing  s@    8\)D8\)D 1181DQG"f,,,r   N)	rk   rl   rm   rn   r   rf  rh  rj  rl  ro   r   r   ra  ra    s5    R4 4 
d -4 -r   ra  )r   )r;  )8rn   importlib.util	importlibr   sysr   r   r   pathlibr   rg   __file__parent_SCRIPTS_DIRpathinsertr  _MODULE_PATHutilspec_from_file_locationspecmodule_from_specr   loaderexec_moduler  r   r"   r%   r+   r/   r3   r8   r:   rq   r   r   r   r   r   r  r+  r;  rM  rk  r  r  r  r  intr  r  r  r7  rL  ra  ro   r   r   <module>r~     s     
 2 2   H~$$++ 3|$ % 22~~--.?N ..11$7{{     ( D T t D T T S   $ #   D   D C t  4 #   (@ (@`+ +fG G^M0 M0j/" /"n7 7J+ +f,  , h* *>W# W#~Bh BhTa? a?R_. _.NL_ L_h[$ [$F	4 t    C TW `d   c RV 1+ 1+rY( Y(B1. 1.r- -r   