Ë
    ¥©	jZ  ã                   óH  — d Z ddlZddlmZmZ  ej
                  d«      Z ej
                  d«      Ze G d„ d«      «       Zde	d	efd
„Z
de	d	e	fd„Zde	de	d	efd„Zdede	d	e	dz  fd„Zde	de	d	efd„Z	 d!de	de	dededz  d	edz  f
d„Zddœde	dedede	deded	eee	f   fd „Zy)"aÔ  Reject findings that don't land on code the diff actually changed.

The character searchers ask Granite for `file_path` / `line_start` / `code_snippet`,
but the model routinely fabricates plausible-looking values (e.g.
`frontend/UserDashboard.tsx:123`) when it can't find real issues. This module
parses the unified diff once and exposes a fast deterministic check so the
orchestrator can drop hallucinated findings before they reach the voice
rewriter or the client.
é    N)Ú	dataclassÚfieldz)^diff --git a/(?P<old>.+?) b/(?P<new>.+)$z.^@@ -\d+(?:,\d+)? \+(?P<start>\d+)(?:,\d+)? @@c                   ó°   — e Zd ZU dZ ee¬«      Zeeee	   f   e
d<    ee¬«      Zeeef   e
d<    ee¬«      Zeeee	ef   f   e
d<   edee   fd„«       Zy)	Ú	DiffIndexzAPer-file map of post-image lines the diff added, plus their text.)Údefault_factoryÚadded_linesÚ
added_textÚline_contentÚreturnc                 óH   — t        | j                  j                  «       «      S ©N)Úsetr   Úkeys)Úselfs    ú</var/www/html/prparty/backend/app/services/diff_grounding.pyÚfileszDiffIndex.files   s   € ä�4×#Ñ#×(Ñ(Ó*Ó+Ð+ó    N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   Údictr   Ústrr   ÚintÚ__annotations__r	   r
   Úpropertyr   © r   r   r   r      sx   … áKÙ',¸TÔ'B€K��c˜3˜s™8�mÑ$ÓBÙ!&°tÔ!<€J��S˜#�X‘Ó<ñ /4ÀDÔ.I€L�$�s˜D  c ™NÐ*Ñ+ÓIàð,�s˜3‘xò ,ó ñ,r   r   Údiffr   c                 ó  — t        «       }d}d}| j                  d«      D �]c  }t        j                  |«      }|rp|j	                  d«      }|j
                  j                  |t        «       «       |j                  j                  |d«       |j                  j                  |i «       d}Œ‹|j                  d«      s|j                  d«      rŒ®t        j                  |«      }|rt        |j	                  d«      «      }Œà|�|€Œå|j                  d«      rV|d	d }|j
                  |   j                  |«       |j                  |xx   |dz   z  cc<   ||j                  |   |<   |d	z  }�ŒL|j                  d
«      r�Œ_|d	z  }�Œf |S )zDParse a unified diff and record which (file, line) pairs were added.NÚ
ÚnewÚ z+++z---Ústartú+é   Ú-)r   ÚsplitÚ_FILE_HEADER_REÚmatchÚgroupr   Ú
setdefaultr   r	   r
   Ú
