2727 create_bigram_vector ,
2828 normalize_vector ,
2929)
30+
31+ # Import skills helper for skills
32+ from aider .helpers .skills import SkillsManager
3033from aider .mcp .server import LocalServer
3134from aider .repo import ANY_GIT_ERROR
3235
5053 indent_lines ,
5154 insert_block ,
5255 list_changes ,
56+ load_skill ,
5357 ls ,
5458 make_editable ,
5559 make_readonly ,
5660 remove ,
61+ remove_skill ,
5762 replace_all ,
5863 replace_line ,
5964 replace_lines ,
@@ -126,6 +131,8 @@ def __init__(self, *args, **kwargs):
126131
127132 # Enable context management by default only in agent mode
128133 self .context_management_enabled = True # Enabled by default for agent mode
134+ # Skills configuration
135+ self .skills_manager = None # Will be initialized later
129136
130137 # Initialize change tracker for granular editing
131138 self .change_tracker = ChangeTracker ()
@@ -193,10 +200,12 @@ def _build_tool_registry(self):
193200 indent_lines ,
194201 insert_block ,
195202 list_changes ,
203+ load_skill ,
196204 ls ,
197205 make_editable ,
198206 make_readonly ,
199207 remove ,
208+ remove_skill ,
200209 replace_all ,
201210 replace_line ,
202211 replace_lines ,
@@ -219,6 +228,10 @@ def _build_tool_registry(self):
219228 "tools_excludelist" , agent_config .get ("tools_blacklist" , [])
220229 )
221230
231+ if "skills" not in self .allowed_context_blocks or not agent_config .get ("skills_paths" ):
232+ tools_excludelist .append ("loadskill" )
233+ tools_excludelist .append ("removeskill" )
234+
222235 # Always include essential tools regardless of includelist/excludelist
223236 essential_tools = {"makeeditable" , "replacetext" , "view" , "finished" }
224237 for module in tool_modules :
@@ -286,6 +299,7 @@ def _get_agent_config(self):
286299 "git_status" ,
287300 "symbol_outline" ,
288301 "todo_list" ,
302+ "skills" ,
289303 }
290304
291305 if "exclude_context_blocks" in config :
@@ -301,8 +315,51 @@ def _get_agent_config(self):
301315 "skip_cli_confirmations" , config .get ("yolo" , False )
302316 )
303317
318+ if "skills" in self .allowed_context_blocks :
319+ # Skills configuration
320+ if "skills_paths" not in config :
321+ config ["skills_paths" ] = []
322+ if "skills_includelist" not in config :
323+ config ["skills_includelist" ] = []
324+ if "skills_excludelist" not in config :
325+ config ["skills_excludelist" ] = []
326+
327+ self ._initialize_skills_manager (config )
328+
304329 return config
305330
331+ def _initialize_skills_manager (self , config ):
332+ """
333+ Initialize the skills manager with the configured directory paths and filters.
334+ """
335+ if not config .get ("skills_paths" , []):
336+ return
337+
338+ try :
339+ git_root = str (self .repo .root ) if self .repo else None
340+ self .skills_manager = SkillsManager (
341+ directory_paths = config .get ("skills_paths" , []),
342+ include_list = config .get ("skills_includelist" , []),
343+ exclude_list = config .get ("skills_excludelist" , []),
344+ git_root = git_root ,
345+ coder = self , # Pass reference to the coder instance
346+ )
347+
348+ except Exception as e :
349+ self .io .tool_warning (f"Failed to initialize skills manager: { str (e )} " )
350+
351+ def show_announcements (self ):
352+ super ().show_announcements ()
353+
354+ # Find and log available skills
355+ skills = self .skills_manager .find_skills ()
356+ if skills :
357+ skills_list = []
358+ for skill in skills :
359+ skills_list .append (skill .name )
360+
361+ self .io .tool_output (f"Available Skills: { ", " .join (skills_list )} " )
362+
306363 def get_local_tool_schemas (self ):
307364 """Returns the JSON schemas for all local tools using the tool registry."""
308365 schemas = []
@@ -503,6 +560,8 @@ def _calculate_context_block_tokens(self, force=False):
503560 "directory_structure" ,
504561 "git_status" ,
505562 "symbol_outline" ,
563+ "skills" ,
564+ "loaded_skills" ,
506565 ]
507566
508567 for block_type in block_types :
@@ -539,6 +598,10 @@ def _generate_context_block(self, block_name):
539598 content = self .get_context_summary ()
540599 elif block_name == "todo_list" :
541600 content = self .get_todo_list ()
601+ elif block_name == "skills" :
602+ content = self .get_skills_context ()
603+ elif block_name == "loaded_skills" :
604+ content = self .get_skills_content ()
542605
543606 # Cache the result if it's not None
544607 if content is not None :
@@ -697,13 +760,16 @@ def format_chat_chunks(self):
697760 chunks = ChatChunks (
698761 chunk_ordering = [
699762 "system" ,
763+ "static" ,
700764 "examples" ,
701765 "readonly_files" ,
702766 "repo" ,
703767 "chat_files" ,
768+ "pre_message" ,
704769 "done" ,
705770 "edit_files" ,
706771 "cur" ,
772+ "post_message" ,
707773 "reminder" ,
708774 ]
709775 )
@@ -760,59 +826,75 @@ def format_chat_chunks(self):
760826 # This also populates the context block cache
761827 self ._calculate_context_block_tokens ()
762828
763- # Get blocks from cache to avoid regenerating them
764- env_context = self .get_cached_context_block ("environment_info" )
765- dir_structure = self .get_cached_context_block ("directory_structure" )
766- git_status = self .get_cached_context_block ("git_status" )
767- symbol_outline = self .get_cached_context_block ("symbol_outline" )
768- todo_list = self .get_cached_context_block ("todo_list" )
769-
770- # Context summary needs special handling because it depends on other blocks
771- context_summary = self .get_context_summary ()
829+ # Initialize chunk sections
830+ chunks .static = []
831+ chunks .pre_message = []
832+ chunks .post_message = []
772833
773834 # 1. Add relatively static blocks BEFORE done_messages
774835 # These blocks change less frequently and can be part of the cacheable prefix
775836 static_blocks = []
776- if env_context and "environment_info" in self .allowed_context_blocks :
777- static_blocks .append (env_context )
778- if dir_structure and "directory_structure" in self .allowed_context_blocks :
779- static_blocks .append (dir_structure )
780-
781- if static_blocks :
782- static_message = "\n \n " .join (static_blocks )
783- # Insert as a system message right before done_messages
784- chunks .system .append (dict (role = "system" , content = static_message ))
785837
786838 # 2. Add dynamic blocks AFTER chat_files
787839 # These blocks change with the current files in context
788- pre_dynamic_blocks = []
789- post_dynamic_blocks = []
790- if context_summary and "context_summary" in self .allowed_context_blocks :
791- pre_dynamic_blocks .append (context_summary )
792- if symbol_outline and "symbol_outline" in self .allowed_context_blocks :
793- pre_dynamic_blocks .append (symbol_outline )
794- if git_status and "git_status" in self .allowed_context_blocks :
795- pre_dynamic_blocks .append (git_status )
796-
797- if todo_list and "todo_list" in self .allowed_context_blocks :
798- pre_dynamic_blocks .append (todo_list )
840+ pre_message_blocks = []
841+ post_message_blocks = []
842+
843+ if "environment_info" in self .allowed_context_blocks :
844+ block = self .get_cached_context_block ("environment_info" )
845+ static_blocks .append (block )
846+
847+ if "directory_structure" in self .allowed_context_blocks :
848+ block = self .get_cached_context_block ("directory_structure" )
849+ static_blocks .append (block )
850+
851+ if "skills" in self .allowed_context_blocks :
852+ block = self ._generate_context_block ("skills" )
853+ static_blocks .append (block )
854+
855+ if "symbol_outline" in self .allowed_context_blocks :
856+ block = self .get_cached_context_block ("symbol_outline" )
857+ pre_message_blocks .append (block )
858+
859+ if "git_status" in self .allowed_context_blocks :
860+ block = self .get_cached_context_block ("git_status" )
861+ pre_message_blocks .append (block )
862+
863+ if "todo_list" in self .allowed_context_blocks :
864+ block = self .get_cached_context_block ("todo_list" )
865+ pre_message_blocks .append (block )
866+
867+ if "skills" in self .allowed_context_blocks :
868+ block = self ._generate_context_block ("loaded_skills" )
869+ pre_message_blocks .append (block )
870+
871+ if "context_summary" in self .allowed_context_blocks :
872+ # Context summary needs special handling because it depends on other blocks
873+ block = self .get_context_summary ()
874+ pre_message_blocks .insert (0 , block )
875+
799876 # Add tool usage context if there are repetitive tools
800877 if hasattr (self , "tool_usage_history" ) and self .tool_usage_history :
801878 repetitive_tools = self ._get_repetitive_tools ()
802879 if repetitive_tools :
803880 tool_context = self ._generate_tool_context (repetitive_tools )
804881 if tool_context :
805- post_dynamic_blocks .append (tool_context )
882+ post_message_blocks .append (tool_context )
806883
807- if pre_dynamic_blocks :
808- dynamic_message = "\n \n " .join (pre_dynamic_blocks )
809- # Append as a system message on reminders
810- chunks .done .insert (0 , dict (role = "system" , content = dynamic_message ))
884+ if static_blocks :
885+ for block in static_blocks :
886+ if block :
887+ chunks .static .append (dict (role = "system" , content = block ))
888+
889+ if pre_message_blocks :
890+ for block in pre_message_blocks :
891+ if block :
892+ chunks .pre_message .append (dict (role = "system" , content = block ))
811893
812- if post_dynamic_blocks :
813- dynamic_message = " \n \n " . join ( post_dynamic_blocks )
814- # Append as a system message on reminders
815- reminder_message . insert ( 0 , dict (role = "system" , content = dynamic_message ))
894+ if post_message_blocks :
895+ for block in post_message_blocks :
896+ if block :
897+ chunks . post_message . append ( dict (role = "system" , content = block ))
816898
817899 # Use accurate token counting method that considers enhanced context blocks
818900 base_messages = chunks .all_messages ()
@@ -855,6 +937,9 @@ def format_chat_chunks(self):
855937 )
856938 chunks .cur [- 1 ] = dict (role = final ["role" ], content = new_content )
857939
940+ if self .verbose :
941+ self ._log_chunks (chunks )
942+
858943 return chunks
859944
860945 def _update_edit_file_tracking (self , edit_file_names ):
@@ -2273,6 +2358,38 @@ def get_todo_list(self):
22732358 self .io .tool_error (f"Error generating todo list context: { str (e )} " )
22742359 return None
22752360
2361+ def get_skills_context (self ):
2362+ """
2363+ Generate a context block for available skills.
2364+
2365+ Returns:
2366+ Formatted context block string or None if no skills available
2367+ """
2368+ if not self .use_enhanced_context or not self .skills_manager :
2369+ return None
2370+
2371+ try :
2372+ return self .skills_manager .get_skills_context ()
2373+ except Exception as e :
2374+ self .io .tool_error (f"Error generating skills context: { str (e )} " )
2375+ return None
2376+
2377+ def get_skills_content (self ):
2378+ """
2379+ Generate a context block with the actual content of loaded skills.
2380+
2381+ Returns:
2382+ Formatted context block string with skill contents or None if no skills available
2383+ """
2384+ if not self .use_enhanced_context or not self .skills_manager :
2385+ return None
2386+
2387+ try :
2388+ return self .skills_manager .get_skills_content ()
2389+ except Exception as e :
2390+ self .io .tool_error (f"Error generating skills content context: { str (e )} " )
2391+ return None
2392+
22762393 def get_git_status (self ):
22772394 """
22782395 Generate a git status context block for repository information.
@@ -2416,3 +2533,46 @@ def cmd_context_blocks(self, args=""):
24162533 self .tokens_calculated = False
24172534
24182535 return True
2536+
2537+ def _log_chunks (self , chunks ):
2538+ try :
2539+ import hashlib
2540+ import json
2541+
2542+ if not hasattr (self , "_message_hashes" ):
2543+ self ._message_hashes = {
2544+ "system" : None ,
2545+ "static" : None ,
2546+ "examples" : None ,
2547+ "readonly_files" : None ,
2548+ "repo" : None ,
2549+ "chat_files" : None ,
2550+ "pre_message" : None ,
2551+ "done" : None ,
2552+ "edit_files" : None ,
2553+ "cur" : None ,
2554+ "post_message" : None ,
2555+ "reminder" : None ,
2556+ }
2557+
2558+ changes = []
2559+ for key , value in self ._message_hashes .items ():
2560+ json_obj = json .dumps (
2561+ getattr (chunks , key , "" ), sort_keys = True , separators = ("," , ":" )
2562+ )
2563+ new_hash = hashlib .sha256 (json_obj .encode ("utf-8" )).hexdigest ()
2564+ if self ._message_hashes [key ] != new_hash :
2565+ changes .append (key )
2566+
2567+ self ._message_hashes [key ] = new_hash
2568+
2569+ print ("" )
2570+ print ("MESSAGE CHUNK HASHES" )
2571+ print (self ._message_hashes )
2572+ print ("" )
2573+ print (changes )
2574+ print ("" )
2575+
2576+ except Exception as e :
2577+ print (e )
2578+ pass
0 commit comments