
    i                         d Z ddlZddlZddlZddlmZ ddlmZmZm	Z	m
Z
 ddlmZ  ej        e          Zg dZdZh dZd	hZd
Z G d d          ZdS )a  Progressive subdirectory hint discovery.

As the agent navigates into subdirectories via tool calls (read_file, terminal,
search_files, etc.), this module discovers and loads project context files
(AGENTS.md, CLAUDE.md, .cursorrules) from those directories.  Discovered hints
are appended to the tool result so the model gets relevant context at the moment
it starts working in a new area of the codebase.

This complements the startup context loading in ``prompt_builder.py`` which only
loads from the CWD.  Subdirectory hints are discovered lazily and injected into
the conversation without modifying the system prompt (preserving prompt caching).

Inspired by Block/goose's SubdirectoryHintTracker.
    N)Path)DictAnyOptionalSet)_scan_context_content)z	AGENTS.mdz	agents.mdz	CLAUDE.mdz	claude.mdz.cursorrulesi@  >   pathworkdir	file_pathterminal   c                       e Zd ZdZddee         fdZdedeeef         dee         fdZ	ded	eeef         de
fd
Zdedee         fdZdedee         fdZdedefdZdedee         fdZdS )SubdirectoryHintTrackeras  Track which directories the agent visits and load hints on first access.

    Usage::

        tracker = SubdirectoryHintTracker(working_dir="/path/to/project")

        # After each tool call:
        hints = tracker.check_tool_call("read_file", {"path": "backend/src/main.py"})
        if hints:
            tool_result += hints  # append to the tool result string
    Nworking_dirc                     t          |pt          j                                                              | _        t                      | _        | j                            | j                   d S N)r   osgetcwdresolver   set_loaded_dirsadd)selfr   s     =/home/ubuntu/.hermes/hermes-agent/agent/subdirectory_hints.py__init__z SubdirectoryHintTracker.__init__=   sU     :ry{{;;CCEE'*uud./////    	tool_name	tool_argsreturnc                     |                      ||          }|sdS g }|D ].}|                     |          }|r|                    |           /|sdS dd                    |          z   S )zCheck tool call arguments for new directories and load any hint files.

        Returns formatted hint text to append to the tool result, or None.
        N

)_extract_directories_load_hints_for_directoryappendjoin)r   r   r   dirs	all_hintsdhintss          r   check_tool_callz'SubdirectoryHintTracker.check_tool_callC   s     ((I>> 	4	 	( 	(A22155E (  ''' 	4I....r   argsc                    t                      }t          D ]V}|                    |          }t          |t                    r*|                                r|                     ||           W|t          v rA|                    dd          }t          |t                    r|                     ||           t          |          S )z1Extract directory paths from tool call arguments.command )
r   _PATH_ARG_KEYSget
isinstancestrstrip_add_path_candidate_COMMAND_TOOLS_extract_paths_from_commandlist)r   r   r+   
candidateskeyvalcmds          r   r"   z,SubdirectoryHintTracker._extract_directories[   s     !$
 " 	: 	:C((3--C#s## :		 :((j999 &&((9b))C#s## B00jAAAJr   raw_pathr8   c                    	 t          |                                          }|                                s
| j        |z  }|                                }|j        s(|                                r|                                r|j        }t          t                    D ]J}|| j        v r dS |                     |          r|                    |           |j        }||k    r dS |}KdS # t          t          f$ r Y dS w xY w)a  Resolve a raw path and add its directory + ancestors to candidates.

        Walks up from the resolved directory toward the filesystem root,
        stopping at the first directory already in ``_loaded_dirs`` (or after
        ``_MAX_ANCESTOR_WALK`` levels).  This ensures that reading
        ``project/src/main.py`` discovers ``project/AGENTS.md`` even when
        ``project/src/`` has no hint files of its own.
        N)r   
