
    L0&j             	          d 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mZmZmZ ddlmZ ddlmZ ddlmZmZmZ  e ej                              Z ee          Z ee          Z ej        d	          Z ej        d
          ZdedefdZ dedee         fdZ!dededefdZ"dZ#dede$ee%f         fdZ&dee         de%fdZ'dede%fdZ(e G d d                      Z)e G d d                      Z*e G d d                      Z+e G d d                      Z,e G d d                       Z-e G d! d"                      Z.e G d# d$                      Z/d%ede$eef         fd&Z0 ej        d'          Z1d(ede$ee2ef         dz  fd)Z3 G d* d+e          Z4h d,Z5d-d.d/d0d1d2Z6 e7h d3          Z8d4d5d6d7Z9d8ed%ede%fd9Z:d:ede$e%ef         fd;Z;d:ede$e%ef         fd<Z<d:ede$e%ef         fd=Z=d:ede$e%ef         fd>Z>e>e;e<e<e=d?Z?d@Z@d@ZAdAZBdBZCdCZDdZEdDZFdEedFe2de2fdGZGeCeDfdHedIede$e2e2f         fdJZHeEeFfdHedIede$e2e2f         fdKZI G dL dMe4          ZJdS )NaL  
File Operations Module

Provides file manipulation capabilities (read, write, patch, search) that work
across all terminal backends (local, docker, ssh, singularity, modal, daytona).

The key insight is that all file operations can be expressed as shell commands,
so we wrap the terminal backend's execute() interface to provide a unified file API.

Usage:
    from tools.file_operations import ShellFileOperations
    from tools.terminal_tool import _active_environments
    
    # Get file operations for a terminal environment
    file_ops = ShellFileOperations(terminal_env)
    
    # Read a file
    result = file_ops.read_file("/path/to/file.py")
    
    # Write a file
    result = file_ops.write_file("/path/to/new.py", "print('hello')")
    
    # Search for content
    result = file_ops.search("TODO", path=".", file_glob="*.py")
    N)ABCabstractmethod)	dataclassfield)OptionalListDictAny)Path)BINARY_EXTENSIONS)build_write_denied_pathsbuild_write_denied_prefixesis_write_deniedz!\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)z+'?\x07?__HERMES_FENCE_[A-Za-z0-9]+__\x07?'?textreturnc                 t   | s| S g }|                      d          D ]}d|v pd|v }t                              d|          }t                              d|          }|                    dd          }|r|                    d          dk    rr|                    |           d                    |          S )z;Strip leaked terminal fence wrappers from file read output.Tkeepends__HERMES_FENCE_z] z'
	 )
splitlines_OSC_SEQUENCE_REsub_FENCE_MARKER_REreplacestripappendjoin)r   cleaned_lineslinehad_terminal_wrappercleaneds        :/home/ubuntu/.hermes/hermes-agent/tools/file_operations.py_strip_terminal_fence_leaksr%   ;   s     !M.. & &0D8KGtO"&&r400"&&r733//&"-- 	GMM*$=$=$C$CW%%%%77=!!!    samplec                 :    | sdS | dd         }d|v rdS d|v rdS dS )u"  Return the dominant line ending in ``sample`` or None if undetermined.

    Looks at the first few line breaks and picks ``\r\n`` if any are
    present (Windows / DOS), otherwise ``\n`` (Unix).  Returns ``None``
    for empty / single-line content where we can't tell.  Used to
    preserve the file's original line endings across write_file and
    patch operations — without this the agent's bare-LF tool args
    silently normalize Windows-line-ending files, and patch produces
    mixed endings when only a substituted region changes.
    Ni   

 )r'   heads     r$   _detect_line_endingr-   L   s?      t%4%=D~~vt||t4r&   targetc                     |                      dd                               dd          }|dk    r|S |dk    r|                     dd          S | S )a5  Convert all line endings in ``text`` to ``target`` (``\n`` or ``\r\n``).

    Idempotent: ``_normalize_line_endings(_normalize_line_endings(x, "\r\n"), "\r\n") == _normalize_line_endings(x, "\r\n")``.
    Strips lone ``\r`` characters as well, so mixed-ending content is
    homogenized in a single pass.
    r)   r*   r   )r   r.   lf_normalizeds      r$   _normalize_line_endingsr3   b   s\     LL..66tTBBM~~$$T6222Kr&   u   ﻿c                 ~    | r8|                      t                    r| t          t                    d         dfS | dfS )zReturn (text-without-leading-BOM, had_bom).

    Only a single leading BOM is stripped; a BOM appearing mid-content is
    left alone (it's legitimate data there, not a file marker).
    NTF)
