
    iY              
       j   d 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Zddl	Z	ddl
mZ ddlmZ ddlmZmZmZmZmZ  ej(                  e      Zh dZ ej0                         dz  dz  Zd	Zd
edee   fdZd
edee   defdZdedee   fdZdededdfdZ dedeejB                     fdZ"dejB                  dedee   fdZ#dejB                  deeef   deeee$f      fdZ%dejB                  dededee   fdZ& G d d      Z'	 d#d
edee   dee   dee   fd Z(d! Z)ed"k(  r e)        yy)$u  
ast_dependency_map.py — AST 기반 Python 의존성 맵 생성기

주어진 변경 파일 목록에 대해 영향 범위(blast radius)를 분석합니다.
- 직접 임포터 (direct importers)
- 전이적 의존자 (transitive dependents, 최대 2홉)
- 테스트 파일 (test_*.py 또는 tests/ 디렉토리)
- 함수 레벨 호출자 (function-level callers)

Usage:
    python3 ast_dependency_map.py --root /home/jay/workspace/dashboard --files data_loader.py
    python3 ast_dependency_map.py --root /home/jay/workspace/dashboard --files data_loader.py --function get_member_status
    python3 ast_dependency_map.py --root /home/jay/workspace/dashboard --files wiki_engine.py --function sync_firestore
    N)defaultdict)Path)DictListOptionalSetTuple>   .git.next.venv
.worktrees.mypy_cache.pytest_cachesite-packages.codegraph-venvdistvenvbuild__pycache__node_modules.cacher   zanu-ast   rootreturnc                     g }t        j                  |       D ]X  \  }}}|D cg c]  }|t        vs| c}|dd |D ]1  }|j                  d      s|j	                  t        |      |z         3 Z |S c c}w )u8   EXCLUDE_DIRS를 건너뛰며 .py 파일을 수집한다.N.py)oswalkEXCLUDE_DIRSendswithappendr   )r   outdirpathdirnames	filenamesdfns          K/home/jay/workspace/.worktrees/task-2374-dev7/scripts/ast_dependency_map.py_iter_py_filesr)   .   s}    C(* /$9"*DQa|.CqD 	/B{{5!

4=2-.	// J	 Es
   A5A5py_filesc                     t        j                         }|j                  t        | j	                               j                                |j                  t        t        |            j                                d}|D ]$  }	 |j                         j                  }||kD  r|}& |j                  |dj                                |j                  dt         j                                |j                         dd S # t        $ r Y w xY w)u?   루트 경로 + 파일 개수 + 최대 mtime 기반 캐시 키.g        z.6fvN   )hashlibsha256updatestrresolveencodelenstatst_mtimeOSErrorCACHE_VERSION	hexdigest)r   r*   h	max_mtimefms         r(   
