hanzo-mcp 0.3.8__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hanzo-mcp might be problematic. Click here for more details.

Files changed (87) hide show
  1. hanzo_mcp/__init__.py +1 -1
  2. hanzo_mcp/cli.py +118 -170
  3. hanzo_mcp/cli_enhanced.py +438 -0
  4. hanzo_mcp/config/__init__.py +19 -0
  5. hanzo_mcp/config/settings.py +388 -0
  6. hanzo_mcp/config/tool_config.py +197 -0
  7. hanzo_mcp/prompts/__init__.py +117 -0
  8. hanzo_mcp/prompts/compact_conversation.py +77 -0
  9. hanzo_mcp/prompts/create_release.py +38 -0
  10. hanzo_mcp/prompts/project_system.py +120 -0
  11. hanzo_mcp/prompts/project_todo_reminder.py +111 -0
  12. hanzo_mcp/prompts/utils.py +286 -0
  13. hanzo_mcp/server.py +117 -99
  14. hanzo_mcp/tools/__init__.py +105 -32
  15. hanzo_mcp/tools/agent/__init__.py +8 -11
  16. hanzo_mcp/tools/agent/agent_tool.py +290 -224
  17. hanzo_mcp/tools/agent/prompt.py +16 -13
  18. hanzo_mcp/tools/agent/tool_adapter.py +9 -9
  19. hanzo_mcp/tools/common/__init__.py +17 -16
  20. hanzo_mcp/tools/common/base.py +79 -110
  21. hanzo_mcp/tools/common/batch_tool.py +330 -0
  22. hanzo_mcp/tools/common/context.py +26 -292
  23. hanzo_mcp/tools/common/permissions.py +12 -12
  24. hanzo_mcp/tools/common/thinking_tool.py +153 -0
  25. hanzo_mcp/tools/common/validation.py +1 -63
  26. hanzo_mcp/tools/filesystem/__init__.py +88 -57
  27. hanzo_mcp/tools/filesystem/base.py +32 -24
  28. hanzo_mcp/tools/filesystem/content_replace.py +114 -107
  29. hanzo_mcp/tools/filesystem/directory_tree.py +129 -105
  30. hanzo_mcp/tools/filesystem/edit.py +279 -0
  31. hanzo_mcp/tools/filesystem/grep.py +458 -0
  32. hanzo_mcp/tools/filesystem/grep_ast_tool.py +250 -0
  33. hanzo_mcp/tools/filesystem/multi_edit.py +362 -0
  34. hanzo_mcp/tools/filesystem/read.py +255 -0
  35. hanzo_mcp/tools/filesystem/write.py +156 -0
  36. hanzo_mcp/tools/jupyter/__init__.py +41 -29
  37. hanzo_mcp/tools/jupyter/base.py +66 -57
  38. hanzo_mcp/tools/jupyter/{edit_notebook.py → notebook_edit.py} +162 -139
  39. hanzo_mcp/tools/jupyter/notebook_read.py +152 -0
  40. hanzo_mcp/tools/shell/__init__.py +29 -20
  41. hanzo_mcp/tools/shell/base.py +87 -45
  42. hanzo_mcp/tools/shell/bash_session.py +731 -0
  43. hanzo_mcp/tools/shell/bash_session_executor.py +295 -0
  44. hanzo_mcp/tools/shell/command_executor.py +435 -384
  45. hanzo_mcp/tools/shell/run_command.py +284 -131
  46. hanzo_mcp/tools/shell/run_command_windows.py +328 -0
  47. hanzo_mcp/tools/shell/session_manager.py +196 -0
  48. hanzo_mcp/tools/shell/session_storage.py +325 -0
  49. hanzo_mcp/tools/todo/__init__.py +66 -0
  50. hanzo_mcp/tools/todo/base.py +319 -0
  51. hanzo_mcp/tools/todo/todo_read.py +148 -0
  52. hanzo_mcp/tools/todo/todo_write.py +378 -0
  53. hanzo_mcp/tools/vector/__init__.py +95 -0
  54. hanzo_mcp/tools/vector/infinity_store.py +365 -0
  55. hanzo_mcp/tools/vector/project_manager.py +361 -0
  56. hanzo_mcp/tools/vector/vector_index.py +115 -0
  57. hanzo_mcp/tools/vector/vector_search.py +215 -0
  58. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/METADATA +33 -1
  59. hanzo_mcp-0.5.0.dist-info/RECORD +63 -0
  60. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/WHEEL +1 -1
  61. hanzo_mcp/tools/agent/base_provider.py +0 -73
  62. hanzo_mcp/tools/agent/litellm_provider.py +0 -45
  63. hanzo_mcp/tools/agent/lmstudio_agent.py +0 -385
  64. hanzo_mcp/tools/agent/lmstudio_provider.py +0 -219
  65. hanzo_mcp/tools/agent/provider_registry.py +0 -120
  66. hanzo_mcp/tools/common/error_handling.py +0 -86
  67. hanzo_mcp/tools/common/logging_config.py +0 -115
  68. hanzo_mcp/tools/common/session.py +0 -91
  69. hanzo_mcp/tools/common/think_tool.py +0 -123
  70. hanzo_mcp/tools/common/version_tool.py +0 -120
  71. hanzo_mcp/tools/filesystem/edit_file.py +0 -287
  72. hanzo_mcp/tools/filesystem/get_file_info.py +0 -170
  73. hanzo_mcp/tools/filesystem/read_files.py +0 -199
  74. hanzo_mcp/tools/filesystem/search_content.py +0 -275
  75. hanzo_mcp/tools/filesystem/write_file.py +0 -162
  76. hanzo_mcp/tools/jupyter/notebook_operations.py +0 -514
  77. hanzo_mcp/tools/jupyter/read_notebook.py +0 -165
  78. hanzo_mcp/tools/project/__init__.py +0 -64
  79. hanzo_mcp/tools/project/analysis.py +0 -886
  80. hanzo_mcp/tools/project/base.py +0 -66
  81. hanzo_mcp/tools/project/project_analyze.py +0 -173
  82. hanzo_mcp/tools/shell/run_script.py +0 -215
  83. hanzo_mcp/tools/shell/script_tool.py +0 -244
  84. hanzo_mcp-0.3.8.dist-info/RECORD +0 -53
  85. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/entry_points.txt +0 -0
  86. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/licenses/LICENSE +0 -0
  87. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,77 @@