startswith	_UTF8_BOMlenr   s    r$   
_strip_bomr9      sC      +	** +C	NNOO$d**;r&   c                 T    t          |           o|                     t                    S )z)True if ``text`` begins with a UTF-8 BOM.)boolr5   r6   r8   s    r$   _has_bomr<      s    ::4$//)444r&   pathc                      t          |           S )z.Return True if path is on the write deny list.)_shared_is_write_denied)r=   s    r$   _is_write_deniedr@      s    "4(((r&   c                   $   e Zd ZU dZdZeed<   dZeed<   dZ	eed<   dZ
eed<   d	Zee         ed
<   dZeed<   dZeed<   d	Zee         ed<   d	Zee         ed<   d	Zee         ed<   d	Zee         ed<    ee          Zee         ed<   defdZd	S )
ReadResultzResult from reading a file.r   contentr   total_lines	file_sizeF	truncatedNhint	is_binaryis_imagebase64_content	mime_type
dimensionserrordefault_factorysimilar_filesr   c                 H    d | j                                         D             S )Nc                 *    i | ]\  }}||g k    ||S Nr+   .0kvs      r$   
<dictcomp>z&ReadResult.to_dict.<locals>.<dictcomp>   s(    TTTA!-AQSGG1GGGr&   __dict__itemsselfs    r$   to_dictzReadResult.to_dict   s$    TT!4!4!6!6TTTTr&   )__name__
__module____qualname____doc__rC   str__annotations__rD   intrE   rF   r;   rG   r   rH   rI   rJ   rK   rL   rM   r   listrP   r   dictr^   r+   r&   r$   rB   rB      s#        %%GSKIsItD(3-ItHd$(NHSM(((#Ix}### $J$$$E8C=$uT:::M49:::U U U U U U Ur&   rB   c                       e Zd ZU dZdZeed<   dZeed<   dZ	e
eeef                  ed<   dZe
e         ed<   dZe
e         ed	<   dZe
e         ed
<   defdZdS )WriteResultzResult from writing a file.r   bytes_writtenFdirs_createdNlintlsp_diagnosticsrM   warningr   c                 H    d | j                                         D             S )Nc                     i | ]
\  }}|||S rS   r+   rT   s      r$   rX   z'WriteResult.to_dict.<locals>.<dictcomp>   s    HHHA!-1---r&   rY   r\   s    r$   r^   zWriteResult.to_dict   s$    HH!4!4!6!6HHHHr&   )r_   r`   ra   rb   rj   re   rd   rk   r;   rl   r   r	   rc   r
   rm   rM   rn   rg   r^   r+   r&   r$   ri   ri      s         %%M3L$%)D(4S>
"))) &*OXc])))E8C=!GXc]!!!I I I I I I Ir&   ri   c                   $   e Zd ZU dZdZeed<   dZeed<    e	e
          Zee         ed<    e	e
          Zee         ed<    e	e
          Zee         ed	<   d
Zeeeef                  ed<   d
Zee         ed<   d
Zee         ed<   defdZd
S )PatchResultzResult from patching a file.Fsuccessr   diffrN   files_modifiedfiles_createdfiles_deletedNrl   rm   rM   r   c                    d| j         i}| j        r
| j        |d<   | j        r
| j        |d<   | j        r
| j        |d<   | j        r
| j        |d<   | j        r
| j        |d<   | j        r
| j        |d<   | j        r
| j        |d<   |S )	Nrs   rt   ru   rv   rw   rl   rm   rM   )rs   rt   ru   rv   rw   rl   rm   rM   r]   results     r$   r^   zPatchResult.to_dict   s    T\*9 	'!YF6N 	;'+':F#$ 	9&*&8F?# 	9&*&8F?#9 	'!YF6N 	=(,(<F$%: 	)"jF7Or&   )r_   r`   ra   rb   rs   r;   rd   rt   rc   r   rf   ru   r   rv   rw   rl   r   r	   r
   rm   rM   rg   r^   r+   r&   r$   rr   rr      s         &&GTD#NNN %d ; ; ;NDI;;;$uT:::M49:::$uT:::M49:::%)D(4S>
")))%)OXc])))E8C=      r&   rr   c                   @    e Zd ZU dZeed<   eed<   eed<   dZeed<   dS )SearchMatchzA single search match.r=   line_numberrC   g        mtimeN)	r_   r`   ra   rb   rc   rd   re   r~   floatr+   r&   r$   r|   r|      sF           
IIILLLE5r&   r|   c                       e Zd ZU dZ ee          Zee         e	d<    ee          Z
ee         e	d<    ee          Zeeef         e	d<   dZee	d<   dZee	d	<   d
Zee         e	d<   defdZd
S )SearchResultzResult from searching.rN   matchesfilescountsr   total_countFrF   NrM   r   c                     d| j         i}| j        rd | j        D             |d<   | j        r
| j        |d<   | j        r
| j        |d<   | j        rd|d<   | j        r
| j        |d<   |S )	Nr   c                 8    g | ]}|j         |j        |j        d S ))r=   r!   rC   r=   r}   rC   )rU   ms     r$   
<listcomp>z(SearchResult.to_dict.<locals>.<listcomp>   s8     ! ! ! 19MM! ! !r&   r   r   r   TrF   rM   )r   r   r   r   rF   rM   ry   s     r$   r^   zSearchResult.to_dict   s    !12< 	! !! ! !F9 : 	)"jF7O; 	+#{F8> 	'"&F;: 	)"jF7Or&   )r_   r`   ra   rb   r   rf   r   r   r|   rd   r   rc   rg   r   r	   re   r   rF   r;   rM   r   r^   r+   r&   r$   r   r      s           !&t!<!<!<GT+<<<uT222E49222"U4888FDcN888KItE8C=      r&   r   c                   X    e Zd ZU dZdZeed<   dZeed<   dZe	ed<   dZ
e	ed<   d	efd
ZdS )
LintResultzResult from linting a file.Trs   Fskippedr   outputmessager   c                 p    | j         r
d| j        dS | j        rdnd| j        d}| j        r
| j        |d<   |S )Nr   )statusr   okrM   )r   r   r   )r   r   rs   r   ry   s     r$   r^   zLintResult.to_dict  sR    < 	B'DLAAA$(L=DDgUU< 	- $F9r&   N)r_   r`   ra   rb   rs   r;   rd   r   r   rc   r   rg   r^   r+   r&   r$   r   r     sw         %%GTGTFCGS      r&   r   c                   0    e Zd ZU dZdZeed<   dZeed<   dS )ExecuteResultz&Result from executing a shell command.r   stdoutr   	exit_codeN)	r_   r`   ra   rb   r   rc   rd   r   re   r+   r&   r$   r   r     s8         00FCIsr&   r   r   c                    g }g }|                      d          D ]}|                                s|                                }|                    d          s|                    d          r|                    |           k|dk    st
                              |          r|                    |           |                    |           d                    |          d                    |          fS )uC  Separate rg/grep diagnostic lines from real match output.

    ``_exec`` runs commands with ``stderr=subprocess.STDOUT``, so error and
    warning text from ``rg``/``grep`` is interleaved with match lines in a
    single stream. Diagnostics must not be parsed as matches, and on a hard
    failure they are the error message to surface.

    Returns ``(diagnostics, payload)`` where ``payload`` contains only lines
    that look like real search output — a match line (``file:line:content``),
    a files-only path, a count line, or a context line/separator. Everything
    else (tool-prefixed errors, rg's multi-line ``regex parse error`` block
    with its indented carets, blank lines) is folded into ``diagnostics``.

    Classifying by *shape* rather than by error prefix is what lets the
    exit-2 guard distinguish a pure failure (no usable payload → surface the
    error) from a partial failure (some files matched, one was unreadable →
    keep the matches). It also means error text can never be mis-parsed as a
    match, a latent bug that predates the exit-code fix.
    r*   zrg: zgrep: --)splitr   lstripr5   r   _SEARCH_OUTPUT_REmatchr   )r   diagnosticspayloadr!   strippeds        r$   _split_tool_diagnosticsr      s    (  KGT"" % %zz|| 	 ;;==v&& 	(*=*=h*G*G 	t$$$ 4<<,22488<NN4    t$$$$99[!!499W#5#555r&   z0^([A-Za-z]:)?[^\s:][^\n]*?[:\-]\d|^[^\s:][^\s]*$r!   c                    | r| dk    rdS d}t          j        d|           D ]}|}|dS | d|                                         }|sdS |t          |                    d                    | |                                d         fS )a]  Parse grep/rg context output in ``path-line-content`` format.

    Context lines are ambiguous because filenames may legitimately contain
    ``-<digits>-`` segments. Prefer the rightmost numeric separator so a path
    like ``dir/file-12-name.py-8-context`` resolves to
    ``dir/file-12-name.py`` line ``8`` instead of truncating at ``file``.
    r   Nz-(\d+)-   )refinditerstartre   groupend)r!   r   	candidater=   s       r$   _parse_search_context_liner   Y  s      44<<tE[T22  	}tD tU[[^^$$d599;;<<&888r&   c                   z   e Zd ZdZed#dedededefd            Zededefd	            Z	eded
ede
fd            Ze	 d$dededededef
d            Zededefd            Zedede
fd            Zd$dedede
fdZededede
fd            Ze	 	 	 d%dedededee         deded ed!edefd"            ZdS )&FileOperationsz@Abstract interface for file operations across terminal backends.r     r=   offsetlimitr   c                     dS )z$Read a file with pagination support.Nr+   )r]   r=   r   r   s       r$   	read_filezFileOperations.read_filey  	     	r&   c                     dS )a
  Read the complete file content as a plain string.

        No pagination, no line-number prefixes, no per-line truncation.
        Returns ReadResult with .content = full file text, .error set on
        failure. Always reads to EOF regardless of file size.
        Nr+   r]   r=   s     r$   read_file_rawzFileOperations.read_file_raw~  s	     	r&   rC   c                     dS )z8Write content to a file, creating directories as needed.Nr+   )r]   r=   rC   s      r$   
