
    PL
jS                        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dS )(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_cachepathstrreturnc                z    t           j                            t           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.
    )osr
   abspath
expanduser)r
   s    7/home/kuhnn/.hermes/hermes-agent/agent/lsp/workspace.pynormalize_pathr   !   s(     7??27--d33444    startOptional[str]c                <   	 t          t          |                     }|                                r|j        }n# t          t
          t          f$ r Y dS w xY wt                              t          |                    }||\  }}|S |}t          d          D ]k}|dz  }	 |                                r,t          |          }|dft          t          |          <   |c S n# t          $ r Y  nw xY w|j        }	|	|k    r n|	}ldt          t          |          <   dS )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	   getr   rangeexists)
r   
start_pathcachedroot_is_gitcur_
git_markerresolvedr   s
             r   find_git_worktreer)   ,   sX   .//00
 	+#*J\:.    tt !!#j//22Fg
C 2YY  6\
	  ""  s885=t4D Z1   	 	 	EE	 S==E(5S__%4s!   7: AA!=C""
C0/C0workspace_rootboolc                    t          |           }t          |          }||k    rdS 	 t          j                            ||g          }n# t          $ r Y dS w xY w||k    S )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
   r*   pr#   commons        r   is_inside_workspacer0   [   sz     	tA.))DDyyt##QI..   uu T>s   !A
 

AA)excludesceilingmarkersIterable[str]r1   Optional[Iterable[str]]r2   c               |   t          t          |                     }	 |                                r|j        }n# t          t
          t          f$ r Y dS w xY w|rt          t          |                    nd}t          |          }|rt          |          ng }|}t          d          D ]}	|D ].}
	 ||
z  	                                r  dS # t          $ r Y +w xY w|D ]=}	 ||z  	                                rt          |          c c S .# t          $ r Y :w xY w|	||k    r dS |j        }||k    r dS |}dS )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   r3   r1   r2   r!   ceiling_pathmarkers_listexcludes_listr%   r&   excmarkerr   s                r   nearest_rootr=   q   s   & nU++,,J 	+#*J\:.   tt4;E4w//000L==L&.6DNNNBM
C 2YY   ! 	 	C#I%%''  444     # 	 	F&L((** $s88OOOOO$    #|(;(;44S==444s3   : AA2C
CC#%D
DD)cwd	file_pathr>   Tuple[Optional[str], bool]c                   |pt          j                    }t          |          }|t          | |          r|dfS t          |           }||dfS dS )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.
    NTr   )r   getcwdr)   r0   )r?   r>   cwd_root	file_roots       r   resolve_workspace_for_filerE      sg    ( 
C %%Hy(33 	"T>! "),,I$;r   Nonec                 8    t                                            dS )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_cacherJ      s     r   )r)   r0   r=   r   rE   rJ   )r
   r   r   r   )r   r   r   r   )r
   r   r*   r   r   r+   )
r   r   r3   r4   r1   r5   r2   r   r   r   )r?   r   r>   r   r   r@   )r   rF   )__doc__
__future__r   loggingr   pathlibr   typingr   r   r   	getLoggerloggerr	   __annotations__r   r)   r0   r=   rE   rJ   __all__rI   r   r   <module>rT      sB    " # " " " " "  				       , , , , , , , , , ,		0	1	1
      5 5 5 5, , , ,^   4 )-!9 9 9 9 9 9~      D     r   