
    `gjS                        U d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	m
Z
  ej                  d      Zi Zded<   dd	Zdd
ZddZddd	 	 	 	 	 	 	 	 	 ddZdd	 	 	 	 	 ddZddZg dZy)u0  Workspace and project-root resolution for LSP.

Two concerns live here:

1. **Workspace gate** — the upper-level "is this directory a project?"
   check.  Hermes only runs LSP when the cwd (or the file being edited)
   sits inside a git worktree.  Files outside any git root never
   trigger LSP, even if a server is configured.  This keeps Telegram
   gateway users on user-home cwd's from spawning daemons.

2. **NearestRoot** — the per-server project-root walk.  Each language
   server cares about a different marker (``pyproject.toml`` for
   Python, ``Cargo.toml`` for Rust, ``go.mod`` for Go, etc.) and
   wants the directory containing that marker.  ``nearest_root()``
   walks up from a starting path looking for any of a list of marker
   files, optionally bailing if an exclude marker shows up first.
    )annotationsN)Path)IterableOptionalTuplezagent.lsp.workspacedict_workspace_cachec                z    t         j                  j                  t         j                  j                  |             S )uE  Normalize a path for use as a stable map key.

    Resolves ``~``, makes absolute, and collapses ``.``/``..``.  We do
    NOT resolve symlinks here — symlink stability matters for some
    LSP servers (rust-analyzer cares about Cargo workspace identity)
    and we want the canonical path the user typed when possible.
    )ospathabspath
expanduser)r   s    F/root/.hermes/venv/lib/python3.12/site-packages/agent/lsp/workspace.pynormalize_pathr   !   s&     77??277--d344    c                   	 t        t        |             }|j                         r|j                  }t        j                  t        |            }||\  }}|S |}t        d      D ]Q  }|dz  }	 |j                         r#t        |      }|dft        t        |      <   |c S 	 |j                  }	|	|k(  r n|	}S dt        t        |      <   y# t        t
        t        f$ r Y yw xY w# t        $ r Y  9w xY w)uF  Walk up from ``start`` looking for a ``.git`` entry (file or dir).

    Returns the directory containing ``.git``, or ``None`` if no git
    root is found before hitting the filesystem root.

    A ``.git`` *file* (not directory) means we're inside a git
    worktree set up via ``git worktree add`` — both forms count.
    N@   z.gitTNF)r   r   is_fileparentOSErrorRuntimeError
ValueErrorr	   getstrrangeexists)
start
start_pathcachedroot_is_gitcur_
git_markerresolvedr   s
             r   find_git_worktreer'   ,   s   ./0
#**J !!#j/2Fg
C 2Y 6\
	  "s85=t4D Z1 # S= )6S_%? \:.  ,  		s#   0C /0C'C$#C$'	C43C4c                    t        |       }t        |      }||k(  ry	 t        j                  j                  ||g      }||k(  S # t        $ r Y yw xY w)u[  Return True iff ``path`` is inside (or equal to) ``workspace_root``.

    Uses absolute paths but does not resolve symlinks — a file accessed
    via a symlink that points outside the workspace still counts as
    outside.  This is the conservative interpretation; matches LSP
    behaviour where servers reject didOpen for unrelated files.
    TF)r   r   r   
commonpathr   )r   workspace_rootpr!   commons        r   is_inside_workspacer-   [   s_     	tA.)DDy##QI. T>  s   !A 	AA)excludesceilingc               *   t        t        |             }	 |j                         r|j                  }|rt        t        |            nd}t        |      }|rt        |      ng }|}t        d      D ]h  }	|D ]  }
	 ||
z  j                         r  y |D ]%  }	 ||z  j                         rt        |      c c S ' |||k(  r y|j                  }||k(  r y|}j y# t        t
        t        f$ r Y yw xY w# t        $ r Y w xY w# t        $ r Y }w xY w)u+  Walk up from ``start`` looking for any of the given marker files.

    Returns the **directory containing** the first matched marker, or
    ``None`` if no marker is found before hitting ``ceiling`` (or the
    filesystem root if no ceiling).

    If ``excludes`` is provided and an exclude marker matches *first*
    in the upward walk, returns ``None`` — the server is gated off
    for that file.  Mirrors OpenCode's NearestRoot exclude semantics
    (e.g. typescript skips deno projects when ``deno.json`` is found
    before ``package.json``).
    Nr   )r   r   r   r   r   r   r   listr   r   r   )r   markersr.   r/   r   ceiling_pathmarkers_listexcludes_listr#   r$   excmarkerr   s                r   nearest_rootr8   q   sG   & nU+,J#**J 5<4w/0L=L&.DNBM
C 2Y  ! 	C#I%%' (	 # 	F&L((*s8O +	 #|(;S=-. E \:. $    s5   C :C7DC43C47	DD	DD)cwdc                   |xs t        j                         }t        |      }|t        | |      r|dfS t        |       }||dfS y)uz  Resolve the workspace root for a file.

    Returns ``(workspace_root, gated_in)`` where ``gated_in`` is True
    iff LSP should run for this file at all.  Currently the gate is
    "file is inside a git worktree found by walking up from cwd OR
    from the file itself".

    The cwd path takes precedence — if the agent was launched in a
    git project, that worktree is the workspace, and any edit inside
    it (regardless of where the file lives) is in-scope.  If the cwd
    isn't in a git worktree, we try the file's own location as a
    fallback.

    Returns ``(None, False)`` when neither path is in a git worktree.
    Tr   )r   getcwdr'   r-   )	file_pathr9   cwd_root	file_roots       r   resolve_workspace_for_filer?      sW    ( 
C %Hy(3T>! "),I$r   c                 ,    t         j                          y)zClear the workspace-resolution cache.

    Called on service shutdown so a subsequent re-init doesn't pick
    up stale results from a previous session.
    N)r	   clear r   r   clear_cacherC      s     r   )r'   r-   r8   r   r?   rC   )r   r   returnr   )r   r   rD   Optional[str])r   r   r*   r   rD   bool)
r   r   r2   zIterable[str]r.   zOptional[Iterable[str]]r/   rE   rD   rE   )r<   r   r9   rE   rD   zTuple[Optional[str], bool])rD   None)__doc__
__future__r   loggingr   pathlibr   typingr   r   r   	getLoggerloggerr	   __annotations__r   r'   r-   r8   r?   rC   __all__rB   r   r   <module>rQ      s   " #  	  , ,			0	1
  $ 5,^4 )-!999 &	9
 9 9~  
  	Dr   