write_filezFileOperations.write_file  r   r&   F
old_string
new_stringreplace_allc                     dS )z,Replace text in a file using fuzzy matching.Nr+   )r]   r=   r   r   r   s        r$   patch_replacezFileOperations.patch_replace  s	     	r&   patch_contentc                     dS )zApply a V4A format patch.Nr+   )r]   r   s     r$   	patch_v4azFileOperations.patch_v4a  r   r&   c                     dS )z>Delete a file. Returns WriteResult with .error set on failure.Nr+   r   s     r$   delete_filezFileOperations.delete_file  r   r&   	recursivec                 P    |rt          d          S |                     |          S )zCross-platform delete that handles files and (with recursive=True)
        directory trees. Default implementation delegates to ``delete_file``
        for the non-recursive case; backends with native recursive support
        should override.
        z1Recursive delete not implemented for this backendrM   )ri   r   r]   r=   r   s      r$   delete_pathzFileOperations.delete_path  s3      	Z%XYYYY%%%r&   srcdstc                     dS )zSMove/rename a file from src to dst. Returns WriteResult with .error set on failure.Nr+   )r]   r   r   s      r$   	move_filezFileOperations.move_file  r   r&   .N2   r   patternr.   	file_globoutput_modecontextc	                     dS )zSearch for content or files.Nr+   )	r]   r   r=   r.   r   r   r   r   r   s	            r$   searchzFileOperations.search  s	    
 	r&   r   r   Fr   rC   Nr   r   rC   r   )r_   r`   ra   rb   r   rc   re   rB   r   r   ri   r   r;   rr   r   r   r   r   r   r   r   r   r+   r&   r$   r   r   v  s?       JJ c 3 3     ^ # *    ^ s S [    ^ */ # 3 C #'4?   ^
 s {    ^      ^& & & & & & & & S s {    ^ BKOP<= c  C "3-7:IL69BN   ^  r&   r   >   .bmp.gif.ico.jpg.png.jpeg.webpz python -m py_compile {file} 2>&1znode --check {file} 2>&1znpx tsc --noEmit {file} 2>&1zgo vet {file} 2>&1zrustfmt --check {file} 2>&1).pyz.js.ts.go.rs>   r   r   r   )z/this is not the tsc command you are looking forz%could not determine executable to runznot found in npm registry)zno input filename givenzerror: not a workspace)zcannot find packagezgo: cannot find main module)npxrustfmtgobase_cmdc                     t                               |           }|sdS |                                t          fd|D                       S )aU  Return True iff ``output`` from ``base_cmd`` indicates the linter
    itself couldn't run (a tooling gap), as opposed to a real lint error
    in the file being checked.

    ``base_cmd`` is the first word of the linter command line (``npx``,
    ``rustfmt``, ``go``, ...).  ``output`` is the stdout/stderr captured
    from running it.
    Fc              3       K   | ]}|v V  	d S rS   r+   )rU   plowers     r$   	<genexpr>z._looks_like_linter_unusable.<locals>.<genexpr>  s'      ,,aqEz,,,,,,r&   )_LINTER_UNUSABLE_PATTERNSgetr   any)r   r   patternsr   s      @r$   _looks_like_linter_unusabler     sU     ),,X66H uLLNNE,,,,8,,,,,,r&   rC   c           	          ddl }	 |                    |            dS # |j        $ r'}dd|j         d|j         d|j         dfcY d}~S d}~wt          $ r%}dt          |          j         d	| fcY d}~S d}~ww xY w)
z;In-process JSON syntax check.  Returns (ok, error_message).r   NTr   FzJSONDecodeError:  (line 	, column ): )	jsonloadsJSONDecodeErrormsglinenocolno	Exceptiontyper_   )rC   _jsones      r$   _lint_json_inprocr     s    1Gx  V V VU!%UUUU17UUUUUUUUUU 1 1 1a)00Q0000000001s,    
A;A	A;	A;A60A;6A;c                     	 ddl }n# t          $ r Y dS w xY w	 |                    |            dS # |j        $ r}dd| fcY d}~S d}~wt          $ r%}dt          |          j         d| fcY d}~S d}~ww xY w)u   In-process YAML syntax check.  Returns (ok, error_message).

    Skipped gracefully if PyYAML isn't installed — YAML parsing is optional.
    r   NT__SKIP__r   FzYAMLError: r   )yamlImportError	safe_load	YAMLErrorr   r   r_   )rC   _yamlr   s      r$   _lint_yaml_inprocr  *  s    
        1   x? ( ( ('A''''''''' 1 1 1a)00Q0000000001s8    
0 
A8A A8A8A3-A83A8c                     	 ddl }n(# t          $ r 	 ddl}n# t          $ r Y Y dS w xY wY nw xY w	 |                    |            dS # t          $ r%}dt          |          j         d| fcY d}~S d}~ww xY w)z<In-process TOML syntax check (stdlib tomllib, Python 3.11+).r   Nr   r   Fr   )tomllibr  tomlir   r   r   r_   )rC   _tomlr   s      r$   _lint_toml_inprocr  =  s    $ $ $ $	$!!!!! 	$ 	$ 	$####	$ "!$1Gx 1 1 1a)00Q0000000001sA    
,,
&,&,,A 
A6A1+A61A6c                 >   ddl }	 |                    |            dS # t          $ rH}|j        rd|j         d|j         dnd}dt          |          j         d	|j         | fcY d}~S d}~wt          $ r%}dt          |          j         d	| fcY d}~S d}~ww xY w)
u   In-process Python syntax check via ast.parse.

    Catches SyntaxError, IndentationError, and everything else the
    ast module rejects — matching py_compile's scope but with no
    subprocess overhead and no dependency on a ``python`` in PATH.
    r   Nr   r   r   r   r   Fr   )	astparseSyntaxErrorr   r   r   r_   r   r   )rC   _astr   locs       r$   _lint_python_inprocr  N  s     1

7x : : ::;(J666186666a)99QU9C999999999 1 1 1a)00Q0000000001s,    
B=A*$B*B7BBB)r   z.jsonz.yamlz.ymlz.tomli  i   r   r   r   valuedefaultc                 T    	 t          |           S # t          t          f$ r |cY S w xY w)z8Best-effort integer coercion for tool pagination inputs.)re   	TypeError
ValueError)r  r  s     r$   _coerce_intr  w  s<    5zzz"   s    ''r   r   c                     ddl m}  |            }t          dt          | t                              }t          |t
                    }t          dt          ||                    }||fS )a  Return safe read_file pagination bounds.

    Tool schemas declare minimum/maximum values, but not every caller or
    provider enforces schemas before dispatch. Clamp here so invalid values
    cannot leak into sed ranges like ``0,-1p``.

    The upper bound on ``limit`` comes from ``tool_output.max_lines`` in
    config.yaml (defaults to the module-level ``MAX_LINES`` constant).
    r   )get_max_linesr   )tools.tool_output_limitsr  maxr  DEFAULT_READ_OFFSETDEFAULT_READ_LIMITmin)r   r   r  	max_linesnormalized_offsetnormalized_limits         r$   normalize_read_paginationr$    st     766666IA{63FGGHH"5*<==1c"2I>>??...r&   c                     t          dt          | t                              }t          dt          |t                              }||fS )zCReturn safe search pagination bounds for shell head/tail pipelines.r   r   )r  r  DEFAULT_SEARCH_OFFSETDEFAULT_SEARCH_LIMIT)r   r   r"  r#  s       r$   normalize_search_paginationr(    sF     A{63HIIJJ1k%1EFFGG...r&   c                      e Zd ZdZdMdefdZ	 	 dNdededededef
d	Zd
ede	fdZ
dMdedede	fdZdede	fdZdOdededefdZdedefdZdedefdZdededdfdZdMdedee         dee         fdZdMdedee         de	fdZdedededefdZdPded!ed"edefd#Zdedefd$Zdedefd%Zdedefd&ZdQded(e	defd)Zded(e	defd*Zd+ed,edefd-Zdededefd.Z	 dQded/ed0ed1e	def
d2Z d3edefd4Z!dMdedee         de"fd5Z#	 dMdedee         d6ee         de"fd7Z$de	fd8Z%d9ede	fd:Z&dede	fd;Z'deddfd<Z(ddd=dedee         d6ee         defd>Z)	 	 	 dRdBededCedDee         d"ed!edEedFede*fdGZ+dBeded"ed!ede*f
dHZ,dBeded"ed!ede*f
dIZ-dBededDee         d"ed!edEedFede*fdJZ.dBededDee         d"ed!edEedFede*fdKZ/dBededDee         d"ed!edEedFede*fdLZ0dS )SShellFileOperationsz
    File operations implemented via shell commands.
    
    Works with ANY terminal backend that has execute(command, cwd) method.
    This includes local, docker, singularity, ssh, modal, and daytona environments.
    Ncwdc                     || _         |p2t          |dd          p!t          t          |dd          dd          pd| _        i | _        dS )u  
        Initialize file operations with a terminal environment.

        Args:
            terminal_env: Any object with execute(command, cwd) method.
                         Returns {"output": str, "returncode": int}
            cwd: Optional explicit fallback cwd when the terminal env has
                 no cwd attribute (rare — most backends track cwd live).

        Note:
            Every _exec() call prefers the LIVE ``terminal_env.cwd`` over
            ``self.cwd`` so ``cd`` commands run via the terminal tool are
            picked up immediately.  ``self.cwd`` is only used as a fallback
            when the env has no cwd at all — it is NOT the authoritative
            cwd, despite being settable at init time.

            Historical bug (fixed): prior versions of this class used the
            init-time cwd for every _exec() call, which caused relative
            paths passed to patch/read/write to target the wrong directory
            after the user ran ``cd`` in the terminal.  Patches would
            claim success and return a plausible diff but land in the
            original directory, producing apparent silent failures.
        r+  Nconfig/)envgetattrr+  _command_cache)r]   terminal_envr+  s      r$   __init__zShellFileOperations.__init__  se    0  
  V',t<< V7<4@@%NNVRU 	 02r&   commandtimeout
stdin_datar   c                     i }|r||d<   |||d<   |pt          | j        dd          p| j        } | j        j        |fd|i|}t	          |                    dd          |                    dd          	          S )
u  Execute command via terminal backend.

        Args:
            stdin_data: If provided, piped to the process's stdin instead of
                        embedding in the command string. Bypasses ARG_MAX.

        Cwd resolution order (critical — see class docstring):
          1. Explicit ``cwd`` arg (if provided)
          2. Live ``self.env.cwd`` (tracks ``cd`` commands run via terminal)
          3. Init-time ``self.cwd`` (fallback when env has no cwd attribute)

        This ordering ensures relative paths in file operations follow the
        terminal's current directory — not the directory this file_ops was
        originally created in.  See test_file_ops_cwd_tracking.py.
        r5  Nr6  r+  r   r   
returncoder   )r   r   )r0  r/  r+  executer   r   )r]   r4  r+  r5  r6  kwargseffective_cwdrz   s           r$   _execzShellFileOperations._exec  s    "  	( 'F9!#-F<  Iwtx==I!!'GG}GGG::h++jjq11
 
 
 	
r&   cmdc                     || j         vr>|                     d| d          }|j                                        dk    | j         |<   | j         |         S )z6Check if a command exists in the environment (cached).zcommand -v z >/dev/null 2>&1 && echo 'yes'yes)r1  r<  r   r   )r]   r=  rz   s      r$   _has_commandz ShellFileOperations._has_command  s[    d)))ZZ Qc Q Q QRRF'-}':':'<'<'ED$"3''r&   r=   content_samplec                    t           j                            |          d                                         }|t          v rdS |rEt          d |dd         D                       }|t          t          |          d          z  dk    S dS )z
        Check if a file is likely binary.
        
        Uses extension check (fast) + content analysis (fallback).
        r   Tc              3   H   K   | ]}t          |          d k     |dvdV  dS )    z
	r   N)ord)rU   cs     r$   r   z8ShellFileOperations._is_likely_binary.<locals>.<genexpr>  sJ        E  Ea"%a&&2++!82C2C !"2C2C2C2C E  Er&   Ni  g333333?F)osr=   splitextr   r   sumr   r7   )r]   r=   rA  extnon_printables        r$   _is_likely_binaryz%ShellFileOperations._is_likely_binary  s     gt$$Q'--//###4  	I  E  E>%4%+@  E  E  E E EM 3s>':':D#A#AADHHur&   c                     t           j                            |          d                                         }|t          v S )z2Check if file is an image we can return as base64.r   )rG  r=   rH  r   IMAGE_EXTENSIONS)r]   r=   rJ  s      r$   	_is_imagezShellFileOperations._is_image  s4    gt$$Q'--//&&&r&   r   rC   
start_linec                    ddl m}  |            }|                    d          }g }t          ||          D ]?\  }}t	          |          |k    r|d|         dz   }|                    | d|            @d                    |          S )uJ  Add line numbers to content in ``LINE_NUM|CONTENT`` format.

        The gutter uses a compact ``<n>|`` prefix (e.g. ``34|foo``) rather
        than a fixed-width zero/space-padded one (``    34|foo``). The
        padding was pure token overhead: on dense source the padded gutter
        cost ~48% more tokens than the bare content and ~16% more than the
        compact form, because the leading spaces + zero-padding tokenize
        into extra tokens on every single line. An A/B (Sonnet 4.6, 2
        passes) showed the compact gutter matches the padded gutter on
        line-reference / patch / value-lookup / structure tasks (4/4 both),
        while dropping line numbers entirely regressed line-referencing
        (the model hand-counted and was off-by-one, 3/4) — so we keep the
        numbers, just not the padding.
        r   )get_max_line_lengthr*   )r   Nz... [truncated]|)r  rR  r   	enumerater7   r   r   )	r]   rC   rP  rR  max_line_lengthlinesnumberedir!   s	            r$   _add_line_numbersz%ShellFileOperations._add_line_numbers  s     	A@@@@@--//d## j999 	+ 	+GAt4yy?**,_,-0AAOOqMM4MM****yy"""r&   c                    |s|S |                     d          r9|                     d          }|j        dk    r|j                                        r|j                                        }|dk    r|S |                     d          r||dd         z   S |dd         }|                    d          }|dk    r
|d|         n|}|rt          j        d|          rt|                     d	|           }|j        dk    rQ|j                                        r8|j                                        }|dt          |          z   d         }	||	z   S |S )
z
        Expand shell-style paths like ~ and ~user to absolute paths.
        
        This must be done BEFORE shell escaping, since ~ doesn't expand
        inside single quotes.
        ~z
echo $HOMEr   z~/r   Nr.  z[a-zA-Z0-9._-]+zecho ~)	r5   r<  r   r   r   findr   	fullmatchr7   )
r]   r=   rz   homerest	slash_idxusernameexpand_result	user_homesuffixs
             r$   _expand_pathz ShellFileOperations._expand_path  sr     	K ??3 	2ZZ--F1$$)<)<)>)>$}**,,3;;K__T** +$qrr(?* ABBx IIcNN	/8A~~4

++4 2-? J J 2 %)JJ/B/B/B$C$CM$.!338L8R8R8T8T3$1$8$>$>$@$@	!%a#h--&7&8&8!9(611r&   argc                 :    d|                     dd          z   dz   S )z/Escape a string for safe use in shell commands.'z'"'"'r1   )r]   rf  s     r$   _escape_shell_argz%ShellFileOperations._escape_shell_argB  s"     S[[i000366r&   r   c                    |                      |          }t          j                            |          pd}|                      |          }|                      d          }d| d| d|z   dz   }|                     ||          S )a"  Write ``content`` to ``path`` atomically via temp-file + rename.

        Streams ``content`` over stdin into a temp file in the SAME
        directory as ``path`` (so the final ``mv`` is a real rename on the
        same filesystem, not a non-atomic cross-device copy), preserves the
        existing file's mode if it exists, then renames over the target.
        On any failure the temp file is removed so we never leak a partial
        ``.hermes-tmp`` file next to the user's data, and the original file
        is left untouched. Content rides stdin so there is no ARG_MAX limit.

        Returns an :class:`ExecuteResult`; ``exit_code == 0`` means the file
        was swapped into place atomically. A non-zero exit means nothing was
        renamed and the original (if any) is intact.
        r   z.hermes-tmp.XXXXXXz
set -e; d=z; t=z; tmp="$(mktemp -p "$d" a   2>/dev/null || mktemp "$d/.hermes-tmp.$$.XXXXXX" 2>/dev/null || { tmp="$d/.hermes-tmp.$$"; : > "$tmp" && echo "$tmp"; })"; [ -n "$tmp" ] || { echo "atomic write: could not create temp file" >&2; exit 1; }; trap 'rm -f "$tmp"' EXIT; if [ -e "$t" ]; then m="$(stat -c%a "$t" 2>/dev/null || stat -f%Lp "$t" 2>/dev/null || true)"; [ -n "$m" ] && chmod "$m" "$tmp" 2>/dev/null || true; fi; cat > "$tmp"; mv -f "$tmp" "$t"; trap - EXIT)r6  )ri  rG  r=   dirnamer<  )r]   r=   rC   q_pathparentq_parenttmplscripts           r$   _atomic_writez!ShellFileOperations._atomic_writeG  s     ''--&&-#))&11 %%&:;;%% %%% % %'+,/ 	" zz&Wz555r&   pre_contentc                     |rt          |          S d|                     |           d}|                     |          }|j        dk    s|j        sdS t          |j                  S )u  Detect the dominant line ending of a file on disk.

        If ``pre_content`` is already available (we just read the file
        for lint/LSP purposes), inspect that — zero extra exec calls.
        Otherwise issue a tiny ``head -c 4096`` to sample the first 4KB.

        Returns ``"\r\n"`` for CRLF (Windows), ``"\n"`` for LF (Unix),
        or ``None`` if undetermined (new file, empty file, single-line
        file with no line break in the first chunk).
        zhead -c 4096  2>/dev/nullr   N)r-   ri  r<  r   r   r]   r=   rr  head_cmdhead_results        r$   _detect_file_line_endingz,ShellFileOperations._detect_file_line_ending}  sx      	4&{333 N4#9#9$#?#?MMMjj** A%%[-?%4";#5666r&   c                     |t          |          S d|                     |           d}|                     |          }|j        dk    s|j        sdS t          |j                  S )aL  Whether the file on disk starts with a UTF-8 BOM.

        Uses ``pre_content`` if we already read the file (zero extra exec
        calls); otherwise issues a tiny ``head -c 3`` to sample just the
        marker. A missing/empty file returns False (new writes get no BOM
        unless the caller explicitly includes one).
        Nz
head -c 3 rt  r   F)r<   ri  r<  r   r   ru  s        r$   _file_has_bomz!ShellFileOperations._file_has_bom  ss     "K(((J 6 6t < <JJJjj** A%%[-?%5*+++r&   old_contentnew_contentfilenamec                     |                     d          }|                     d          }t          j        ||d| d|           }d                    |          S )z2Generate unified diff between old and new content.Tr   za/zb/)fromfiletofiler   )r   difflibunified_diffr   )r]   r{  r|  r}  	old_lines	new_linesrt   s          r$   _unified_diffz!ShellFileOperations._unified_diff  sn    **D*99	**D*99	#y$(__"??
 
 

 wwt}}r&   r   r   r   c           	      D   |                      |          }t          ||          \  }}d|                     |           d}|                     |          }|j        dk    r|                     |          S t          |j                  }	 t          |	                                          }n# t          $ r d}Y nw xY w|t          k    r	 |                     |          rt          dd|d          S d|                     |           d}|                     |          }	t          |	j                  }
|                     ||
          rt          d|d	          S ||z   d
z
  }d| d| d|                     |           }|                     |          }|j        dk    rt          d|j                   S t          |j                  }|d
k    rt          |          \  }}d|                     |           }|                     |          }t          |j                  }	 t          |	                                          }n# t          $ r d}Y nw xY w||k    }d}|rd|d
z    d| d| d| d	}t          |                     ||          ||||          S )a  
        Read a file with pagination, binary detection, and line numbers.
        
        Args:
            path: File path (absolute or relative to cwd)
            offset: Line number to start from (1-indexed, default 1)
            limit: Maximum lines to return (default 500, max 2000)
        
        Returns:
            ReadResult with content, metadata, or error info
        wc -c < rt  r   TzImage file detected. Automatically redirected to vision_analyze tool. Use vision_analyze with this file path to inspect the image contents.)rI   rH   rE   rG   head -c 1000 zUBinary file - cannot display as text. Use appropriate tools to handle this file type.rH   rE   rM   r   zsed -n ',zp' Failed to read file: r   zwc -l < NzUse offset=z to continue reading (showing -z of z lines))rC   rD   rE   rF   rG   )re  r$  ri  r<  r   _suggest_similar_filesr%   r   re   r   r  MAX_FILE_SIZErO  rB   rL  r9   rY  )r]   r=   r   r   stat_cmdstat_resultstat_outputrE   
sample_cmdsample_resultsample_outputend_lineread_cmdread_resultread_output_wc_cmd	wc_result	wc_outputrD   rF   rG   s                         r$   r   zShellFileOperations.read_file  s9      &&1&%@@ Id44T::HHHjj** A%%..t4441+2DEE	K--//00II 	 	 	III	 }$$ >>$ 		#\    PT%;%;D%A%AOOO


:..3M4HII!!$66 	#m    E>A%RfRRxRRD4J4J44P4PRRjj** A%%$PK<N$P$PQQQQ1+2DEE Q;;'44NK ;D22488::JJv&&	/	0@AA		ioo//00KK 	 	 	KKK	  (*	 	yxAxxVxxV^xxdoxxxD**;??#
 
 
 	
s$   !B. .B=<B=*!I IIc                    t           j                            |          pd}t           j                            |          }t           j                            |          d         }t           j                            |          d                                         }|                                }d|                     |           d}|                     |          }g }	|j        dk    r|j	        
                                r|j	        
                                                    d          D ]}
|
s|
                                }d}||k    rd}n:t           j                            |
          d                                         |                                k    rd}n|                    |          s|                    |          rd	}n||v rd
}n||v rt          |          dk    rd}n|rt           j                            |
          d                                         |k    r_t          |          t          |          z  }t          |          t          t          |          t          |                    dz  k    rd}|dk    r5|	                    |t           j                            ||
          f           |	                    d            d |	dd         D             }t'          d| |          S )z;Suggest similar files when the requested file is not found.r   r   r   ls -1 z 2>/dev/null | head -50r*   d   Z   F   <      (   g?   c                     | d          S )Nr   r+   )xs    r$   <lambda>z<ShellFileOperations._suggest_similar_files.<locals>.<lambda>?  s    1Q4% r&   )keyc                     g | ]\  }}|S r+   r+   )rU   r  fps      r$   r   z>ShellFileOperations._suggest_similar_files.<locals>.<listcomp>@  s    ...%!R2...r&   N   zFile not found: )rM   rP   )rG  r=   rk  basenamerH  r   ri  r<  r   r   r   r   r5   r7   setr  r   r   sortrB   )r]   r=   dir_pathr}  basename_no_extrJ  
lower_namels_cmd	ls_resultscoredflfscorecommonsimilars                  r$   r  z*ShellFileOperations._suggest_similar_files  s   7??4((/C7##D))'**844Q7gx((+1133^^%%
 T$00::SSSJJv&&	!##	(8(>(>(@(@#%++--33D99 F F WWYY ##EEW%%a((+11337L7L7N7NNNEE]]:.. #*2G2G2K2K #EE2%%EE:%%#b''A++EE #RW--a00399;;sBB __s2ww6F6{{c#j//3r77&C&Cc&III "199MM5"',,x*C*C"DEEE(((..6"1":...+T++!
 
 
 	
r&   c                 ~   |                      |          }d|                     |           d}|                     |          }|j        dk    r|                     |          S t          |j                  }	 t          |                                          }n# t          $ r d}Y nw xY w| 
                    |          rt          dd|          S |                     d|                     |           d          }t          |j                  }|                     ||          rt          d|d          S |                     d	|                     |                     }|j        dk    rt          d
|j                   S t          t          |j                            \  }	}
t          |	|          S )zRead the complete file content as a plain string.

        No pagination, no line-number prefixes, no per-line truncation.
        Uses cat so the full file is returned regardless of size.
        r  rt  r   T)rI   rH   rE   r  u'   Binary file — cannot display as text.r  cat r  r   )rC   rE   )re  ri  r<  r   r  r%   r   re   r   r  rO  rB   rL  r9   )r]   r=   r  r  r  rE   r  r  
cat_resultraw_contentr  s              r$   r   z!ShellFileOperations.read_file_rawG  s      &&Hd44T::HHHjj** A%%..t4441+2DEE	K--//00II 	 	 	III	>>$ 	RttyQQQQ

#]43I3I$3O3O#]#]#]^^3M4HII!!$66 	)?    ZZ Et'='=d'C'C E EFF
1$$$OJ<M$O$OPPPP $$?
@Q$R$RSSQ
 
 
 	
s   9!B B*)B*c                 0    |                      |d          S )u(  Delete a single file.

        Cross-platform: runs via ``python -c`` against the terminal env's
        Python so it works on Windows shells (``cmd.exe``/PowerShell) that
        don't ship ``rm``. Directories are rejected here — use
        ``delete_path(recursive=True)`` for trees.
        Fr   _python_deleter   s     r$   r   zShellFileOperations.delete_filen  s     ""45"999r&   Fr   c                 0    |                      ||          S )a  Cross-platform delete that handles files and (with recursive=True)
        directory trees. Always preferred over emitting ``rm -rf`` /
        ``Remove-Item -Recurse`` directly so the same tool call works on
        every backend (local / docker / ssh / Windows).
        r  r  r   s      r$   r   zShellFileOperations.delete_pathx  s     ""49"===r&   c                 
   |                      |          }t          |          rt          d| d          S d|dt          |          d}|                     d|                     |                     }|j        dk    r6d	|j        pd
v r+|                     d|                     |                     }|j        dk    r1t          d| d|j        pd
                                pd           S t                      S )NzDelete denied:  is a protected pathr   z-import shutil, pathlib, sys
p = pathlib.Path(z)
recursive = aI  
try:
    if p.is_dir() and not p.is_symlink():
        if recursive:
            shutil.rmtree(p)
        else:
            print('is a directory: ' + str(p), file=sys.stderr); sys.exit(2)
    else:
        p.unlink()
except FileNotFoundError:
    pass
except Exception as exc:
    print(str(exc), file=sys.stderr); sys.exit(1)
zpython3 -c r   python3r   z
python -c zFailed to delete r   zunknown error)	re  r@   ri   r;   r<  ri  r   r   r   )r]   r=   r   snippetrz   s        r$   r  z"ShellFileOperations._python_delete  s?     &&D!! 	S%Qt%Q%Q%QRRRRB $B B	??B B B 	, K$*@*@*I*IKKLL q  Y6=3FB%G%GZZ NT-C-CG-L-L N NOOFq  %s%s%s&-BUSUA\A\A^A^Aqbq%s%stttt}}r&   r   r   c                    |                      |          }|                      |          }||fD ]'}t          |          rt          d| d          c S (|                     d|                     |           d|                     |                     }|j        dk    rt          d| d| d	|j                   S t                      S )
zMove a file via mv.zMove denied: r  r   zmv  r   zFailed to move z -> r   )re  r@   ri   r<  ri  r   r   )r]   r   r   r   rz   s        r$   r   zShellFileOperations.move_file  s    $$$$s 	R 	RA"" R")P)P)P)PQQQQQQRM$((--MM0F0Fs0K0KMM
 
 q  %Vs%V%V%V%Vv}%V%VWWWW}}r&   c                    |                      |          }t          |          rt          d| d          S t          j                            |          d                                         }d}|t          v p|                     |          }|rGd| 	                    |           d}| 
                    |          }|j        dk    r|j        r|j        }|                     ||          }|d	k    rt          |d	          }|                     ||          rt!          |          s
t"          |z   }|                     |           t          j                            |          }	d
}
|	r:d| 	                    |	           }| 
                    |          }|j        dk    rd}
|                     ||          }|j        dk    rt          d|j                   S d| 	                    |           d}| 
                    |          }	 t+          |j                                                  }n2# t.          $ r% t1          |                    d                    }Y nw xY w|                     |||          }d}|j        s|j        r|                     |||          }|r|}t          ||
|r|                                nd|          S )uO  
        Write content to a file, creating parent directories as needed.

        Pipes content through stdin to avoid OS ARG_MAX limits on large
        files. The content never appears in the shell command string —
        only the file path does.

        After the write, runs a post-first / pre-lazy lint check via
        ``_check_lint_delta()``.  If the new content is clean, the lint
        call is O(one parse).  If the new content has errors, the pre-write
        content is linted too and only errors newly introduced by this
        write are surfaced — pre-existing problems are filtered out so
        the agent isn't distracted chasing them.

        Args:
            path: File path to write
            content: Content to write

        Returns:
            WriteResult with bytes written, lint summary, or error.
        Write denied: '(' is a protected system/credential file.r   r   Nr  rt  r   r)   Fz	mkdir -p TzFailed to write file: r  zutf-8rr  post_content)rj   rk   rl   rm   )re  r@   ri   rG  r=   rH  r   LINTERS_INPROC_lsp_handles_extensionri  r<  r   r   rx  r3   rz  r<   r6   _snapshot_lsp_baselinerk  rq  re   r   r  r7   encode_check_lint_deltars   r   _maybe_lsp_diagnosticsr^   )r]   r=   rC   rJ  rr  want_prer  r  original_endingrm  rk   	mkdir_cmdmkdir_resultwrite_resultr  r  rj   lint_resultrm   blocks                       r$   r   zShellFileOperations.write_file  s5   .   && D!! 	g%et%e%e%effff" gt$$Q'--//%).(LD,G,G,L,L 	1
 Id44T::HHHH**X..K$))k.@))0 77kJJf$$-gv>>G dK00 	*'9J9J 	*')G 	##D))) && 	$DD$:$:6$B$BDDI::i00L%**#" ))$88!Q&&%Sl>Q%S%STTTT Id44T::HHHjj**	9 2 8 8 : :;;MM 	9 	9 	9w 7 788MMM	9 ,,T{Y`,aa *. 	(+"5 	(//+G 0  E  ("''%*5?$$&&&4+	
 
 
 	
s   &H5 5,I$#I$r   r   r   c           
      J   |                      |          }t          |          rt          d| d          S d|                     |           d}|                     |          }|j        dk    rt          d|           S |j        }t          |          \  }}ddlm	}	  |	||||          \  }
}}}|s|dk    r@|pd	| }	 dd
lm
} | |||||          z  }n# t          $ r Y nw xY wt          |          S t          |          }|rt          |
|          }
|                     ||
          }|j        rt          d|j                   S d|                     |           d}|                     |          }|j        dk    rt          d|           S t          |j                  \  }}|                    dd                              dd          }|
                    dd                              dd          }||k    r4t          d| dt#          |           dt#          |           d          S |                     ||