_cache_keyr>   :   s    AHHS '')*HHSX&&()I 	!!A9}		 HH	#&&()HHq '')*;;="	  		s   !D	DD	cache_keyc                    t         |  dz  }|j                         sy 	 |j                  d      5 }t        j                  |      }d d d        j                  d      t        k7  ry |S # 1 sw Y   $xY w# t        $ r Y y w xY w)N.pklrbversion)	CACHE_DIRexistsopenpickleloadgetr8   	Exception)r?   
cache_pathr<   datas       r(   _try_load_cacherM   L   s    	{$//J__T" 	"a;;q>D	"88I-/		" 	"
  s.   A5 A) A5 'A5 )A2.A5 5	B Bpayloadc                 d   	 t         j                  dd       t         |  dz  }|j                  d      }|j                  d      5 }t	        j
                  ||       d d d        |j                  |       y # 1 sw Y   xY w# t        $ r"}t        j                  d|        Y d }~y d }~ww xY w)NT)parentsexist_okrA   z.pkl.tmpwbu   [ast-cache] save 실패: )
rD   mkdirwith_suffixrF   rG   dumpreplacerJ   loggerwarning)r?   rN   rK   tmpr<   es         r(   _save_cacher[   Z   s    8t4I;d!33
$$Z0XXd^ 	$qKK#	$J	$ 	$  821#6778s0   AB A8B 8B=B 	B/B**B/	file_pathc                    	 | j                         j                  }|dkD  rt        j                  d|dz  |        y| j	                  dd      }t        j                  |t        |             S # t        $ r Y yt        $ r Y yw xY w)	uU   파일을 AST로 파싱합니다. 구문 오류가 있으면 None을 반환합니다.i u!   대형 파일 스킵 (%.1fKB): %si   Nzutf-8rV   )encodingerrors)filename)
r5   st_sizerW   rX   	read_textastparser1   SyntaxErrorrJ   )r\   	file_sizesources      r(   _parse_filerh   k   s    NN$,,	wNN>	D@PR[\$$gi$Hyy#i.99  s   9A/ 2A/ /	B:BBtreepkg_namec                    t               }t        j                  |       D ]  }t        |t        j                        r|j
                  D ]t  }|j                  }d|v rQ|j                  d      }|d   |k(  r#t        |      dkD  r|j                  |d          O|j                  |d          d|j                  |       v t        |t        j                        s|j                  xs d}|j                  }|dkD  rW|r'|j                  d      d   }	|j                  |	       |j
                  D ]  }|j                  |j                          5|s9|j                  d      }|d   |k(  r$t        |      dkD  r|j                  |d          vt        |      dk(  r|j                  |d          |j                  |d           |S )u~  
    AST에서 임포트된 모듈 이름을 추출합니다.

    다음 패턴을 처리합니다:
    - import X
    - from X import Y
    - from dashboard.X import Y  →  X
    - from .X import Y  →  X (상대 임포트)
    - try/except 블록 내의 임포트 패턴

    반환값: 해당 파일이 의존하는 모듈명 집합 (예: {"data_loader", "server_utils"})
    .r   r    )setrc   r   
isinstanceImportnamesnamesplitr4   add
ImportFrommodulelevel)
ri   rj   modulesnodealiasrs   partsrw   rx   tops
             r(   _extract_imported_modulesr~   {   s    G +.dCJJ' &zz$; JJsOEQx8+E
QE!H-  E"I.KK%& cnn-[[&BFJJEqy ,,s+A.CKK$ "& 0EJJ/0 "LL-EQx8+E
QE!H-UqE!H-  E!H-W+.Z N    imported_namesc                    g }t        j                  |       D ]  }t        |t         j                        s|j                  }t        |t         j
                        r7|j                  |v sS|j                  |j                  |j                  f       {t        |t         j                        s|j                  }||v s|j                  ||j                  f        |S )u   
    AST에서 특정 함수 호출을 찾습니다.

    imported_names: {함수명: 소스모듈명} 매핑
    반환값: [(함수명, 줄번호), ...]
    )rc   r   rp   CallfuncNameidr!   lineno	Attributeattr)ri   r   callsrz   r   rs   s         r(   _extract_function_callsr      s     $&E 6dCHH%99D$)77n,LL$''4;;!78D#--0yy>)LL$!456 Lr   target_modulec                    t               }t        j                  |       D ]  }t        |t        j                        s|j
                  xs d}|j                  }|dkD  r|r|j                  d      d   nd}|}n6|j                  d      }	|	d   |k(  rt        |	      dkD  r|	d   }n	|	r|	d   nd}||k(  s|j                  D ]E  }
|j                  |
j                         |
j                  s+|j                  |
j                         G  |S )u   
    특정 모듈에서 임포트된 이름들을 추출합니다.

    target_module: 소스 모듈 이름 (예: "data_loader")
    반환값: 임포트된 이름 집합
    rn   r   rl   r   )ro   rc   r   rp   rv   rw   rx   rt   r4   rr   ru   rs   asname)ri   r   rj   rr   rz   rw   rx   base
normalizedr|   r{   s              r(   #_extract_imported_names_from_moduler      s     eE 0dCNN+[[&BFJJE qy/5v||C(+2!
S)8x'CJN!&qJ-2qJ]*!ZZ 0EIIejj)||		%,,/0%0. Lr   c            	           e Zd ZdZdefdZd Zdedee   fdZ	dede
e   fd	Zdded
ede
e   fdZdedeej                     fdZdededeeeef      fdZdedefdZy)DependencyGraphu]   
    대상 디렉토리 내 Python 파일들의 의존성 그래프를 구축합니다.
    r   c                     || _         |j                  | _        i | _        i | _        i | _        t        t              | _        | j                          y N)