expanduseris_absoluter   r   suffixexistsis_fileparentrange_MAX_ANCESTOR_WALKr   _is_valid_subdirr   OSError
ValueError)r   r<   r8   p_rC   s         r   r4   z+SubdirectoryHintTracker._add_path_candidateo   s   	X))++A==?? )$q(		Ax AHHJJ 199;; H-..  )))EE((++ &NN1%%%Q;;EE  $ 	 	 	DD	s   B)C, -8C, 'C, ,D Dr;   c                    	 t          j        |          }n$# t          $ r |                                }Y nw xY w|D ]M}|                    d          rd|vrd|vr!|                    d          r7|                     ||           NdS )z5Extract path-like tokens from a shell command string.-/.)zhttp://zhttps://zgit@N)shlexsplitrH   
startswithr4   )r   r;   r8   tokenstokens        r   r6   z3SubdirectoryHintTracker._extract_paths_from_command   s    	![%%FF 	! 	! 	!YY[[FFF	!  
	8 
	8E$$ %Cu$4$4 ?@@ $$UJ7777
	8 
	8s    88r	   c                 n    	 |                                 sdS n# t          $ r Y dS w xY w|| j        v rdS dS )z5Check if path is a valid directory to scan for hints.FT)is_dirrG   r   )r   r	   s     r   rF   z(SubdirectoryHintTracker._is_valid_subdir   s[    	;;== u 	 	 	55	4$$$5ts    
''	directoryc                 "   | j                             |           g }t          D ]}||z  }	 |                                sn# t          $ r Y +w xY w	 |                    d                                          }|s[t          ||          }t          |          t          k    r'|dt                   d| dt          |          ddz   }t          |          }	 t          |                    | j                            }nZ# t          $ rM 	 t          |                    t          j                                        }d|z   }n# t          $ r Y nw xY wY nw xY w|                    ||f            n4# t"          $ r'}t$                              d	||           Y d}~d}~ww xY w|sdS g }|D ] \  }}|                    d
| d|            !t$                              d|d |D                        d                    |          S )zALoad hint files from a directory. Returns formatted text or None.zutf-8)encodingNz

[...truncated z: ,z chars total]z~/zCould not read %s: %sz"[Subdirectory context discovered: z]
z%Loaded subdirectory hints from %s: %sc                     g | ]
}|d          S )r    ).0hs     r   
<listcomp>zESubdirectoryHintTracker._load_hints_for_directory.<locals>.<listcomp>   s    '''aQqT'''r   r!   )r   r   _HINT_FILENAMESrB   rG   	read_textr3   r   len_MAX_HINT_CHARSr2   relative_tor   rH   r   homer$   	Exceptionloggerdebugr%   )	r   rV   found_hintsfilename	hint_pathcontentrel_pathexcsectionss	            r   r#   z1SubdirectoryHintTracker._load_hints_for_directory   s   i((('  	F  	FH!H,I ((**    F#--w-??EEGG /BBw<</11 0 01XxXX3w<<XXXXY 
 y>>"9#8#89I#J#JKKHH!   #&y'<'<TY[['I'I#J#J#'(?%   	 ""Hg#6777 F F F4iEEEEEEEEF  	4!, 	 	HgOOKXKK'KK    	3'';'''	
 	
 	

 {{8$$$sx   A
AA*E9?AE9'DE9
E8E
	E

EEEEE9EE99
F*F%%F*r   )__name__
__module____qualname____doc__r   r2   r   r   r   r*   r7   r"   r   r   r4   r6   boolrF   r#   r[   r   r   r   r   0   sF       
 
0 0HSM 0 0 0 0// S>/ 
#	/ / / /0  $(cN 	       (C SY    <8s 8D	 8 8 8 8&	T 	d 	 	 	 	5%4 5%HSM 5% 5% 5% 5% 5% 5%r   r   )rr   loggingr   rO   pathlibr   typingr   r   r   r   agent.prompt_builderr   	getLoggerro   rf   r_   rb   r/   r5   rE   r   r[   r   r   <module>ry      s      				        + + + + + + + + + + + + 6 6 6 6 6 6		8	$	$
    211   p% p% p% p% p% p% p% p% p% p%r   