|          }|                     |||
          }t          d||g|r|                                nd|j                  S )ai  
        Replace text in a file using fuzzy matching.

        Args:
            path: File path to modify
            old_string: Text to find (must be unique unless replace_all=True)
            new_string: Replacement text
            replace_all: If True, replace all occurrences

        Returns:
            PatchResult with diff and lint results
        r  r  r   r  rt  r   r  )fuzzy_find_and_replacez'Could not find match for old_string in )format_no_match_hintzFailed to write changes: z2Post-write verification failed: could not re-read r)   r*   r0   z#Post-write verification failed for z5: on-disk content differs from intended write (wrote z chars, read back zb chars after normalizing line endings). The patch did not persist. Re-read the file and try again.r  TN)rs   rt   ru   rl   rm   )re  r@   rr   ri  r<  r   r   r9   tools.fuzzy_matchr  r  r   r-   r3   r   rM   r   r7   r  r  r^   rm   )r]   r=   r   r   r   r  r  rC   r  r  r|  match_count	_strategyrM   err_msgr  file_endingr  
verify_cmdverify_result_verify_bomless_verify_stdout_normalized_new_content_normalizedrt   r  s                            r$   r   z!ShellFileOperations.patch_replaceW  s      && D!! 	g%et%e%e%effff E$0066DDDjj** A%%%CT%C%CDDDD$  ((
 	=<<<<<5K5KZ[6
 6
2[)U  	.K1$$OOOOGBBBBBB//jRYZZZ   W---- *'22 	L1+{KKK t[99 	W%UAS%U%UVVVV GD22488FFF


:.."a''%`Z^%`%`aaaa ((<==$3$;$;FD$I$I$Q$QRVX\$]$]!"-"5"5fd"C"C"K"KDRV"W"W$(???Md M M566M M 011M M M    !!';==
 ,,TwU`,aa 6*5?$$&&&4 )8
 
 
 	
s   C 
C,+C,r   c                 t    ddl m}m}  ||          \  }}|rt          d|           S  |||           }|S )a  
        Apply a V4A format patch.
        
        V4A format:
            *** Begin Patch
            *** Update File: path/to/file.py
            @@ context hint @@
             context line
            -removed line
            +added line
            *** End Patch
        
        Args:
            patch_content: V4A format patch string
        
        Returns:
            PatchResult with changes made
        r   )parse_v4a_patchapply_v4a_operationszFailed to parse patch: r   )tools.patch_parserr  r  rr   )r]   r   r  r  
operationsparse_errorrz   s          r$   r   zShellFileOperations.patch_v4a  sm    ( 	MLLLLLLL"1/-"@"@
K 	N%L{%L%LMMMM &%j$77r&   c                 `   t           j                            |          d                                         }t                              |          }||Ud|                     |           d}|                     |          }|j        dk    rt          dd| d	          S |j
        } ||          \  }}|d