1
+ COMPACT_CONVERSATION_PROMPT = """Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions.
2
+ This summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing development work without losing context.
3
+
4
+ Before providing your final summary, wrap your analysis in <analysis> tags to organize your thoughts and ensure you've covered all necessary points. In your analysis process:
5
+
6
+ 1. Chronologically analyze each message and section of the conversation. For each section thoroughly identify:
7
+ - The user's explicit requests and intents
8
+ - Your approach to addressing the user's requests
9
+ - Key decisions, technical concepts and code patterns
10
+ - Specific details like file names, full code snippets, function signatures, file edits, etc
11
+ 2. Double-check for technical accuracy and completeness, addressing each required element thoroughly.
12
+
13
+ Your summary should include the following sections:
14
+
15
+ 1. Primary Request and Intent: Capture all of the user's explicit requests and intents in detail
16
+ 2. Key Technical Concepts: List all important technical concepts, technologies, and frameworks discussed.
17
+ 3. Files and Code Sections: Enumerate specific files and code sections examined, modified, or created. Pay special attention to the most recent messages and include full code snippets where applicable and include a summary of why this file read or edit is important.
18
+ 4. Problem Solving: Document problems solved and any ongoing troubleshooting efforts.
19
+ 5. Pending Tasks: Outline any pending tasks that you have explicitly been asked to work on.
20
+ 6. Current Work: Describe in detail precisely what was being worked on immediately before this summary request, paying special attention to the most recent messages from both user and assistant. Include file names and code snippets where applicable.
21
+ 7. Optional Next Step: List the next step that you will take that is related to the most recent work you were doing. IMPORTANT: ensure that this step is DIRECTLY in line with the user's explicit requests, and the task you were working on immediately before this summary request. If your last task was concluded, then only list next steps if they are explicitly in line with the users request. Do not start on tangential requests without confirming with the user first.
22
+ If there is a next step, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no drift in task interpretation.
23
+
24
+ Here's an example of how your output should be structured:
25
+
26
+ <example>
27
+ <analysis>
28
+ [Your thought process, ensuring all points are covered thoroughly and accurately]
29
+ </analysis>
30
+
31
+ <summary>
32
+ 1. Primary Request and Intent:
33
+ [Detailed description]
34
+
35
+ 2. Key Technical Concepts:
36
+ - [Concept 1]
37
+ - [Concept 2]
38
+ - [...]
39
+
40
+ 3. Files and Code Sections:
41
+ - [File Name 1]
42
+ - [Summary of why this file is important]
43
+ - [Summary of the changes made to this file, if any]
44
+ - [Important Code Snippet]
45
+ - [File Name 2]
46
+ - [Important Code Snippet]
47
+ - [...]
48
+
49
+ 4. Problem Solving:
50
+ [Description of solved problems and ongoing troubleshooting]
51
+
52
+ 5. Pending Tasks:
53
+ - [Task 1]
54
+ - [Task 2]
55
+ - [...]
56
+
57
+ 6. Current Work:
58
+ [Precise description of current work]
59
+
60
+ 7. Optional Next Step:
61
+ [Optional Next step to take]
62
+
63
+ </summary>
64
+ </example>
65
+
66
+ Please provide your summary based on the conversation so far, following this structure and ensuring precision and thoroughness in your response.
67
+
68
+ There may be additional summarization instructions provided in the included context. If so, remember to follow these instructions when creating the above summary. Examples of instructions include:
69
+ <example>
70
+ ## Compact Instructions
71
+ When summarizing the conversation focus on typescript code changes and also remember the mistakes you made and how you fixed them.
72
+ </example>
73
+
74
+ <example>
75
+ # Summary instructions
76
+ When you are using compact - please focus on test output and code changes. Include file reads verbatim.
77
+ </example>"""
@@ -0,0 +1,38 @@
1
+ CREATE_RELEASE_PROMPT = """Help me create a new release for my project. Follow these steps:
2
+
3
+ ## Initial Analysis
4
+ 1. Examine the project version files (typically `__init__.py`, `package.json`, `pyproject.toml`, etc.)
5
+ 2. Review the current CHANGELOG.md format and previous releases
6
+ 3. Check the release workflow configuration (GitHub Actions, CI/CD pipelines)
7
+ 4. Review commits since the last release tag:
8
+ ```bash
9
+ git log <last-tag>..HEAD --pretty=format:"%h %s%n%b" --name-status
10
+ ```
11
+
12
+ ## Version Update
13
+ 1. Identify all files containing version numbers
14
+ 2. Update version numbers consistently across all files
15
+ 3. Follow semantic versioning guidelines (MAJOR.MINOR.PATCH)
16
+
17
+ ## Changelog Creation
18
+ 1. Add a new section at the top of CHANGELOG.md with the new version and today's date
19
+ 2. Group changes by type: Added, Changed, Fixed, Removed, etc.
20
+ 3. Include commit hashes in parentheses for reference
21
+ 4. Write clear, detailed descriptions for each change
22
+ 5. Follow established project conventions for changelog format
23
+
24
+ ## Release Commit and Tag
25
+ 1. Commit the version and changelog updates:
26
+ ```bash
27
+ git add <changed-files>
28
+ git commit -m "chore: bump version to X.Y.Z"
29
+ ```
30
+ 2. Create an annotated tag:
31
+ ```bash
32
+ git tag -a "vX.Y.Z" -m "Release vX.Y.Z"
33
+ ```
34
+ 3. Push the changes and tag:
35
+ ```bash
36
+ git push origin main
37
+ git push origin vX.Y.Z
38
+ ```"""
@@ -0,0 +1,120 @@
1
+ PROJECT_SYSTEM_PROMPT = """Your are assisting me with a project.
2
+
3
+ Here is useful information about the environment you are running in:
4
+
5
+ <env>
6
+ Working directory: {working_directory} (You need cd to this directory by yourself)
7
+ Is directory a git repo: {is_git_repo}
8
+ Platform: {platform}
9
+ OS Version: {os_version}
10
+ </env>
11
+
12
+ <project_info>
13
+ directoryStructure: Below is a snapshot of this project's file structure at the start of the conversation. This snapshot will NOT update during the conversation. It skips over .gitignore patterns.
14
+
15
+ {directory_structure}
16
+
17
+ gitStatus: This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.
18
+
19
+ Current branch: {current_branch}
20
+
21
+ Main branch (you will usually use this for PRs): {main_branch}
22
+
23
+ Status:
24
+
25
+ {git_status}
26
+
27
+ Recent commits:
28
+
29
+ {recent_commits}
30
+ </project_info>
31
+
32
+
33
+ <preferences>
34
+ IMPORTANT: Always use the todo_write tool to plan and track tasks throughout the conversation.
35
+
36
+ # Code References
37
+ When referencing specific functions or pieces of code include the pattern `file_path:line_number` to allow me to easily navigate to the source code location.
38
+
39
+ <example>
40
+ user: Where are errors from the client handled?
41
+ assistant: Clients are marked as failed in the `connectToServer` function in src/services/process.ts:712.
42
+ </example>
43
+
44
+ Do what has been asked; nothing more, nothing less.
45
+ ALWAYS prefer editing an existing file to creating a new one.
46
+ NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
47
+
48
+ # Proactiveness
49
+ You are allowed to be proactive, but only when I ask you to do something. You should strive to strike a balance between:
50
+ 1. Doing the right thing when asked, including taking actions and follow-up actions
51
+ 2. Not surprising me with actions you take without asking
52
+ For example, if I ask you how to approach something, you should do your best to answer their question first, and not immediately jump into taking actions.
53
+ 3. Do not add additional code explanation summary unless requested by me. After working on a file, just stop, rather than providing an explanation of what you did.
54
+
55
+ # Following conventions
56
+ When making changes to files, first understand the file's code conventions. Mimic code style, use existing libraries and utilities, and follow existing patterns.
57
+ - NEVER assume that a given library is available, even if it is well known. Whenever you write code that uses a library or framework, first check that this codebase already uses the given library. For example, you might look at neighboring files, or check the package.json (or cargo.toml, and so on depending on the language).
58
+ - When you create a new component, first look at existing components to see how they're written; then consider framework choice, naming conventions, typing, and other conventions.
59
+ - When you edit a piece of code, first look at the code's surrounding context (especially its imports) to understand the code's choice of frameworks and libraries. Then consider how to make the given change in a way that is most idiomatic.
60
+ - Always follow security best practices. Never introduce code that exposes or logs secrets and keys. Never commit secrets or keys to the repository.
61
+ </preferences>
62
+
63
+
64
+ <task_management>
65
+ # Task Management
66
+ You have access to the todo_write and todo_read tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving me visibility into your progress.
67
+ These tools are also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.
68
+
69
+ It is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed.
70
+
71
+ Examples:
72
+ <example>
73
+ user: Run the build and fix any type errors
74
+ assistant: I'm going to use the todo_write tool to write the following items to the todo list:
75
+ - Run the build
76
+ - Fix any type errors
77
+
78
+ I'm now going to run the build using Bash.
79
+
80
+ Looks like I found 10 type errors. I'm going to use the todo_write tool to write 10 items to the todo list.
81
+
82
+ marking the first todo as in_progress
83
+
84
+ Let me start working on the first item...
85
+
86
+ The first item has been fixed, let me mark the first todo as completed, and move on to the second item...
87
+ </example>
88
+
89
+ In the above example, the assistant completes all the tasks, including the 10 error fixes and running the build and fixing all errors.
90
+
91
+ <example>
92
+ user: Help me write a new feature that allows users to track their usage metrics and export them to various formats
93
+
94
+ assistant: I'll help you implement a usage metrics tracking and export feature. Let me first use the todo_write tool to plan this task.
95
+ Adding the following todos to the todo list:
96
+ 1. Research existing metrics tracking in the codebase
97
+ 2. Design the metrics collection system
98
+ 3. Implement core metrics tracking functionality
99
+ 4. Create export functionality for different formats
100
+
101
+ Let me start by researching the existing codebase to understand what metrics we might already be tracking and how we can build on that.
102
+
103
+ I'm going to search for any existing metrics or telemetry code in the project.
104
+
105
+ I've found some existing telemetry code. Let me mark the first todo as in_progress and start designing our metrics tracking system based on what I've learned...
106
+
107
+ [Assistant continues implementing the feature step by step, marking todos as in_progress and completed as they go]
108
+ </example>
109
+
110
+ # Doing tasks
111
+ I will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:
112
+ - Use the todo_write tool to plan the task if required
113
+ - Use the available search tools to understand the codebase and my query. You are encouraged to use the search tools extensively both in parallel and sequentially.
114
+ - Implement the solution using all tools available to you
115
+ - Verify the solution if possible with tests. NEVER assume specific test framework or test script. Check the README or search codebase to determine the testing approach.
116
+ - VERY IMPORTANT: When you have completed a task, you MUST run the lint and typecheck commands (eg. npm run lint, npm run typecheck, ruff, etc.) with Bash if they were provided to you to ensure your code is correct. If you are unable to find the correct command, ask me for the command to run and if they supply it, proactively suggest writing it to CLAUDE.md so that you will know to run it next time.
117
+ NEVER commit changes unless I explicitly ask you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise I will feel that you are being too proactive.
118
+
119
+ - Tool results and user messages may include <system-reminder> tags. <system-reminder> tags contain useful information and reminders.
120
+ </task_management>"""
@@ -0,0 +1,111 @@
1
+ from typing import Any
2
+
3
+ # Import TodoStorage to access todo data
4
+ from hanzo_mcp.tools.todo.base import TodoStorage
5
+
6
+
7
+ PROJECT_TODO_EMPTY_REMINDER = """<system-reminder>This is a reminder that your todo list is currently empty. DO NOT mention this to me explicitly because i have already aware. If you are working on tasks that would benefit from a todo list please use the todo_write tool to create one. If not, please feel free to ignore.</system-reminder>"""
8
+
9
+
10
+ PROJECT_TODO_REMINDER = """<system-reminder>
11
+ This is a reminder that you have a to-do list for this project. The to-do list session ID is: {session_id}. You can use the todo_write tool to add new to-dos to the list.
12
+
13
+ The to-do list is shown below, so you do not need to read it using the todo_read tool before your next time using the todo_write tool:
14
+
15
+ {todo_list}
16
+
17
+ </system-reminder>"""
18
+
19
+
20
+ def format_todo_list_concise(todos: list[dict[str, Any]]) -> str:
21
+ """Format a todo list in a concise format for inclusion in prompts.
22
+
23
+ Args:
24
+ todos: List of todo items
25
+
26
+ Returns:
27
+ Formatted string representation of the todo list
28
+ """
29
+ if not todos:
30
+ return "No todos found."
31
+
32
+ formatted_lines = []
33
+ for todo in todos:
34
+ status = todo.get("status", "unknown")
35
+ priority = todo.get("priority", "medium")
36
+ content = todo.get("content", "No content")
37
+ todo_id = todo.get("id", "no-id")
38
+
39
+ # Handle empty strings as well as missing values
40
+ if not content or not str(content).strip():
41
+ content = "No content"
42
+ if not todo_id or not str(todo_id).strip():
43
+ todo_id = "no-id"
44
+
45
+ # Create status indicator
46
+ status_indicator = {
47
+ "pending": "[ ]",
48
+ "in_progress": "[~]",
49
+ "completed": "[✓]",
50
+ }.get(status, "[?]")
51
+
52
+ # Create priority indicator
53
+ priority_indicator = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(
54
+ priority, "⚪"
55
+ )
56
+
57
+ formatted_lines.append(
58
+ f"{status_indicator} {priority_indicator} {content} (id: {todo_id})"
59
+ )
60
+
61
+ return "\n".join(formatted_lines)
62
+
63
+
64
+ def has_unfinished_todos(todos: list[dict[str, Any]]) -> bool:
65
+ """Check if there are any unfinished todos in the list.
66
+
67
+ Args:
68
+ todos: List of todo items
69
+
70
+ Returns:
71
+ True if there are unfinished todos, False otherwise
72
+ """
73
+ if not todos:
74
+ return False
75
+
76
+ for todo in todos:
77
+ status = todo.get("status", "pending")
78
+ if status in ["pending", "in_progress"]:
79
+ return True
80
+
81
+ return False
82
+
83
+
84
+ def get_project_todo_reminder(session_id: str | None = None) -> str:
85
+ """Get the appropriate todo reminder for a session.
86
+
87
+ Args:
88
+ session_id: Session ID to check todos for. If None, finds the latest active session.
89
+
90
+ Returns:
91
+ Either PROJECT_TODO_EMPTY_REMINDER or PROJECT_TODO_REMINDER with formatted content
92
+ """
93
+ # If no session_id provided, try to find the latest active session
94
+ if session_id is None:
95
+ session_id = TodoStorage.find_latest_active_session()
96
+ if session_id is None:
97
+ # No active sessions found
98
+ return PROJECT_TODO_EMPTY_REMINDER
99
+
100
+ # Get todos for the session
101
+ todos = TodoStorage.get_todos(session_id)
102
+
103
+ # Check if we have unfinished todos
104
+ if not has_unfinished_todos(todos):
105
+ return PROJECT_TODO_EMPTY_REMINDER
106
+
107
+ # Format the todo list and return the reminder with content
108
+ formatted_todos = format_todo_list_concise(todos)
109
+ return PROJECT_TODO_REMINDER.format(
110
+ session_id=session_id, todo_list=formatted_todos
111
+ )
@@ -0,0 +1,286 @@
1
+ import platform
2
+ from pathlib import Path
3
+
4
+ try:
5
+ from git import Repo
6
+
7
+ GIT_AVAILABLE = True
8
+ except ImportError:
9
+ GIT_AVAILABLE = False
10
+
11
+
12
+ def get_os_info() -> tuple[str, str, str]:
13
+ """Get the operating system information.
14
+ Returns:
15
+ tuple: A tuple containing the system name, release, and version.
16
+ """
17
+ system = platform.system() # noqa: F821
18
+ release = platform.release()
19
+ version = platform.version()
20
+
21
+ if system == "Darwin":
22
+ system = "MacOS"
23
+ elif system == "Linux":
24
+ try:
25
+ with open("/etc/os-release") as f:
26
+ for line in f:
27
+ if line.startswith("NAME="):
28
+ name = line.split("=")[1].strip().strip('"')
29
+ if "Ubuntu" in name:
30
+ system = "Ubuntu"
31
+ elif "Debian" in name:
32
+ system = "Debian"
33
+ elif "Fedora" in name:
34
+ system = "Fedora"
35
+ elif "CentOS" in name:
36
+ system = "CentOS"
37
+ elif "Arch Linux" in name:
38
+ system = "Arch Linux"
39
+ system = name
40
+ except FileNotFoundError:
41
+ dist = platform.freedesktop_os_release()
42
+ if dist and "NAME" in dist:
43
+ name = dist["NAME"]
44
+ if "Ubuntu" in name:
45
+ system = "Ubuntu"
46
+ system = name
47
+ system = "Linux"
48
+ elif system == "Java":
49
+ system = "Java"
50
+
51
+ return system, release, version
52
+
53
+
54
+ def get_directory_structure(
55
+ path: str, max_depth: int = 3, include_filtered: bool = False
56
+ ) -> str:
57
+ """Get a directory structure similar to directory_tree tool.
58
+
59
+ Args:
60
+ path: The directory path to scan
61
+ max_depth: Maximum depth to traverse (0 for unlimited)
62
+ include_filtered: Whether to include normally filtered directories
63
+
64
+ Returns:
65
+ Formatted directory structure as a string
66
+ """
67
+ try:
68
+ dir_path = Path(path)
69
+
70
+ if not dir_path.exists() or not dir_path.is_dir():
71
+ return f"Error: {path} is not a valid directory"
72
+
73
+ # Define filtered directories (same as directory_tree.py)
74
+ FILTERED_DIRECTORIES = {
75
+ ".git",
76
+ "node_modules",
77
+ ".venv",
78
+ "venv",
79
+ "__pycache__",
80
+ ".pytest_cache",
81
+ ".idea",
82
+ ".vs",
83
+ ".vscode",
84
+ "dist",
85
+ "build",
86
+ "target",
87
+ ".ruff_cache",
88
+ ".llm-context",
89
+ }
90
+
91
+ def should_filter(current_path: Path) -> bool:
92
+ """Check if a directory should be filtered."""
93
+ # Don't filter if it's the explicitly requested path
94
+ if str(current_path.absolute()) == str(dir_path.absolute()):
95
+ return False
96
+ # Filter based on directory name if filtering is enabled
97
+ return current_path.name in FILTERED_DIRECTORIES and not include_filtered
98
+
99
+ def build_tree(current_path: Path, current_depth: int = 0) -> list[dict]:
100
+ """Build directory tree recursively."""
101
+ result = []
102
+
103
+ try:
104
+ # Sort entries: directories first, then files alphabetically
105
+ entries = sorted(
106
+ current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name)
107
+ )
108
+
109
+ for entry in entries:
110
+ if entry.is_dir():
111
+ entry_data = {"name": entry.name, "type": "directory"}
112
+
113
+ # Check if we should filter this directory
114
+ if should_filter(entry):
115
+ entry_data["skipped"] = "filtered-directory"
116
+ result.append(entry_data)
117
+ continue
118
+
119
+ # Check depth limit (if enabled)
120
+ if max_depth > 0 and current_depth >= max_depth:
121
+ entry_data["skipped"] = "depth-limit"
122
+ result.append(entry_data)
123
+ continue
124
+
125
+ # Process children recursively
126
+ entry_data["children"] = build_tree(entry, current_depth + 1)
127
+ result.append(entry_data)
128
+ else:
129
+ # Add files only if within depth limit
130
+ if max_depth <= 0 or current_depth < max_depth:
131
+ result.append({"name": entry.name, "type": "file"})
132
+
133
+ except Exception:
134
+ # Skip directories we can't read
135
+ pass
136
+
137
+ return result
138
+
139
+ def format_tree(tree_data: list[dict], level: int = 0) -> list[str]:
140
+ """Format tree data as indented strings."""
141
+ lines = []
142
+
143
+ for item in tree_data:
144
+ # Indentation based on level
145
+ indent = " " * level
146
+
147
+ # Format based on type
148
+ if item["type"] == "directory":
149
+ if "skipped" in item:
150
+ lines.append(
151
+ f"{indent}{item['name']}/ [skipped - {item['skipped']}]"
152
+ )
153
+ else:
154
+ lines.append(f"{indent}{item['name']}/")
155
+ # Add children with increased indentation if present
156
+ if "children" in item:
157
+ lines.extend(format_tree(item["children"], level + 1))
158
+ else:
159
+ # File
160
+ lines.append(f"{indent}{item['name']}")
161
+
162
+ return lines
163
+
164
+ # Build and format the tree
165
+ tree_data = build_tree(dir_path)
166
+ formatted_lines = format_tree(tree_data)
167
+
168
+ # Add the root directory path as a prefix
169
+ result = f"- {dir_path}/"
170
+ if formatted_lines:
171
+ result += "\n" + "\n".join(f" {line}" for line in formatted_lines)
172
+
173
+ return result
174
+
175
+ except Exception as e:
176
+ return f"Error generating directory structure: {str(e)}"
177
+
178
+
179
+ def get_git_info(path: str) -> dict[str, str | None]:
180
+ """Get git information for a repository.
181
+
182
+ Args:
183
+ path: Path to the git repository
184
+
185
+ Returns:
186
+ Dictionary containing git information
187
+ """
188
+ if not GIT_AVAILABLE:
189
+ return {
190
+ "current_branch": None,
191
+ "main_branch": None,
192
+ "git_status": "GitPython not available",
193
+ "recent_commits": "GitPython not available",
194
+ }
195
+
196
+ try:
197
+ repo = Repo(path)
198
+
199
+ # Get current branch
200
+ try:
201
+ current_branch = repo.active_branch.name
202
+ except Exception:
203
+ current_branch = "HEAD (detached)"
204
+
205
+ # Try to determine main branch
206
+ main_branch = "main" # default
207
+ try:
208
+ # Check if 'main' exists
209
+ if "origin/main" in [ref.name for ref in repo.refs]:
210
+ main_branch = "main"
211
+ elif "origin/master" in [ref.name for ref in repo.refs]:
212
+ main_branch = "master"
213
+ elif "main" in [ref.name for ref in repo.refs]:
214
+ main_branch = "main"
215
+ elif "master" in [ref.name for ref in repo.refs]:
216
+ main_branch = "master"
217
+ except Exception:
218
+ pass
219
+
220
+ # Get git status
221
+ try:
222
+ status_lines = []
223
+
224
+ # Check for staged changes
225
+ staged_files = list(repo.index.diff("HEAD"))
226
+ if staged_files:
227
+ for item in staged_files[:25]: # Limit to first 25
228
+ change_type = item.change_type
229
+ status_lines.append(f"{change_type[0].upper()} {item.a_path}")
230
+ if len(staged_files) > 25:
231
+ status_lines.append(
232
+ f"... and {len(staged_files) - 25} more staged files"
233
+ )
234
+
235
+ # Check for unstaged changes
236
+ unstaged_files = list(repo.index.diff(None))
237
+ if unstaged_files:
238
+ for item in unstaged_files[:25]: # Limit to first 25
239
+ status_lines.append(f"M {item.a_path}")
240
+ if len(unstaged_files) > 25:
241
+ status_lines.append(
242
+ f"... and {len(unstaged_files) - 25} more modified files"
243
+ )
244
+
245
+ # Check for untracked files
246
+ untracked_files = repo.untracked_files
247
+ if untracked_files:
248
+ for file in untracked_files[:25]: # Limit to first 25
249
+ status_lines.append(f"?? {file}")
250
+ if len(untracked_files) > 25:
251
+ status_lines.append(
252
+ f"... and {len(untracked_files) - 25} more untracked files"
253
+ )
254
+
255
+ git_status = (
256
+ "\n".join(status_lines) if status_lines else "Working tree clean"
257
+ )
258
+
259
+ except Exception:
260
+ git_status = "Unable to get git status"
261
+
262
+ # Get recent commits
263
+ try:
264
+ commits = []
265
+ for commit in repo.iter_commits(max_count=5):
266
+ short_hash = commit.hexsha[:7]
267
+ message = commit.message.split("\n")[0] # First line only
268
+ commits.append(f"{short_hash} {message}")
269
+ recent_commits = "\n".join(commits)
270
+ except Exception:
271
+ recent_commits = "Unable to get recent commits"
272
+
273
+ return {
274
+ "current_branch": current_branch,
275
+ "main_branch": main_branch,
276
+ "git_status": git_status,
277
+ "recent_commits": recent_commits,
278
+ }
279
+
280
+ except Exception as e:
281
+ return {
282
+ "current_branch": None,
283
+ "main_branch": None,
284
+ "git_status": f"Error: {str(e)}",
285
+ "recent_commits": f"Error: {str(e)}",
286
+ }