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.
- hanzo_mcp/__init__.py +1 -1
- hanzo_mcp/cli.py +118 -170
- hanzo_mcp/cli_enhanced.py +438 -0
- hanzo_mcp/config/__init__.py +19 -0
- hanzo_mcp/config/settings.py +388 -0
- hanzo_mcp/config/tool_config.py +197 -0
- hanzo_mcp/prompts/__init__.py +117 -0
- hanzo_mcp/prompts/compact_conversation.py +77 -0
- hanzo_mcp/prompts/create_release.py +38 -0
- hanzo_mcp/prompts/project_system.py +120 -0
- hanzo_mcp/prompts/project_todo_reminder.py +111 -0
- hanzo_mcp/prompts/utils.py +286 -0
- hanzo_mcp/server.py +117 -99
- hanzo_mcp/tools/__init__.py +105 -32
- hanzo_mcp/tools/agent/__init__.py +8 -11
- hanzo_mcp/tools/agent/agent_tool.py +290 -224
- hanzo_mcp/tools/agent/prompt.py +16 -13
- hanzo_mcp/tools/agent/tool_adapter.py +9 -9
- hanzo_mcp/tools/common/__init__.py +17 -16
- hanzo_mcp/tools/common/base.py +79 -110
- hanzo_mcp/tools/common/batch_tool.py +330 -0
- hanzo_mcp/tools/common/context.py +26 -292
- hanzo_mcp/tools/common/permissions.py +12 -12
- hanzo_mcp/tools/common/thinking_tool.py +153 -0
- hanzo_mcp/tools/common/validation.py +1 -63
- hanzo_mcp/tools/filesystem/__init__.py +88 -57
- hanzo_mcp/tools/filesystem/base.py +32 -24
- hanzo_mcp/tools/filesystem/content_replace.py +114 -107
- hanzo_mcp/tools/filesystem/directory_tree.py +129 -105
- hanzo_mcp/tools/filesystem/edit.py +279 -0
- hanzo_mcp/tools/filesystem/grep.py +458 -0
- hanzo_mcp/tools/filesystem/grep_ast_tool.py +250 -0
- hanzo_mcp/tools/filesystem/multi_edit.py +362 -0
- hanzo_mcp/tools/filesystem/read.py +255 -0
- hanzo_mcp/tools/filesystem/write.py +156 -0
- hanzo_mcp/tools/jupyter/__init__.py +41 -29
- hanzo_mcp/tools/jupyter/base.py +66 -57
- hanzo_mcp/tools/jupyter/{edit_notebook.py → notebook_edit.py} +162 -139
- hanzo_mcp/tools/jupyter/notebook_read.py +152 -0
- hanzo_mcp/tools/shell/__init__.py +29 -20
- hanzo_mcp/tools/shell/base.py +87 -45
- hanzo_mcp/tools/shell/bash_session.py +731 -0
- hanzo_mcp/tools/shell/bash_session_executor.py +295 -0
- hanzo_mcp/tools/shell/command_executor.py +435 -384
- hanzo_mcp/tools/shell/run_command.py +284 -131
- hanzo_mcp/tools/shell/run_command_windows.py +328 -0
- hanzo_mcp/tools/shell/session_manager.py +196 -0
- hanzo_mcp/tools/shell/session_storage.py +325 -0
- hanzo_mcp/tools/todo/__init__.py +66 -0
- hanzo_mcp/tools/todo/base.py +319 -0
- hanzo_mcp/tools/todo/todo_read.py +148 -0
- hanzo_mcp/tools/todo/todo_write.py +378 -0
- hanzo_mcp/tools/vector/__init__.py +95 -0
- hanzo_mcp/tools/vector/infinity_store.py +365 -0
- hanzo_mcp/tools/vector/project_manager.py +361 -0
- hanzo_mcp/tools/vector/vector_index.py +115 -0
- hanzo_mcp/tools/vector/vector_search.py +215 -0
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/METADATA +33 -1
- hanzo_mcp-0.5.0.dist-info/RECORD +63 -0
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/WHEEL +1 -1
- hanzo_mcp/tools/agent/base_provider.py +0 -73
- hanzo_mcp/tools/agent/litellm_provider.py +0 -45
- hanzo_mcp/tools/agent/lmstudio_agent.py +0 -385
- hanzo_mcp/tools/agent/lmstudio_provider.py +0 -219
- hanzo_mcp/tools/agent/provider_registry.py +0 -120
- hanzo_mcp/tools/common/error_handling.py +0 -86
- hanzo_mcp/tools/common/logging_config.py +0 -115
- hanzo_mcp/tools/common/session.py +0 -91
- hanzo_mcp/tools/common/think_tool.py +0 -123
- hanzo_mcp/tools/common/version_tool.py +0 -120
- hanzo_mcp/tools/filesystem/edit_file.py +0 -287
- hanzo_mcp/tools/filesystem/get_file_info.py +0 -170
- hanzo_mcp/tools/filesystem/read_files.py +0 -199
- hanzo_mcp/tools/filesystem/search_content.py +0 -275
- hanzo_mcp/tools/filesystem/write_file.py +0 -162
- hanzo_mcp/tools/jupyter/notebook_operations.py +0 -514
- hanzo_mcp/tools/jupyter/read_notebook.py +0 -165
- hanzo_mcp/tools/project/__init__.py +0 -64
- hanzo_mcp/tools/project/analysis.py +0 -886
- hanzo_mcp/tools/project/base.py +0 -66
- hanzo_mcp/tools/project/project_analyze.py +0 -173
- hanzo_mcp/tools/shell/run_script.py +0 -215
- hanzo_mcp/tools/shell/script_tool.py +0 -244
- hanzo_mcp-0.3.8.dist-info/RECORD +0 -53
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
}
|