k    rt          dd| d	          S t          ||rdn|          S |t          vrt          dd| d	          S |t          v r*|                     |          rt          dd| d	          S t          |         }	|	                                d         }
|                     |
          st          d|
 d	          S |	                    d|                     |                    }|                     |d          }|j        dk    rt#          |
|j
                  rzddlm}  ||j
                                                  }t+          d |                                D             |dd                   }t          d|
 d|dd          	          S t          |j        dk    |j
                                        r|j
                                        nd          S )u8  
        Run syntax check on a file after editing.

        Prefers the in-process linter for structured formats (JSON, YAML,
        TOML) when possible — those parse via the Python stdlib in
        microseconds and don't require a subprocess.  Falls back to the
        shell linter table for compiled/type-checked languages
        (py_compile, node --check, tsc, go vet, rustfmt).

        Args:
            path: File path (used to select the linter + for shell invocation).
            content: Optional file content.  If provided AND an in-process
                     linter matches the extension, we lint the content
                     directly without re-reading the file from disk.  Ignored
                     for shell linters.

        Returns:
            LintResult with status and any errors.
        r   Nr  rt  r   TzFailed to read z	 for lint)r   r   r  zNo linter available for z (missing dependency)r   rs   r   zNo linter for z fileszLSP server handles u    — shell linter skippedz not availablez{file}r  r5  )