r   rs   rj   module_to_filefile_to_astfile_importsr   ro   module_importers_build)selfr   s     r(   __init__zDependencyGraph.__init__   sD    			 02=?246A#6Fr   c                 2   t        j                          }d}t        | j                        }t        | j                  |      }t	        |      }|t
        j                  dt        |d          d       |d   j                         D ci c]  \  }}|t        |       c}}| _
        |d   j                         D ci c]  \  }}t        |      t        |       c}}| _        t        t        |d   j                         D ci c]  \  }}||D ch c]  }t        |       c}! c}}}      | _        yt
        j                  d       |D ]  }	|	j                  | j                        }
|
j                   }|| j                  vr|	| j                  |<   H| j                  |   }t        |	j"                        t        |j"                        k  s|	| j                  |<    |D ]  }	t        j                          |z
  |kD  rrt
        j                  d	|t        | j$                        t        |             |D ]7  }|| j$                  vsd| j$                  |<   t               | j                  |<   9  n[t'        |	      }|| j$                  |	<   |&t)        || j*                        }|| j                  |	<   t               | j                  |	<    | j                  j                         D ]*  \  }	}|D ]   }| j                  |   j-                  |	       " , t.        | j                  j                         D ci c]  \  }}|t1        |       c}}| j                  j                         D ci c]  \  }}t1        |      t3        |       c}}| j                  j                         D ci c]  \  }}||D cg c]  }t1        |       c}! c}}}d
}t5        ||       yc c}}w c c}}w c c}w c c}}}w c c}}w c c}}w c c}w c c}}}w )u:   디렉토리를 스캔하고 그래프를 구축합니다.<   Nz[ast-cache] hit (r   z files)r   r   u   [ast-cache] miss → rebuildu;   그래프 구축 타임아웃 (%ds): %d/%d 파일 처리됨)rC   r   r   r   )timer)   r   r>   rM   rW   rX   r4   itemsr   r   ro   r   r   r   relative_tostemr|   r   rh   r~   rj   ru   r8   r1   listr[   )r   build_startBUILD_TIMEOUTr*   ckcachedkr,   ppy_filerelmodule_nameexisting	remainingri   importedimportsmodrN   s                      r(   r   zDependencyGraph._build  s   iik!$)), 		8, $NN.s6:J3K/L.MWUV:@AQ:R:X:X:Z"[$!Q1d1g:"[D=CN=S=Y=Y=[ \TQa#a& \D$/4:;M4N4T4T4V6 6,0AqQ'DG''6 %D! 56   	?G%%dii0C((K$"5"553:##K0..{;w}}%HNN(;;7>D''4	?   	3Gyy{[(=8Q!(()M	 "* =I (8(886:((37:u)))4= w'D(,DW%4T4==I-5!!'*-0U!!'*+	30 !% 1 1 7 7 9 	8GW 8%%c*..w78	8 %595H5H5N5N5PQTQq#a&yQ9=9J9J9P9P9RSASVT!W_SDHDYDYD_D_Da b bDAqQ$7SV$7!7 b	
 	B y #\ \'6l RS$7 bsH   
O)?O/O:O5'O:;P1P
1P>PP5O:Pr`   r   c                     |j                  d      r|dd }n|}|| j                  v r| j                  |   S | j                  |z  }|j                         r|S y)u   
        파일명 또는 모듈명으로 파일 경로를 반환합니다.
        예: "data_loader.py" 또는 "data_loader"
        r   N)r    r   r   rE   )r   r`   r   	candidates       r(   get_file_pathzDependencyGraph.get_file_pathZ  sh     U#"3B-K"K $---&&{33 II(	r   r   c                 \    t        | j                  j                  |t                           S )uE   해당 모듈을 직접 임포트하는 파일들을 반환합니다.)ro   r   rI   )r   r   s     r(   get_direct_importersz$DependencyGraph.get_direct_importersp  s"    4((,,[#%@AAr   hopsc                 @   t               }| j                  |      }|j                  |       t        |dz
        D ]`  }t               }|D ]M  }|j                  }| j                  |      }	|	D ])  }
|
|vs|j                  |
       |j                  |
       + O |}b |S )u   
        전이적 의존자를 최대 hops 단계까지 반환합니다.
        직접 임포터는 포함하지 않습니다.
        r   )ro   r   r0   ranger   ru   )r   r   r   visitedcurrent_level_
next_levelr\   file_module	importersimps              r(   get_transitive_dependentsz)DependencyGraph.get_transitive_dependentst  s    
 !U#'#<#<[#I}%tax 
	'A$'EJ* )	'nn 55kB	$ )C')"s+C()	) 'M
	' r   r\   c                 t    || j                   v r| j                   |   S t        |      }|| j                   |<   |S )u?   캐시 hit 후 file_to_ast가 비어 있을 경우 lazy 파싱.)r   rh   )r   r\   ri   s      r(   _ensure_astzDependencyGraph._ensure_ast  s@    (((##I..9%&*#r   function_namec                 r   g }| j                  |      }| j                  |d      }||z  }|D ]  }| j                  |      }|t        ||| j                        }	||	vr%||	D 
cg c]  }
|
j                  d      d    c}
vr	 t        |||i      }|D ]  \  }}|j                  ||f         |S c c}
w )uo   
        특정 모듈의 특정 함수를 호출하는 (파일, 줄번호) 목록을 반환합니다.
           r   rl   rm   )r   r   r   r   rj   rt   r   r!   )r   r   r   callersdirect_importersall_dependentsall_candidatesr\   ri   r   nr   r   r   s                 r(   get_function_callersz$DependencyGraph.get_function_callers  s     +-44[A 77!7L)N:' 	4I##I.D| A{TXTaTabN N2}guLvbcQWWUX\Z\M]Lv7v ,D=+2NOE" 4	6	6234	4$  Mws   %B4c                     |j                   }|j                  d      r|j                  d      ry	 |j                  | j                        }|j
                  }d|dd v ry	 y# t        $ r Y yw xY w)u+   테스트 파일 여부를 확인합니다.test_r   TtestsNrm   F)rs   
startswithr    r   r   r|   
ValueError)r   r\   rs   r   r|   s        r(   is_test_filezDependencyGraph.is_test_file  sy    ~~??7#e(<	''		2CIIE%*$ %   		s   .A" "	A.-A.N)r   )__name__
__module____qualname____doc__r   r   r   r1   r   r   r   r   intr   rc   Moduler   r   r	   r   boolr    r   r(   r   r      s    T I!Vc htn ,B BD	 BS  CPTI .T hszz.B  C DQVW[]`W`QaLb >d t r   r   changed_filesr   c                 H    t               g }|D ]  }t        j                         dz  }j                  |       t        |      j                  }j                  |      }j                  |      }|r|j                  |       j                  |d      }	|	|z
  }
t               }t               }|D ]6  }j                  |      r|j                  |       &|j                  |       8 |
D ]%  }j                  |      s|j                  |       ' g }|rj                  ||      }dt        dt        f fd||
z  |z  }t        |      }d|i}|r||d<   t        fd	|D              }t        fd
|
D              }t        fd|D              }t        fd|D              }|||||d|d<   t        j                         dz  |z
  }|dkD  r#t        d| d|ddt         j"                         t%        |d      |d<   |j'                  |        |S )u   
    변경된 파일들에 대한 의존성 분석을 수행합니다.

    반환값: 각 변경 파일에 대한 분석 결과 리스트
    i  r   r   r   r   c                 n    	 t        | j                              S # t        $ r t        |       cY S w xY wr   )r1   r   r   )r   r   s    r(   to_relzanalyze.<locals>.to_rel  s4    1==.// 1vs    44changed_filechanged_functionc              3   R   K   | ]  }j                  |      r |         y wr   r   .0r   graphr   s     r(   	<genexpr>zanalyze.<locals>.<genexpr>  s$     %gAQVQcQcdeQffQi%g   ''c              3   R   K   | ]  }j                  |      r |         y wr   r   r   s     r(   r   zanalyze.<locals>.<genexpr>  s$     faPUPbPbcdPeq	fr   c              3   .   K   | ]  } |        y wr   r   )r   r   r   s     r(   r   zanalyze.<locals>.<genexpr>  s     >aq	>s   c              3   >   K   | ]  \  }} |       d |   yw):Nr   )r   r   r   r   s      r(   r   zanalyze.<locals>.<genexpr>  s$     YFq	{!F84Ys   )r   r   transitive_dependents
test_filestotal_affectedblast_radiusi'  u$   ⚠️ 대형 파일 분석 경고: z (z.0fzms)fileanalysis_time_ms)r   r   r   r   r   r   discardr   ro   r   ru   r   r1   r4   sortedprintsysstderrroundr!   )r   r   r   resultsr   file_start_msr   r   target_pathr   r   r   non_test_directr   callers_with_linesall_affectedr   resultdirect_importers_reltransitive_reltest_files_relcallers_relelapsedr   r   s   `                      @@r(   analyzer    sZ    D!EG% M		d* 	L)<(-- ',&@&@&M )),7$$[1 %*$C$CKVW$C$X .1A A !$
%(U! 	'A!!!$q!##A&		' ' 	"A!!!$q!	"
 68!&!;!;K!W	d 	s 	 #35J"JZ"W\* L
 )6F%&  &%g9I%ggf3Hff>:>>YFXYY !5"%3(,"
~ ))+$}4V8bQTUXY`c`j`jk%*7A%6!"v[M^ Nr   c                  X   t        j                  dt         j                  t              } | j	                  dt
        dd       | j	                  ddd	d
d       | j	                  dt
        d d       | j	                  ddd
d       | j	                  dddd       | j                         }t        j                  t        j                  dt        j                         t        |j                        j                         }|j                         sHt!        t#        j$                  dd| i      t        j                         t        j&                  d       |j)                         sHt!        t#        j$                  dd| i      t        j                         t        j&                  d       t+        ||j,                  |j.                        }t1        |      dk(  r|d   n|}|j"                  rd }n|j2                  rdnd }t!        t#        j$                  |d|              y )!Nu)   AST 기반 Python 의존성 맵 생성기)descriptionformatter_classepilogz--rootrl   uY   분석 대상 Python 코드베이스 루트 디렉토리 (기본값: 현재 디렉토리))typedefaulthelpz--files+FILETu>   분석할 변경 파일 목록 (예: data_loader.py server.py))nargsmetavarrequiredr  z
--functionu8   함수 레벨 분석을 위한 함수명 (선택 사항)z--pretty
store_trueu-   들여쓰기된 JSON 출력 (기본값: True))actionr  r  z--jsonFu0   컴팩트 JSON 출력 (--pretty 오버라이드)z%(message)s)rx   formatstreamerroru0   루트 디렉토리를 찾을 수 없습니다: r   r   u/   루트 경로가 디렉토리가 아닙니다: )r   r   r   r   r   )ensure_asciiindent)argparseArgumentParserRawDescriptionHelpFormatterr   add_argumentr1   
parse_argsloggingbasicConfigWARNINGr   r   r   r   r2   rE   r   jsondumpsexitis_dirr  filesfunctionr4   pretty)parserargsr   r   outputr  s         r(   mainr,  -  s   $$? <<F
 h	   M   G	   <	   ?	   DgoomCJJW		?""$D;;=JJ#STXSY!Z[\	
 	;;=JJ#RSWRX!YZ[	
 	jjmmG w<1,WQZ'F yykkt	$**V%
?@r   __main__r   )*r   r  rc   r.   r"  r  r   rG   r   r   collectionsr   pathlibr   typingr   r   r   r   r	   	getLoggerr   rW   r   homerD   r8   r)   r1   r>   dictrM   r[   r   rh   r~   r   r   r   r   r  r,  r   r   r(   <module>r4     s    
    	  
  #  3 3			8	$ DIIK("Y.		 	$t* 	T T$Z C $s x~ 	83 	8 	8$ 	8"4 HSZZ$8  <CJJ <# <#c( <~#** d38n QUV[\_ad\dVeQf 2 cjj    X[  `cdg`h  PE E` $(]
]9] C=] 
$Z	]JFAR zF r   