startswithÚ_HUNK_REr   Úadd)r   ÚindexÚcurrent_fileÚnew_line_noÚrawÚm_fileÚm_hunkÚcontents           r   Ú
parse_diffr6   !   st  € ä‹K€EØ#€LØ"€Kà�z‰z˜$Óó  ˆÜ ×&Ñ& sÓ+ˆÙØ!Ÿ<™<¨Ó.ˆLØ×Ñ×(Ñ(¨´s³uÔ=Ø×Ñ×'Ñ'¨°bÔ9Ø×Ñ×)Ñ)¨,¸Ô;ØˆKØà�>‰>˜%Ô  C§N¡N°5Ô$9Øä—‘ Ó$ˆÙÜ˜fŸl™l¨7Ó3Ó4ˆKØàÐ ;Ð#6Øà�>‰>˜#ÔØ˜!˜"�gˆGØ×Ñ˜lÑ+×/Ñ/°Ô<Ø×Ñ˜\Ó*¨g¸©nÑ<Ó*Ø<CˆE×Ñ˜|Ñ,¨[Ñ9Ø˜1ÑŠKØ�^‰^˜CÔ áð ˜1ÑŠKðA ðD €Lr   Úpc                 ó^   — | j                  «       j                  d«      j                  d«      S )Nz./ú/)ÚstripÚlstrip)r7   s    r   Ú_normalize_pathr<   L   s$   € Ø�7‰7‹9×Ñ˜DÓ!×(Ñ(¨Ó-Ð-r   ÚclaimedÚactualc                 óŽ   — t        | «      } t        |«      }| |k(  ry| j                  d|z   «      xs |j                  d| z   «      S )z<Tolerate models that drop or add a leading directory prefix.Tr9   )r<   Úendswith)r=   r>   s     r   Ú_paths_matchrA   P   sH   € ä˜gÓ&€GÜ˜VÓ$€FØ�&ÒØØ×Ñ˜C &™LÓ)ÒK¨V¯_©_¸SÀ7¹]Ó-KÐKr   r/   Úclaimed_pathc                 óL   — |sy| j                   D ]  }t        ||«      sŒ|c S  y)zAReturn the diff's canonical path that the model's path refers to.N)r   rA   )r/   rB   r>   s      r   Úresolve_filerD   Y   s/   € áØØ—+‘+ò ˆÜ˜ fÕ-ØŠMðð r   Úsnippetr	   c                 óî   ‡— | j                  «       sy| j                  «       D �cg c]/  }t        |j                  «       «      dk\  sŒ |j                  «       ‘Œ1 }}|syt        ˆfd„|D «       «      S c c}w )zDAt least one non-trivial snippet line must appear in the added text.Té   c              3   ó&   •K  — | ]  }|‰v –— Œ
 y ­wr   r   )Ú.0Úliner	   s     €r   ú	<genexpr>z#_snippet_matches.<locals>.<genexpr>l   s   øè ø€ Ò; dˆt�zÔ!Ñ;ùs   ƒ)r:   Ú
splitlinesÚlenÚany)rE   r	   rJ   Úneedle_liness    `  r   Ú_snippet_matchesrP   c   sh   ø€ à�=‰=Œ?Øà!(×!3Ñ!3Ó!5öØ¼¸T¿Z¹Z»\Ó9JÈaÓ9Oˆ�
‰
�ð€Lð ñ ØÜÓ;¨lÔ;Ó;Ð;ùòs   ¥!A2ÁA2ÚfileÚ	hint_linec                 ót  ‡— | j                  «       sy|j                  j                  |«      }|syt        d„ d„ | j	                  «       D «       D «       t
        d¬«      }|sy|D ]L  }|j                  «       D ��cg c]  \  }}||v sŒ|‘Œ }	}}|	sŒ-‰�t        |	ˆfd„¬«      c S t        |	«      c S  yc c}}w )uƒ  Return the post-image line number where `snippet`'s strongest needle lives.

    Used to correct the model's frequent Â±1-3 line drift on `line_start`: pick
    the longest line of the snippet, scan `line_content[file]` for an exact
    contains-match, and return the closest match to `hint_line` (the model's
    original guess) so we don't jump if the snippet repeats in the file.
    Nc              3   ó>   K  — | ]  }t        |«      d k\  sŒ|–— Œ y­w)rG   N)rM   )rI   Úss     r   rK   z$find_snippet_line.<locals>.<genexpr>ƒ   s   è ø€ ÒSˆqÄsÈ1ÃvÐQRÃ{ŒÑSùs   ‚–c              3   ó<   K  — | ]  }|j                  «       –— Œ y ­wr   )r:   )rI   rJ   s     r   rK   z$find_snippet_line.<locals>.<genexpr>ƒ   s   è ø€ ÒC d�T—Z‘Z—\ÑCùs   ‚T)ÚkeyÚreversec                 ó    •— t        | ‰z
  «      S r   )Úabs)ÚnrR   s    €r   ú<lambda>z#find_snippet_line.<locals>.<lambda>�   s   ø€ ¬c°!°i±-Ó.@€ r   )rW   )r:   r
   ÚgetÚsortedrL   rM   ÚitemsÚmin)
rE   rQ   r/   rR   Ú
file_linesÚneedlesÚneedler[   r5   Úmatchess
      `      r   Úfind_snippet_linere   o   sÁ   ø€ ð �=‰=Œ?ØØ×#Ñ#×'Ñ'¨Ó-€JÙØäÙSÑC¨g×.@Ñ.@Ó.BÔCÔSÜØô€Gñ
 Øàò ˆØ'1×'7Ñ'7Ó'9×O™˜˜G¸VÀwÒ=N’1ÐOˆÑOÙØØÐ Ü�wÓ$@ÔAÒAÜ�7‹|Òðð ùó Ps   Á:B4ÂB4é   )Ú