strip_ansic              3   f   K   | ],}|                                 |                                 V  -d S rS   r   rU   lns     r$   r   z2ShellFileOperations._check_lint.<locals>.<genexpr>;  s7      IIbhhjjIIIIIIIr&   x   z not usable:    )rG  r=   rH  r   r  r   ri  r<  r   r   r   LINTERS_SHELL_LINTER_LSP_REDUNDANT_lsp_will_handler   r@  r   r   tools.ansi_stripr  r   nextr   )r]   r=   rC   rJ  inprocr  r  r   err
linter_cmdr   r=  rz   r  r#   
first_lines                   r$   _check_lintzShellFileOperations._check_lint  s   ( gt$$Q'--//  ##C((L$"8"8">">LLL"jj22(A--%d<]d<]<]<]^^^^%,fWooGBj  !$8mSV8m8m8mnnnnbr1BsCCCC gd4PS4P4P4PQQQQ ---$2G2G2M2M-LcLLL   
 S\
##%%a(  ** 	Qdx4O4O4OPPPP   4+A+A$+G+GHHC,,q  %@6=%Y%Y  433333 j//5577GIIg&8&8&:&:III J #DD*TcT2BDD   
 $),2M,?,?,A,AI6=&&(((r
 
 
 	
r&   r  c                    |                      ||          }|j        s|j        r|S ||S |                      ||          }|j        s|j        s|j        s|S d |j                                        D             fd|j                                        D             }|st          d|j        d          S t          ddd	                    |          z   
          S )u\  
        Run post-write syntax lint with pre-write baseline comparison.

        Two-tier strategy:

        1. **Syntax check** (in-process or shell-based, microseconds).
           Catches the bug class that motivated this layer: corrupt
           writes, mashed quotes, truncated output.  Hot path.

        2. **Delta refinement against pre-write content** when the
           syntax tier reports errors.  Filter out errors that already
           existed pre-edit so the agent isn't distracted by inherited
           state.

        Semantic diagnostics from the LSP layer are fetched separately
        via :meth:`_maybe_lsp_diagnostics` and surfaced in the
        ``lsp_diagnostics`` field on :class:`WriteResult` /
        :class:`PatchResult`.  Keeping the two channels separate lets
        the agent (and any downstream parsers) read syntax errors and
        semantic errors as independent signals.

        Args:
            path: File path (for linter selection).
            pre_content: File content BEFORE the write.  Pass None for new
                         files or when the pre-state isn't available — the
                         delta refinement is skipped and all post errors
                         are returned.
            post_content: File content AFTER the write.  Optional; if None,
                          the shell linter reads from disk (same as
                          _check_lint).

        Returns:
            LintResult.  ``output`` contains either the full post-lint
            errors (no pre-state) or just the new-error lines (delta
            refinement applied).
        )rC   Nc                 ^    h | ]*}|                                 |                                 +S r+   r  r  s     r$   	<setcomp>z8ShellFileOperations._check_lint_delta.<locals>.<setcomp>  s-    PPPBRXXZZPRXXZZPPPr&   c                 h    g | ].}|                                 |                                 v,|/S r+   r  )rU   r  	pre_liness     r$   r   z9ShellFileOperations._check_lint_delta.<locals>.<listcomp>  s9    jjjRrxxzzjbhhjj`iNiNibNiNiNir&   Fu^   Pre-existing lint errors — this edit didn't introduce new ones but the file is still broken.)rs   r   r   zLNew lint errors introduced by this edit (pre-existing errors filtered out):
r*   r  )r  rs   r   r   r   r   r   )r]   r=   rr  r  postpre
post_linesr  s          @r$   r  z%ShellFileOperations._check_lint_deltaH  s-   L l;; < 	4< 	K Kt[99; 	#+ 	SZ 	 K QP#**?*?*A*APPP	jjjj4;#9#9#;#;jjj
 	 {x    8:>))J:O:OP
 
 
 	
r&   c                 ~    t          | dd          }|dS 	 ddlm} n# t          $ r Y dS w xY wt	          ||          S )u}  Return True iff this FileOperations is wired to a local backend.

        LSP servers run on the host process — they need access to the
        files they're linting.  Remote/sandboxed backends (Docker,
        Modal, SSH, Daytona) keep files inside the sandbox where the
        host-side LSP server can't reach them, so we skip the LSP
        path for those entirely.
        r/  NFr   )LocalEnvironment)r0  tools.environments.localr  r   
isinstance)r]   r/  r  s      r$   _lsp_local_onlyz#ShellFileOperations._lsp_local_only  sn     dE4((; 5	AAAAAAA 	 	 	55	#/000s    
,,rJ  c                     |sdS 	 ddl m} n# t          $ r Y dS w xY w|                                }|D ]}||j        v r dS dS )u  Return True iff some registered LSP server claims this extension.

        Used to decide whether to capture pre-write content for the
        line-shift map.  Capturing is cheap (one ``cat`` on the host)
        but pointless if no LSP would ever look at the file.

        Safe to call on remote backends — the registry is purely
        in-process metadata; we still gate the actual LSP path on
        :meth:`_lsp_local_only`.
        Fr   )SERVERST)agent.lsp.serversr  r   r   
extensions)r]   rJ  r  	ext_lowersrvs        r$   r  z*ShellFileOperations._lsp_handles_extension  s      	5	1111111 	 	 	55	IIKK	 	 	CCN**tt +us    
c                 
   |                                  sdS 	 ddlm} n# t          $ r Y dS w xY w	  |            }n# t          $ r Y dS w xY w|dS 	 t	          |                    |                    S # t          $ r Y dS w xY w)u!  Return True iff the LSP service is active AND will lint this file.

        Stronger than :meth:`_lsp_handles_extension` — that one only checks
        the static server registry.  This one additionally requires the
        LSP service to be configured/enabled and the file to pass
        :meth:`agent.lsp.manager.LSPService.enabled_for` (which gates on
        workspace detection, disabled-server set, and the broken-pair
        short-circuit).

        Used by :meth:`_check_lint` to decide whether to skip the per-file
        shell linter for extensions in ``_SHELL_LINTER_LSP_REDUNDANT``.

        Best-effort: any failure path returns False so the shell linter
        runs as before — never suppress lint based on an LSP probe that
        couldn't actually answer the question.
        Fr   get_service)r  	agent.lspr  r   r;   enabled_forr]   r=   r  svcs       r$   r  z$ShellFileOperations._lsp_will_handle  s    " ##%% 	5	------- 	 	 	55		+--CC 	 	 	55	;5	--... 	 	 	55	s-    
--
< 
A
	A
!A4 4
BBc                     |                                  sdS 	 ddlm}  |            }n# t          $ r Y dS w xY w|dS 	 |                    |           dS # t          $ r Y dS w xY w)uL  Capture pre-edit LSP diagnostics so the post-write delta is correct.

        Best-effort.  Silent on every failure path — LSP is an
        enrichment layer and must never break a write.

        Skipped entirely on non-local backends (Docker, Modal, SSH,
        etc.) — the server can't see files inside the sandbox.
        Nr   r  )r  r  r  r   snapshot_baseliner  s       r$   r  z*ShellFileOperations._snapshot_lsp_baseline  s     ##%% 	F	------+--CC 	 	 	FF	;F	!!$''''' 	 	 	DD	s   ) 
77A 
A$#A$r  c                   |                                  sdS 	 ddlm} n# t          $ r Y dS w xY w	  |            }n# t          $ r Y dS w xY w||                    |          sdS d}|.|,||k    r&	 ddlm}  |||          }n# t          $ r d}Y nw xY w	 |                    |d|          }n# t          $ r Y dS w xY w|sdS 	 ddlm	}	m
}
  |	||          }|sdS  |
d	|z             S # t          $ r Y dS w xY w)
u  Best-effort LSP semantic diagnostics for ``path``.

        Returns a formatted ``<diagnostics>`` block, or empty string
        when LSP is unavailable / disabled / produced no errors.

        When both ``pre_content`` and ``post_content`` are provided,
        a line-shift map is built and passed to the LSPService so
        baseline diagnostics are remapped into post-edit coordinates
        before the set-difference.  Without this, edits that delete
        or insert lines surface every pre-existing diagnostic below
        the edit point as "introduced by this edit".

        Wraps everything in a try/except so a misbehaving LSP server
        can't break a write.  This intentionally swallows all errors
        — the calling tier already returned a clean syntax result, so
        ``""`` here just means "no extra info to add".

        Skipped entirely on non-local backends (Docker, Modal, SSH,
        etc.) — same reasoning as ``_snapshot_lsp_baseline``.
        r   r   r  N)build_line_shiftT)delta
line_shift)report_for_filetruncatez)LSP diagnostics introduced by this edit:
)r  r  r  r   r  agent.lsp.range_shiftr   get_diagnostics_syncagent.lsp.reporterr#  r$  )r]   r=   rr  r  r  r  r"  r   r   r#  r$  r  s               r$   r  z*ShellFileOperations._maybe_lsp_diagnostics  s   6 ##%% 	2	------- 	 	 	22		+--CC 	 	 	22	;cood33;2
 
"|'?KS_D_D_"BBBBBB--k<HH

 " " "!


"	224tPZ2[[KK 	 	 	22	 	2	DDDDDDDD#OD+66E r8H5PQQQ 	 	 	22	sW    
--
< 
A
	A
3B BBB2 2
C ?C C.  C. .
C<;C<r   r   r   r   r.   r   r   r   c	           	         t          ||          \  }}|                     |          }|                     d|                     |           d          }	d|	j        v rt
          j                            |          pd}
t
          j                            |          }d| g}|                     d|                     |
           d          }d|j        v r=|r:|                     d	|                     |
           d
          }|j	        dk    r|j        
                                r|                                }g }|j        
                                                    d          D ]q}|s|                                }||v s!||v s|                    |dd                   r3|                    t
          j                            |
|                     r|r3|                    dd                    |dd                   z              t!          d                    |          d          S |dk    r|                     ||||          S |                     |||||||          S )a\  
        Search for content or files.
        
        Args:
            pattern: Regex (for content) or glob pattern (for files)
            path: Directory/file to search (default: cwd)
            target: "content" (grep) or "files" (glob)
            file_glob: File pattern filter for content search (e.g., "*.py")
            limit: Max results (default 50)
            offset: Skip first N results
            output_mode: "content", "files_only", or "count"
            context: Lines of context around matches
        
        Returns:
            SearchResult with matches or file list
        ztest -e z! && echo exists || echo not_found	not_foundr   zPath not found: ztest -d z && echo yes || echo nor?  r  z 2>/dev/null | head -20r   r*   N   zSimilar paths: z, r  z. rM   r   r   )r(  re  r<  ri  r   rG  r=   rk  r  r   r   r   r   r5   r   r   r   _search_files_search_content)r]   r   r=   r.   r   r   r   r   r   checkrm  basename_query
hint_partsparent_checkr  lower_q
candidatesentryles                      r$   r   zShellFileOperations.searchH  s   & 4FEBB   && 

ed&<&<T&B&Beeeff%,&&W__T**1cFW--d33N3T334J::R411&99RRR L ++++ JJTT33F;;TTT 	 &!++	0@0F0F0H0H+,2244G!#J!*!1!7!7!9!9!?!?!E!E K K$ %$"[[]]"b==B'MMR]]7SUTUSU;=W=WM&--bgll65.I.IJJJ! "))-		*RaR.0I0II    ii
++   
 W%%gtUFCCC''y%(3W> > >r&   c                 (   |                     d          sd|vr|}n|                    d          d         }t          |          }t          d |j        D                       }|                     d          r|                     ||||          S |                     d          st          d          S |sd	nd
}|rd| nd
}	d
}
|sd|dz    d| }
d|                     |           |	 d|                     |           d|
 }| 	                    |d          }|j
                                        sJd|                     |           |	 d|                     |           d|
 }| 	                    |d          }g }|j
                                                            d          D ]}|s|                    dd          }t          |          dk    rJ|d                             dd
                                          r|                    |d                    x|                    |           |r|                                }g }|D ]}	 t          |                                                              |          j        }n$# t$          $ r t          |          j        }Y nw xY wt          d |D                       r{|                    |           ||||z            }t          |t          |                    S )z-Search for files by name pattern (glob-like).z**/r.  c              3   H   K   | ]}|d vo|                     d          V  dS >   ..r   r   Nr5   rU   parts     r$   r   z4ShellFileOperations._search_files.<locals>.<genexpr>  sL       '
 '
 #<(<(<'
 '
 '
 '
 '
 '
r&   rgr\  zFile search requires 'rg' (ripgrep) or 'find'. Install ripgrep for best results: https://github.com/BurntSushi/ripgrep#installationr   z-not -path '*/.*'r   r  z | tail -n +r   z | head -n zfind z -type f -name z* -printf '%T@ %p\n' 2>/dev/null | sort -rnr  r  z 2>/dev/null | sort -rnr*   r  r   r   c              3   H   K   | ]}|d vo|                     d          V  dS r9  r;  r<  s     r$   r   z4ShellFileOperations._search_files.<locals>.<genexpr>  s8      ^^Dt;.G4??33G3G^^^^^^r&   r   r   )r5   r   r   r   partsr@  _search_files_rgr   ri  r<  r   r   r7   r   isdigitr   resolverelative_tor  )r]   r   r=   r   r   search_patternsearch_roothas_hidden_path_ancestorhidden_excludehidden_filter_exprpagination_exprr=  rz   
cmd_simpler   r!   rA  normalized_rootfiltered_files	file_path	rel_partss                        r$   r,  z!ShellFileOperations._search_files  s    !!%(( 	4S-?-?$NN$]]3//3N4jj#& '
 '
#)'
 '
 '
 $
 $
  T"" 	N((ufMMM   (( 	K    5MT,,RT5CK1111
 ' 	LKVaZKKEKKOMd,,T22 M4F M MW[WmWmn|W}W} M M;JM M C,,}""$$ 	8C!7!7!=!= C?Q C Cbfbxbx  zH  cI  cI C C1@C CJZZ
BZ77FM''))//55 	# 	#D JJsA&&E5zzQ58#3#3C#<#<#D#D#F#FU1X&&&&T""""
 $ 	:)1133ON" 1 1	6 $Y 7 7 9 9 E Eo V V \II! 6 6 6 $Y 5III6^^T]^^^^^ %%i0000"6&5.#89E E


 
 
 	
s   9JJ65J6c                    d|vr|                     d          sd| }n|}||z   }d|                     |           d|                     |           d| }|                     |d          }d |j                                                            d	          D             }	|	s~d
|                     |           d|                     |           d| }
|                     |
d          }d |j                                                            d	          D             }	|	|||z            }t          |t          |	          t          |	          |k              S )ad  Search for files by name using ripgrep's --files mode.

        rg --files respects .gitignore and excludes hidden directories by
        default, and uses parallel directory traversal for ~200x speedup
        over find on wide trees.  Results are sorted by modification time
        (most recently edited first) when rg >= 13.0 supports --sortr.
        r.  *zrg --files --sortr=modified -g r  z 2>/dev/null | head -n r  r  c                     g | ]}||S r+   r+   rU   r  s     r$   r   z8ShellFileOperations._search_files_rg.<locals>.<listcomp>  s    GGG1QGQGGGr&   r*   zrg --files -g c                     g | ]}||S r+   r+   rT  s     r$   r   z8ShellFileOperations._search_files_rg.<locals>.<listcomp>  s    KKKqKKKKr&   )r   r   rF   )r5   ri  r<  r   r   r   r   r7   )r]   r   r=   r   r   glob_patternfetch_limit
cmd_sortedrz   	all_files	cmd_plainpages               r$   rB  z$ShellFileOperations._search_files_rg  s    gg&8&8&=&=(w==LL"Lfn'd.D.D\.R.R ' '%%d++' '$' ' 	
 J33GG 3 3 5 5 ; ;D A AGGG	 	L+!7!7!E!E + +))$//+ +(+ + 
 ZZ	2Z66FKKFM$7$7$9$9$?$?$E$EKKKI./I)nn3
 
 
 	
r&   c           	          |                      d          r|                     |||||||          S |                      d          r|                     |||||||          S t          d          S )z,Search for content inside files (grep-like).r>  grepzqContent search requires ripgrep (rg) or grep. Install ripgrep: https://github.com/BurntSushi/ripgrep#installationr   )r@  _search_with_rg_search_with_grepr   )r]   r   r=   r   r   r   r   r   s           r$   r-  z#ShellFileOperations._search_content  s     T"" 	''y%(3W> > >v&& 	))'4E6*5w@ @ @  \   r&   c                    g d}|dk    r$|                     dt          |          g           |r*|                     d|                     |          g           |dk    r|                    d           n|dk    r|                    d           |                    |                     |                     |                    |                     |                     |dk    r||z   d	z   n||z   }	|                     d
ddt          |	          g           dd                    |          z   }
|                     |
d          }t          |j                  \  }}|j        dk    rW|	                                sC|	                                p|j        	                                pd}t          d| d          S |}|dk    r^d |	                                                    d          D             }t          |          }||||z            }t          ||          S |dk    ri }|	                                                    d          D ]_}d|v rY|                    dd          }t          |          dk    r0	 t          |d                   ||d         <   O# t          $ r Y [w xY w`t          |t!          |                                                    S t%          j        d          }g }|	                                                    d          D ]}|r|dk    r|                    |          }|r|                    t+          |                    d          pd|                    d          z   t          |                    d                    |                    d          d d!         "                     |dk    rPt/          |          }|r?|                    t+          |d         |d         |d         d d!         "                     t          |          }||||z            }t          |||||z   k    #          S )$zSearch using ripgrep.)r>  z--line-numberz--no-headingz--with-filenamer   -Cz--glob
files_only-lcount-cr  rS  r,   -nset -o pipefail; r  r  r  r  Search errorSearch failed: r+  c                     g | ]}||S r+   r+   rT  s     r$   r   z7ShellFileOperations._search_with_rg.<locals>.<listcomp>L      DDDq!DDDDr&   r*   r@  :r   r   r   ^([A-Za-z]:)?(.*?):(\d+):(.*)$r   r   r*     Nr   r   r   r   rF   )extendrc   ri  r   r   r<  r   r   r   r   r   r   r7   rsplitre   r  rI  valuesr   compiler   r|   r   r   r]   r   r=   r   r   r   r   r   	cmd_partsrW  r=  rz   r   r   	error_msgr   rY  totalr[  r   r!   rA  	_match_rer   r   parseds                             r$   r^  z#ShellFileOperations._search_with_rg  s    ONN	 Q;;dCLL1222  	Lh(>(>y(I(IJKKK ,&&T""""G##T""" 	//88999//55666
 /6kkefns**uv~#vtS-=-=>??? "CHHY$7$77C,,  7v}EEW q   #))++Vv}/B/B/D/DVI&C	&C&CQRSSSS ,&&DDFLLNN$8$8$>$>DDDI	NNEVFUN23Dd>>>>G##F,,T22 ! !$;; KKQ//E5zzQ!/258}}F58,,) ! ! ! D!v3v}};O;OPPPP 
#DEEIG,,T22   tt|| OOD)) NN;ggajj.B!''!**<$'

OO !

4C4 0$ $ $   
  Q;;7==F {!'(.q	$*1IdsdO( ( (    LLE6&5.01D!&5.0   s   J''
J43J4c                    ddg}|                     d           |dk    r$|                    dt          |          g           |r*|                    d|                     |          g           |dk    r|                     d           n|d	k    r|                     d
           |                     |                     |                     |                     |                     |                     ||z   |dk    rdndz   }	|                    dddt          |	          g           dd                    |          z   }
