
    j/                    \   d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	m
Z
 ddlmZmZ ddlmZ dZdZd	Ze
 G d
 d             ZddZddZddZddZ	 d	 	 	 	 	 ddZ	 	 	 	 	 	 	 	 	 	 ddZdd	 	 	 	 	 	 	 	 	 	 	 d dZ	 	 	 	 	 	 d!dZ	 	 	 	 	 	 d"dZd#dZdd$dZedk(  r e        yy)%u   dirty_registry.py — main dirty snapshot 기록(JSONL) + dirty 파일 owner 추정 +
EXTERNAL_DIRTY_BLOCKER vs OWN_DIRTY_FAIL 분류/분리.

task-2700 Phase 1 모듈 2.
    )annotationsN)asdict	dataclass)datetimetimezone)PathEXTERNAL_DIRTY_BLOCKEROWN_DIRTY_FAILCLEANc                  @    e Zd ZU ded<   ded<   ded<   ded<   ded<   y	)
DirtyRecordstrpathstatuszfloat | Nonemtimediff_summary
str | None
owner_taskN)__name__
__module____qualname____annotations__     +/home/jay/workspace/utils/dirty_registry.pyr   r      s    
IKr   r   c                   | dk(  ry| j                  d      r| dd }||k(  s|j                  |dz         ryd| v r^| j                  dd      }t        j                  ||      ryt        j                  t        j
                  j                  |      |      ryyt        j                  ||       ryt        j                  t        j
                  j                  |      |       ryy)	u   ** 및 /** 포함 glob 패턴으로 path 매칭.

    - "**" → 항상 True
    - "a/**" → a 또는 a/ prefix 매칭
    - ** 포함 패턴 → * 치환 후 fnmatch + basename fallback
    - 그 외 → fnmatch (basename fallback 포함)
    z**Tz/**N/*F)endswith
startswithreplacefnmatchosr   basename)patternr   prefixflat_patterns       r   
glob_matchr)   (   s     $ "6>T__Vc\:wtS1??4.??277++D1<@ tW%rww''-w7r   c                   t        |       | dz  }|j                         sg S 	 t        j                  |j	                  dd            }|j                  di       j                  dg       }g }|D ]L  }t        |t              s|j                         r|j                         d   nd}|s<|j                  |       N |S # t        $ r g cY S w xY w)	u   <capabilities_dir>/<task_id>.json 의 allowed_resources.paths 를 읽어
    각 항목에서 첫 공백 이전 토큰만 추출(주석 제거).

    예: "scripts/finish-task.sh (★ GIT-GATE...)" → "scripts/finish-task.sh"
    파일 없으면 [].
    z.jsonutf-8r"   )encodingerrorsallowed_resourcespathsr    )r   existsjsonloads	read_textget
isinstancer   stripsplitappend	Exception)capabilities_dirtask_idcap_filedata	paths_rawresultentrytokens           r   load_expected_filesrC   P   s     $%7)5(99H??	zz(,,gi,PQHH0"599'2F	 	%EeS)(-EKKM!$2Ee$	%  	s   BC -C CCc                   t        |      }|j                         syd}d}t        |j                  d            D ]L  }|j                  }t        t        |      |      }|D ]$  }t        ||       st        |      }	|	|kD  s!|	}|}& N |S )u   capabilities_dir 의 모든 task-*.json 을 스캔.

    load_expected_files 로 패턴 얻어 glob_match 되는 첫 task_id 반환.
    더 구체적인(긴) 패턴 우선. 없으면 None.
    Nztask-*.json)	r   is_dirsortedglobstemrC   r   r)   len)
r   r;   cap_path	best_taskbest_specificity	json_filer<   patternsr&   specificitys
             r   estimate_ownerrQ   n   s     $%H?? IHMM-89 (	..&s8}g> 	(G'4(!'l!11'2$ 'I	(( r   c                $   	 t        j                  dddd|g| ddd      }|j                  j                         j	                         D cg c]#  }|j                         s|j                         % }}|r|d   S 	 y	c c}w # t
        $ r Y y	w xY w)
uQ   git diff --stat -- <path> 의 마지막 요약 줄 반환. 실패 시 "modified".gitdiffz--statz--T
   cwdcapture_outputtexttimeoutrE   modified)
subprocessrunstdoutr7   
splitlinesr:   )	repo_rootr   rlliness        r   _git_diff_stat_summaryrd      s    NNFHdD1
 %&HHNN$4$?$?$AOqQWWYOO9   P  s*   A	B A>!A>3B >B 	BBc           
        t        j                  g d| ddd      }|j                  dk7  rg S g }|j                  j	                         D ]  }t        |      dk  r|dd }|dd }d	|v r#|j                  d	      d
   j                         }n&d|v r"|j                  d      d
   j                         }|j                  d      }t        j                  j                  | |      }d}		 t        j                  j                  |      }	|j                         }
|dk(  rd}n|
dv s|dv rd}nt        | |      }d}|rt        ||      }|j!                  t#        |||	||              |S # t        t        f$ r d}	Y vw xY w)u8   git status --porcelain 파싱 → DirtyRecord 리스트.)rS   r   z--porcelainT   rV   r      N   z -> rE   	"z??	untracked)D)z DzD deleted)r   r   r   r   r   )r\   r]   
returncoder^   r_   rJ   r8   r7   r$   r   joingetmtimeOSErrorFileNotFoundErrorrd   rQ   r9   r   )r`   r;   r@   recordsliner   raw_pathr   abs_pathr   status_strippedr   owners                r   collect_dirtyry      s   
 ^^(F A	!#G((* +t9q=bq8 X~~f-b1779HX~~d+B/557H ~~c" 77<<	40"	GG$$X.E
 !,,.T>&L&&L*@$L1)TBL !"4)9:E{%
 	K+Z N3 *+ 	E	s   (E**E>=E>c                  t        |       }|j                  j                  dd       t        j                  t
        j                        j                         }d}|j                  dd      5 }|D ]m  }||||j                  |j                  |j                  |j                  |j                  d}	|j                  t        j                   |	d	      d
z          |dz  }o 	 ddd       |S # 1 sw Y   |S xY w)u   dirty 파일별 1줄의 JSONL 을 registry_path 에 append.

    각 줄: {"ts", "task_id", "phase", "path", "status", "mtime", "diff_summary", "owner_task"}.
    디렉토리 자동 생성. 기록 줄 수 반환.
    T)parentsexist_okr   ar+   )r,   )tsr<   phaser   r   r   r   r   F)ensure_ascii
   N)r   parentmkdirr   nowr   utc	isoformatopenr   r   r   r   r   writer2   dumps)
registry_pathrs   r   r<   reg_pathr~   lines_writtenfrecrows
             r   write_registryr      s     M"HOO$6	hll	#	-	-	/BM	sW	-  	C"** # 0 0!nn	C GGDJJs7$>?QM	  s   /A3C--C7r;   c               ~    t        | |      }t        ||||      }|||D cg c]  }t        |       c}dS c c}w )uD   collect_dirty + write_registry. phase는 "dispatch" 또는 "finish".r   )r   r<   )countr   rs   )ry   r   r   )r`   r   r   r<   r;   rs   r   ra   s           r   snapshot_main_dirtyr   
  sH     I8HIG='PE&'./!F1I/  0s   :c                    g }g }|D ];  t        fd| D              }|r|j                         +|j                         = ||fS )u   dirty_paths 각각을 expected_files 패턴들과 glob_match.

    매칭되면 own, 아니면 unrelated.
    (own_dirty, unrelated_dirty) 반환.
    c              3  6   K   | ]  }t        |        y wN)r)   ).0patdps     r   	<genexpr>z!separate_dirty.<locals>.<genexpr>-  s     Dcjb)Ds   )anyr9   )expected_filesdirty_pathsown	unrelatedmatchedr   s        @r   separate_dirtyr      sV     CI !D^DDJJrNR ! 	>r   c                d    t        | |      \  }}|r
t        ||dS |r
t        g |dS t        g g dS )u&  own/unrelated dirty 분류 후 EXTERNAL_DIRTY_BLOCKER / OWN_DIRTY_FAIL / CLEAN 판정.

    - own이 비어있지 않으면 → OWN_DIRTY_FAIL (task 책임, FAIL 유지)
    - own 비었고 unrelated 있으면 → EXTERNAL_DIRTY_BLOCKER (환경 책임)
    - 둘 다 비었으면 → CLEAN
    )classification	own_dirtyunrelated_dirty)r   r
   r	   r   )r   r   r   r   s       r   classify_blockerr   6  sW     $NK@NC
,(
 	

 4(
 	
   r   c                     t        j                  d      } | j                  ddd       | j                  ddd       | j                  d	dd
       | j                  ddddgd       | j                  dd d       | S )Nz/Snapshot main dirty state and classify blocker.)descriptionz--repo-rootTzgit repo root path)requiredhelpz	--task-idztask IDz
--registryzJSONL registry file pathz--phasedispatchfinishzphase: dispatch or finish)defaultchoicesr   z--capabilities-dirzcapabilities JSON dir)r   r   )argparseArgumentParseradd_argument)ps    r   _build_parserr   Y  s    E	A NN=46JNKNN;IN>NN<$5ONPNN9j:x:P3  5NN'<SNTHr   c                   t               }|j                  |       }t        j                  j	                  |j
                        }t        ||j                  |j                  |j                  |j                        }|d   D cg c]  }|d   	 }}g }|j                  r t        |j                  |j                        }t        ||      }|j                  |j                  ||d}	t        t        j                  |	dd             y c c}w )N)r   r<   r;   rs   r   )r<   r   snapshotblockerrh   F)indentr   )r   
parse_argsr$   r   abspathr`   r   registryr   r<   r;   rC   r   printr2   r   )
argvparserargsr`   r@   ra   r   expectedr   outputs
             r   mainr   f  s    _FT"D/I jj..F '-Y&781V98K8H&t'<'<dllKx5G <<	F 
$**VAE
:; 9s   D	__main__)r&   r   r   r   returnbool)r;   r   r<   r   r   	list[str])r   r   r;   r   r   r   )r`   r   r   r   r   r   r   )r`   r   r;   r   r   list[DirtyRecord])
r   r   rs   r   r   r   r<   r   r   int)r`   r   r   r   r   r   r<   r   r;   r   r   dict)r   r   r   r   r   ztuple[list[str], list[str]])r   r   r   r   r   r   )r   zargparse.ArgumentParser)r   zlist[str] | Noner   None)__doc__
__future__r   r   r#   r2   r$   r\   dataclassesr   r   r   r   pathlibr   r	   r
   r   r   r)   rC   rQ   rd   ry   r   r   r   r   r   r   r   r   r   r   <module>r      s]  
 #    	  ) ' 1 !   !P<<( $(== = =H!!! 	!
 ! 	!\ $( 	
  ! 
, !, 
F
<< zF r   