line_slackÚ	file_pathÚ
line_startÚline_endÚcode_snippetrg   c                 ól  ‡‡	— t        || «      }|€dd| › �fS |j                  j                  |t        «       «      }|sdd|› �fS t	        d||z
  «      Š	t	        ||«      |z   Št        ˆˆ	fd„|D «       «      sdd|› d|› d|› �fS t        ||j                  j                  |d	«      «      s
dd
|› d|› �fS y)zÃDecide whether a finding lands on code this diff actually touched.

    Returns (grounded, reason). When False, `reason` describes the rejection
    so we can log how the model hallucinated.
    Fzfile_not_in_diff:zfile_has_no_added_lines:r%   c              3   ó>   •K  — | ]  }‰|cxk  xr ‰k  nc –— Œ y ­wr   r   )rI   r[   Ú	span_highÚspan_lows     €€r   rK   zis_grounded.<locals>.<genexpr>¬   s   øè ø€ Ò9¨aˆx˜1Ö) 	×)Ð)Ñ9ùs   ƒzline_not_in_added_range:ú:r&   r"   zsnippet_not_in_diff:)TÚok)rD   r   r]   r   ÚmaxrN   rP   r	   )
rh   ri   rj   rk   r/   rg   r>   Úaddedrn   ro   s
           @@r   Úis_groundedrt   ”   sá   ù€ ô ˜% Ó+€FØ€~ØÐ)¨)¨Ð5Ð5Ð5à×Ñ×!Ñ! &¬#«%Ó0€EÙØÐ0°°Ð9Ð9Ð9ä�1�j :Ñ-Ó.€HÜ�H˜jÓ)¨JÑ6€IÜÔ9°5Ô9Ô9ØÐ0°°¸¸*¸ÀQÀxÀjÐQÐQÐQä˜L¨%×*:Ñ*:×*>Ñ*>¸vÀrÓ*JÔKØÐ,¨V¨H°A°j°\ÐBÐBÐBàr   r   )r   ÚreÚdataclassesr   r   Úcompiler(   r-   r   r   r6   r<   ÚboolrA   rD   rP   r   re   Útuplert   r   r   r   ú<module>rz      sd  ðñó 
ß (ð �"—*‘*ÐIÓJ€Øˆ2�:‰:ÐGÓH€ð ÷,ð ,ó ð,ð(�Sð (˜Yó (ðV.�sð .˜só .ðL˜#ð L sð L¨tó Lð˜	ð °ð ¸¸t¹ó ð	<˜cð 	<¨sð 	<°tó 	<ð  !ñ	"Øð"à
ð"ð ð"ð �T‰zð	"ð
 	ˆ4�Zó"ðX òàðð ðð ð	ð
 ðð ðð ðð ˆ4�ˆ9Ñôr   