|                     |
d          }t          |j                  \  }}|j        dk    rW|	                                sC|	                                p|j        	                                pd}t          d| d          S |}|dk    r^d |	                                                    d          D             }t          |          }||||z            }t          ||          S |d	k    ri }|	                                                    d          D ]_}d|v rY|                    dd          }t          |          dk    r0	 t          |d                   ||d         <   O# t          $ r Y [w xY w`t          |t!          |                                                    S t%          j        d          }g }|	                                                    d          D ]}|r|dk    r|                    |          }|r|                     t+          |                    d          pd|                    d          z   t          |                    d                     |                    d!          d"d#         $                     |dk    rPt/          |          }|r?|                     t+          |d         |d         |d         d"d#         $                     t          |          }||||z            }t          |||||z   k    %          S )&zFallback search using grep.r]  z-rnHz--exclude-dir='.*'r   ra  z	--includerb  rc  rd  re  r  rS  r,   rf  rg  r  r  r  r  rh  ri  r+  c                     g | ]}||S r+   r+   rT  s     r$   r   z9ShellFileOperations._search_with_grep.<locals>.<listcomp>  rk  r&   r*   r@  rl  r   rm  rn  r   r   r*  ro  Nr   r   rp  )r   rq  rc   ri  r   r<  r   r   r   r   r   r   r7   rr  re   r  rI  rs  r   rt  r   r|   r   r   ru  s                             r$   r_  z%ShellFileOperations._search_with_grep  s    V$	 	-... Q;;dCLL1222  	Ok4+A+A)+L+LMNNN ,&&T""""G##T""" 	//88999//55666 fnw{{B#vtS-=-=>??? "CHHY$7$77C,,  7v}EEW q   #))++Vv}/B/B/D/DVI&C	&C&CQRSSSS,&&DDFLLNN$8$8$>$>DDDI	NNEVFUN23Dd>>>>G##F,,T22 ! !$;; KKQ//E5zzQ!/258}}F58,,) ! ! ! D!v3v}};O;OPPPP 
#DEEIG,,T22   tt||OOD)) NN;ggajj.B!''!**<$'

OO !

4C4 0$ $ $   
 Q;;7==F {!'(.q	$*1IdsdO( ( (    LLE6&5.01D!&5.0   s   J99
KKrS   )NNN)r   r   r   r   )1r_   r`   ra   rb   rc   r3  re   r   r<  r;   r@  rL  rO  rY  re  ri  rq  r   rx  rz  r  rB   r   r  r   ri   r   r   r  r   r   rr   r   r   r   r  r  r  r  r  r  r  r   r   r,  rB  r-  r^  r_  r+   r&   r$   r*  r*    sk        !2 !2# !2 !2 !2 !2F CG $
 
S 
s 
C 

)6
 
 
 
@( ( ( ( ( ( c 3 $    $'c 'd ' ' ' '
# # ## #c # # # #4# # # # # #J7S 7S 7 7 7 7
46# 46 46 46 46 46 46l7 7S 7x} 7X`adXe 7 7 7 7*, ,# ,HSM ,T , , , ,"	 	3 	# 	RU 	 	 	 	_
 _
c _
3 _
3 _
 _
 _
 _
 _
B2
3 2
: 2
 2
 2
 2
h%
# %
* %
 %
 %
 %
N: : : : : :> > > > > > > >)3 )4 )K ) ) ) )VS s {    $T
s T
S T
[ T
 T
 T
 T
v +0y
 y
# y
3 y
C y
#'y
4?y
 y
 y
 y
vs {    <V
 V
 V
hsm V
z V
 V
 V
 V
r 9=S
 S
c S
 S
(0S
AKS
 S
 S
 S
j1 1 1 1 1*# $    . S  T        D3 4    6 &*&*@ @ @@ c]	@
 sm@ 
@ @ @ @L CLOP<==> =>c => =>C =>"3-=>7:=>IL=>=>69=>BN=> => => =>~P
S P
 P
C P
 P
Q] P
 P
 P
 P
d)
 )
3 )
s )
C )
T` )
 )
 )
 )
Vs # (3- ",/>ALOT`   "ps p# p(3- p"p,/p>ApLOpT`p p p pdm mC mHSM m!$m.1m@CmNQmVbm m m m m mr&   r*  )Krb   rG  r   r  abcr   r   dataclassesr   r   typingr   r   r	   r
   pathlibr   tools.binary_extensionsr   agent.file_safetyr   r   r   r?   rc   r^  _HOMEWRITE_DENIED_PATHSWRITE_DENIED_PREFIXESrt  r   r   r%   r-   r3   r6   tupler;   r9   r<   r@   rB   ri   rr   r|   r   r   r   r   r   re   r   r   rN  r  	frozensetr  r   r   r   r  r  r  r  	MAX_LINESMAX_LINE_LENGTHr  r  r  r&  r'  r  r$  r(  r*  r+   r&   r$   <module>r     s   4 
			 				  # # # # # # # # ( ( ( ( ( ( ( ( , , , , , , , , , , , ,       5 5 5 5 5 5          	IDIKK--e44 33E::  2:BCC 2:LMM "c "c " " " ""     ,# s s    : 	S U39-    58C= 5T 5 5 5 5
)3 )4 ) ) ) ) U U U U U U U U& I I I I I I I I&        >                6                 .6C .6E#s(O .6 .6 .6 .6l BJRSS 9S 9U3S=-AD-H 9 9 9 9:; ; ; ; ;S ; ; ;F NMM  .%)( X (i(=(=(=>> 
  .-# -s -t - - - - 	1s 	1uT3Y'7 	1 	1 	1 	11s 1uT3Y'7 1 1 1 1&1s 1uT3Y'7 1 1 1 1"1 1tSy)9 1 1 1 10   	    s S S     -@+=/ /c /%(/BGS// / / /& /D-A/ / /'*/FKCQTHo/ / / /Z Z Z Z Z. Z Z Z Z Zr&   