hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.1__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 +6 -0
- hanzo_mcp/__main__.py +1 -1
- hanzo_mcp/analytics/__init__.py +2 -2
- hanzo_mcp/analytics/posthog_analytics.py +76 -82
- hanzo_mcp/cli.py +31 -36
- hanzo_mcp/cli_enhanced.py +94 -72
- hanzo_mcp/cli_plugin.py +27 -17
- hanzo_mcp/config/__init__.py +2 -2
- hanzo_mcp/config/settings.py +112 -88
- hanzo_mcp/config/tool_config.py +32 -34
- hanzo_mcp/dev_server.py +66 -67
- hanzo_mcp/prompts/__init__.py +94 -12
- hanzo_mcp/prompts/enhanced_prompts.py +809 -0
- hanzo_mcp/prompts/example_custom_prompt.py +6 -5
- hanzo_mcp/prompts/project_todo_reminder.py +0 -1
- hanzo_mcp/prompts/tool_explorer.py +10 -7
- hanzo_mcp/server.py +17 -21
- hanzo_mcp/server_enhanced.py +15 -22
- hanzo_mcp/tools/__init__.py +56 -28
- hanzo_mcp/tools/agent/__init__.py +16 -19
- hanzo_mcp/tools/agent/agent.py +82 -65
- hanzo_mcp/tools/agent/agent_tool.py +152 -122
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
- hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
- hanzo_mcp/tools/agent/clarification_tool.py +11 -10
- hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
- hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
- hanzo_mcp/tools/agent/code_auth.py +102 -107
- hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
- hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
- hanzo_mcp/tools/agent/critic_tool.py +86 -73
- hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/iching_tool.py +404 -139
- hanzo_mcp/tools/agent/network_tool.py +89 -73
- hanzo_mcp/tools/agent/prompt.py +2 -1
- hanzo_mcp/tools/agent/review_tool.py +101 -98
- hanzo_mcp/tools/agent/swarm_alias.py +87 -0
- hanzo_mcp/tools/agent/swarm_tool.py +246 -161
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
- hanzo_mcp/tools/agent/tool_adapter.py +21 -11
- hanzo_mcp/tools/common/__init__.py +1 -1
- hanzo_mcp/tools/common/base.py +3 -5
- hanzo_mcp/tools/common/batch_tool.py +46 -39
- hanzo_mcp/tools/common/config_tool.py +120 -84
- hanzo_mcp/tools/common/context.py +1 -5
- hanzo_mcp/tools/common/context_fix.py +5 -3
- hanzo_mcp/tools/common/critic_tool.py +4 -8
- hanzo_mcp/tools/common/decorators.py +58 -56
- hanzo_mcp/tools/common/enhanced_base.py +29 -32
- hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
- hanzo_mcp/tools/common/forgiving_edit.py +91 -87
- hanzo_mcp/tools/common/mode.py +15 -17
- hanzo_mcp/tools/common/mode_loader.py +27 -24
- hanzo_mcp/tools/common/paginated_base.py +61 -53
- hanzo_mcp/tools/common/paginated_response.py +72 -79
- hanzo_mcp/tools/common/pagination.py +50 -53
- hanzo_mcp/tools/common/permissions.py +4 -4
- hanzo_mcp/tools/common/personality.py +186 -138
- hanzo_mcp/tools/common/plugin_loader.py +54 -54
- hanzo_mcp/tools/common/stats.py +65 -47
- hanzo_mcp/tools/common/test_helpers.py +31 -0
- hanzo_mcp/tools/common/thinking_tool.py +4 -8
- hanzo_mcp/tools/common/tool_disable.py +17 -12
- hanzo_mcp/tools/common/tool_enable.py +13 -14
- hanzo_mcp/tools/common/tool_list.py +36 -28
- hanzo_mcp/tools/common/truncate.py +23 -23
- hanzo_mcp/tools/config/__init__.py +4 -4
- hanzo_mcp/tools/config/config_tool.py +42 -29
- hanzo_mcp/tools/config/index_config.py +37 -34
- hanzo_mcp/tools/config/mode_tool.py +175 -55
- hanzo_mcp/tools/database/__init__.py +15 -12
- hanzo_mcp/tools/database/database_manager.py +77 -75
- hanzo_mcp/tools/database/graph.py +137 -91
- hanzo_mcp/tools/database/graph_add.py +30 -18
- hanzo_mcp/tools/database/graph_query.py +178 -102
- hanzo_mcp/tools/database/graph_remove.py +33 -28
- hanzo_mcp/tools/database/graph_search.py +97 -75
- hanzo_mcp/tools/database/graph_stats.py +91 -59
- hanzo_mcp/tools/database/sql.py +107 -79
- hanzo_mcp/tools/database/sql_query.py +30 -24
- hanzo_mcp/tools/database/sql_search.py +29 -25
- hanzo_mcp/tools/database/sql_stats.py +47 -35
- hanzo_mcp/tools/editor/neovim_command.py +25 -28
- hanzo_mcp/tools/editor/neovim_edit.py +21 -23
- hanzo_mcp/tools/editor/neovim_session.py +60 -54
- hanzo_mcp/tools/filesystem/__init__.py +31 -30
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
- hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +316 -224
- hanzo_mcp/tools/filesystem/content_replace.py +4 -4
- hanzo_mcp/tools/filesystem/diff.py +71 -59
- hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
- hanzo_mcp/tools/filesystem/edit.py +4 -4
- hanzo_mcp/tools/filesystem/find.py +173 -80
- hanzo_mcp/tools/filesystem/find_files.py +73 -52
- hanzo_mcp/tools/filesystem/git_search.py +157 -104
- hanzo_mcp/tools/filesystem/grep.py +8 -8
- hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
- hanzo_mcp/tools/filesystem/read.py +12 -10
- hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
- hanzo_mcp/tools/filesystem/search_tool.py +263 -207
- hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
- hanzo_mcp/tools/filesystem/tree.py +35 -33
- hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
- hanzo_mcp/tools/filesystem/watch.py +37 -36
- hanzo_mcp/tools/filesystem/write.py +4 -8
- hanzo_mcp/tools/jupyter/__init__.py +4 -4
- hanzo_mcp/tools/jupyter/base.py +4 -5
- hanzo_mcp/tools/jupyter/jupyter.py +67 -47
- hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
- hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
- hanzo_mcp/tools/llm/__init__.py +5 -7
- hanzo_mcp/tools/llm/consensus_tool.py +72 -52
- hanzo_mcp/tools/llm/llm_manage.py +101 -60
- hanzo_mcp/tools/llm/llm_tool.py +226 -166
- hanzo_mcp/tools/llm/provider_tools.py +25 -26
- hanzo_mcp/tools/lsp/__init__.py +1 -1
- hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
- hanzo_mcp/tools/mcp/__init__.py +2 -3
- hanzo_mcp/tools/mcp/mcp_add.py +27 -25
- hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
- hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
- hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
- hanzo_mcp/tools/memory/__init__.py +39 -21
- hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
- hanzo_mcp/tools/memory/memory_tools.py +90 -108
- hanzo_mcp/tools/search/__init__.py +7 -2
- hanzo_mcp/tools/search/find_tool.py +297 -212
- hanzo_mcp/tools/search/unified_search.py +366 -314
- hanzo_mcp/tools/shell/__init__.py +8 -7
- hanzo_mcp/tools/shell/auto_background.py +56 -49
- hanzo_mcp/tools/shell/base.py +1 -1
- hanzo_mcp/tools/shell/base_process.py +75 -75
- hanzo_mcp/tools/shell/bash_session.py +2 -2
- hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
- hanzo_mcp/tools/shell/bash_tool.py +24 -31
- hanzo_mcp/tools/shell/command_executor.py +12 -12
- hanzo_mcp/tools/shell/logs.py +43 -33
- hanzo_mcp/tools/shell/npx.py +13 -13
- hanzo_mcp/tools/shell/npx_background.py +24 -21
- hanzo_mcp/tools/shell/npx_tool.py +18 -22
- hanzo_mcp/tools/shell/open.py +19 -21
- hanzo_mcp/tools/shell/pkill.py +31 -26
- hanzo_mcp/tools/shell/process_tool.py +32 -32
- hanzo_mcp/tools/shell/processes.py +57 -58
- hanzo_mcp/tools/shell/run_background.py +24 -25
- hanzo_mcp/tools/shell/run_command.py +5 -5
- hanzo_mcp/tools/shell/run_command_windows.py +5 -5
- hanzo_mcp/tools/shell/session_storage.py +3 -3
- hanzo_mcp/tools/shell/streaming_command.py +141 -126
- hanzo_mcp/tools/shell/uvx.py +24 -25
- hanzo_mcp/tools/shell/uvx_background.py +35 -33
- hanzo_mcp/tools/shell/uvx_tool.py +18 -22
- hanzo_mcp/tools/todo/__init__.py +6 -2
- hanzo_mcp/tools/todo/todo.py +50 -37
- hanzo_mcp/tools/todo/todo_read.py +5 -8
- hanzo_mcp/tools/todo/todo_write.py +5 -7
- hanzo_mcp/tools/vector/__init__.py +40 -28
- hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
- hanzo_mcp/tools/vector/git_ingester.py +170 -179
- hanzo_mcp/tools/vector/index_tool.py +96 -44
- hanzo_mcp/tools/vector/infinity_store.py +283 -228
- hanzo_mcp/tools/vector/mock_infinity.py +39 -40
- hanzo_mcp/tools/vector/project_manager.py +88 -78
- hanzo_mcp/tools/vector/vector.py +59 -42
- hanzo_mcp/tools/vector/vector_index.py +30 -27
- hanzo_mcp/tools/vector/vector_search.py +64 -45
- hanzo_mcp/types.py +6 -4
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.1.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/top_level.txt +0 -0
|
@@ -1,51 +1,52 @@
|
|
|
1
1
|
"""Database manager for project-specific SQLite and graph databases."""
|
|
2
2
|
|
|
3
|
-
import sqlite3
|
|
4
3
|
import os
|
|
5
|
-
import
|
|
4
|
+
import sqlite3
|
|
5
|
+
from typing import Dict, List, Optional
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Dict, Any, Optional, Tuple, List
|
|
8
|
-
from datetime import datetime
|
|
9
7
|
|
|
10
8
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
11
9
|
|
|
12
10
|
|
|
13
11
|
class ProjectDatabase:
|
|
14
12
|
"""Manages SQLite and graph databases for a project."""
|
|
15
|
-
|
|
13
|
+
|
|
16
14
|
def __init__(self, project_path: str):
|
|
17
15
|
self.project_path = Path(project_path)
|
|
18
16
|
self.db_dir = self.project_path / ".hanzo" / "db"
|
|
19
17
|
self.db_dir.mkdir(parents=True, exist_ok=True)
|
|
20
|
-
|
|
18
|
+
|
|
21
19
|
# SQLite database path
|
|
22
20
|
self.sqlite_path = self.db_dir / "project.db"
|
|
23
21
|
self.graph_path = self.db_dir / "graph.db"
|
|
24
|
-
|
|
22
|
+
|
|
25
23
|
# Initialize databases
|
|
26
24
|
self._init_sqlite()
|
|
27
25
|
self._init_graph()
|
|
28
|
-
|
|
26
|
+
|
|
29
27
|
# Keep graph in memory for performance
|
|
30
28
|
self.graph_conn = sqlite3.connect(":memory:")
|
|
31
29
|
self._init_graph_schema(self.graph_conn)
|
|
32
30
|
self._load_graph_from_disk()
|
|
33
|
-
|
|
31
|
+
|
|
34
32
|
def _init_sqlite(self):
|
|
35
33
|
"""Initialize SQLite database with common tables."""
|
|
36
34
|
conn = sqlite3.connect(self.sqlite_path)
|
|
37
35
|
try:
|
|
38
36
|
# Create metadata table
|
|
39
|
-
conn.execute(
|
|
37
|
+
conn.execute(
|
|
38
|
+
"""
|
|
40
39
|
CREATE TABLE IF NOT EXISTS metadata (
|
|
41
40
|
key TEXT PRIMARY KEY,
|
|
42
41
|
value TEXT,
|
|
43
42
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
44
43
|
)
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
"""
|
|
45
|
+
)
|
|
46
|
+
|
|
47
47
|
# Create files table
|
|
48
|
-
conn.execute(
|
|
48
|
+
conn.execute(
|
|
49
|
+
"""
|
|
49
50
|
CREATE TABLE IF NOT EXISTS files (
|
|
50
51
|
path TEXT PRIMARY KEY,
|
|
51
52
|
content TEXT,
|
|
@@ -54,10 +55,12 @@ class ProjectDatabase:
|
|
|
54
55
|
hash TEXT,
|
|
55
56
|
metadata TEXT
|
|
56
57
|
)
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
"""
|
|
59
|
+
)
|
|
60
|
+
|
|
59
61
|
# Create symbols table
|
|
60
|
-
conn.execute(
|
|
62
|
+
conn.execute(
|
|
63
|
+
"""
|
|
61
64
|
CREATE TABLE IF NOT EXISTS symbols (
|
|
62
65
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
63
66
|
file_path TEXT,
|
|
@@ -69,17 +72,18 @@ class ProjectDatabase:
|
|
|
69
72
|
signature TEXT,
|
|
70
73
|
FOREIGN KEY (file_path) REFERENCES files(path)
|
|
71
74
|
)
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
"""
|
|
76
|
+
)
|
|
77
|
+
|
|
74
78
|
# Create index for fast searches
|
|
75
|
-
conn.execute(
|
|
76
|
-
conn.execute(
|
|
77
|
-
conn.execute(
|
|
78
|
-
|
|
79
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_files_path ON files(path)")
|
|
80
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)")
|
|
81
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_symbols_type ON symbols(type)")
|
|
82
|
+
|
|
79
83
|
conn.commit()
|
|
80
84
|
finally:
|
|
81
85
|
conn.close()
|
|
82
|
-
|
|
86
|
+
|
|
83
87
|
def _init_graph(self):
|
|
84
88
|
"""Initialize graph database on disk."""
|
|
85
89
|
conn = sqlite3.connect(self.graph_path)
|
|
@@ -88,21 +92,24 @@ class ProjectDatabase:
|
|
|
88
92
|
conn.commit()
|
|
89
93
|
finally:
|
|
90
94
|
conn.close()
|
|
91
|
-
|
|
95
|
+
|
|
92
96
|
def _init_graph_schema(self, conn: sqlite3.Connection):
|
|
93
97
|
"""Initialize graph database schema."""
|
|
94
98
|
# Nodes table
|
|
95
|
-
conn.execute(
|
|
99
|
+
conn.execute(
|
|
100
|
+
"""
|
|
96
101
|
CREATE TABLE IF NOT EXISTS nodes (
|
|
97
102
|
id TEXT PRIMARY KEY,
|
|
98
103
|
type TEXT NOT NULL,
|
|
99
104
|
properties TEXT,
|
|
100
105
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
101
106
|
)
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
"""
|
|
108
|
+
)
|
|
109
|
+
|
|
104
110
|
# Edges table
|
|
105
|
-
conn.execute(
|
|
111
|
+
conn.execute(
|
|
112
|
+
"""
|
|
106
113
|
CREATE TABLE IF NOT EXISTS edges (
|
|
107
114
|
source TEXT NOT NULL,
|
|
108
115
|
target TEXT NOT NULL,
|
|
@@ -114,72 +121,67 @@ class ProjectDatabase:
|
|
|
114
121
|
FOREIGN KEY (source) REFERENCES nodes(id),
|
|
115
122
|
FOREIGN KEY (target) REFERENCES nodes(id)
|
|
116
123
|
)
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
"""
|
|
125
|
+
)
|
|
126
|
+
|
|
119
127
|
# Indexes for graph traversal
|
|
120
|
-
conn.execute(
|
|
121
|
-
conn.execute(
|
|
122
|
-
conn.execute(
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source)")
|
|
129
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target)")
|
|
130
|
+
conn.execute(
|
|
131
|
+
"CREATE INDEX IF NOT EXISTS idx_edges_relationship ON edges(relationship)"
|
|
132
|
+
)
|
|
133
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type)")
|
|
134
|
+
|
|
125
135
|
def _load_graph_from_disk(self):
|
|
126
136
|
"""Load graph from disk into memory."""
|
|
127
137
|
disk_conn = sqlite3.connect(self.graph_path)
|
|
128
138
|
try:
|
|
129
139
|
# Copy nodes
|
|
130
|
-
nodes = disk_conn.execute(
|
|
140
|
+
nodes = disk_conn.execute("SELECT * FROM nodes").fetchall()
|
|
131
141
|
self.graph_conn.executemany(
|
|
132
|
-
|
|
133
|
-
nodes
|
|
142
|
+
"INSERT OR REPLACE INTO nodes VALUES (?, ?, ?, ?)", nodes
|
|
134
143
|
)
|
|
135
|
-
|
|
144
|
+
|
|
136
145
|
# Copy edges
|
|
137
|
-
edges = disk_conn.execute(
|
|
146
|
+
edges = disk_conn.execute("SELECT * FROM edges").fetchall()
|
|
138
147
|
self.graph_conn.executemany(
|
|
139
|
-
|
|
140
|
-
edges
|
|
148
|
+
"INSERT OR REPLACE INTO edges VALUES (?, ?, ?, ?, ?, ?)", edges
|
|
141
149
|
)
|
|
142
|
-
|
|
150
|
+
|
|
143
151
|
self.graph_conn.commit()
|
|
144
152
|
finally:
|
|
145
153
|
disk_conn.close()
|
|
146
|
-
|
|
154
|
+
|
|
147
155
|
def _save_graph_to_disk(self):
|
|
148
156
|
"""Save in-memory graph to disk."""
|
|
149
157
|
disk_conn = sqlite3.connect(self.graph_path)
|
|
150
158
|
try:
|
|
151
159
|
# Clear existing data
|
|
152
|
-
disk_conn.execute(
|
|
153
|
-
disk_conn.execute(
|
|
154
|
-
|
|
160
|
+
disk_conn.execute("DELETE FROM edges")
|
|
161
|
+
disk_conn.execute("DELETE FROM nodes")
|
|
162
|
+
|
|
155
163
|
# Copy nodes
|
|
156
|
-
nodes = self.graph_conn.execute(
|
|
157
|
-
disk_conn.executemany(
|
|
158
|
-
|
|
159
|
-
nodes
|
|
160
|
-
)
|
|
161
|
-
|
|
164
|
+
nodes = self.graph_conn.execute("SELECT * FROM nodes").fetchall()
|
|
165
|
+
disk_conn.executemany("INSERT INTO nodes VALUES (?, ?, ?, ?)", nodes)
|
|
166
|
+
|
|
162
167
|
# Copy edges
|
|
163
|
-
edges = self.graph_conn.execute(
|
|
164
|
-
disk_conn.executemany(
|
|
165
|
-
|
|
166
|
-
edges
|
|
167
|
-
)
|
|
168
|
-
|
|
168
|
+
edges = self.graph_conn.execute("SELECT * FROM edges").fetchall()
|
|
169
|
+
disk_conn.executemany("INSERT INTO edges VALUES (?, ?, ?, ?, ?, ?)", edges)
|
|
170
|
+
|
|
169
171
|
disk_conn.commit()
|
|
170
172
|
finally:
|
|
171
173
|
disk_conn.close()
|
|
172
|
-
|
|
174
|
+
|
|
173
175
|
def get_sqlite_connection(self) -> sqlite3.Connection:
|
|
174
176
|
"""Get SQLite connection."""
|
|
175
177
|
conn = sqlite3.connect(self.sqlite_path)
|
|
176
178
|
conn.row_factory = sqlite3.Row
|
|
177
179
|
return conn
|
|
178
|
-
|
|
180
|
+
|
|
179
181
|
def get_graph_connection(self) -> sqlite3.Connection:
|
|
180
182
|
"""Get in-memory graph connection."""
|
|
181
183
|
return self.graph_conn
|
|
182
|
-
|
|
184
|
+
|
|
183
185
|
def close(self):
|
|
184
186
|
"""Close connections and save graph to disk."""
|
|
185
187
|
self._save_graph_to_disk()
|
|
@@ -188,59 +190,59 @@ class ProjectDatabase:
|
|
|
188
190
|
|
|
189
191
|
class DatabaseManager:
|
|
190
192
|
"""Manages databases for multiple projects."""
|
|
191
|
-
|
|
193
|
+
|
|
192
194
|
def __init__(self, permission_manager: PermissionManager):
|
|
193
195
|
self.permission_manager = permission_manager
|
|
194
196
|
self.projects: Dict[str, ProjectDatabase] = {}
|
|
195
197
|
self.search_paths: List[str] = []
|
|
196
|
-
|
|
198
|
+
|
|
197
199
|
def add_search_path(self, path: str):
|
|
198
200
|
"""Add a path to search for projects."""
|
|
199
201
|
if path not in self.search_paths:
|
|
200
202
|
self.search_paths.append(path)
|
|
201
|
-
|
|
203
|
+
|
|
202
204
|
def get_project_db(self, project_path: str) -> ProjectDatabase:
|
|
203
205
|
"""Get or create database for a project."""
|
|
204
206
|
project_path = os.path.abspath(project_path)
|
|
205
|
-
|
|
207
|
+
|
|
206
208
|
# Check permissions
|
|
207
209
|
if not self.permission_manager.has_permission(project_path):
|
|
208
210
|
raise PermissionError(f"No permission to access: {project_path}")
|
|
209
|
-
|
|
211
|
+
|
|
210
212
|
# Create database if not exists
|
|
211
213
|
if project_path not in self.projects:
|
|
212
214
|
self.projects[project_path] = ProjectDatabase(project_path)
|
|
213
|
-
|
|
215
|
+
|
|
214
216
|
return self.projects[project_path]
|
|
215
|
-
|
|
217
|
+
|
|
216
218
|
def get_project_for_path(self, file_path: str) -> Optional[ProjectDatabase]:
|
|
217
219
|
"""Find the project database for a given file path."""
|
|
218
220
|
file_path = os.path.abspath(file_path)
|
|
219
|
-
|
|
221
|
+
|
|
220
222
|
# Check if file is in a known project
|
|
221
223
|
for project_path in self.projects:
|
|
222
224
|
if file_path.startswith(project_path):
|
|
223
225
|
return self.projects[project_path]
|
|
224
|
-
|
|
226
|
+
|
|
225
227
|
# Search up the directory tree for a project
|
|
226
228
|
current = Path(file_path)
|
|
227
229
|
if current.is_file():
|
|
228
230
|
current = current.parent
|
|
229
|
-
|
|
231
|
+
|
|
230
232
|
while current != current.parent:
|
|
231
233
|
# Check for project markers
|
|
232
234
|
if (current / ".git").exists() or (current / "LLM.md").exists():
|
|
233
235
|
return self.get_project_db(str(current))
|
|
234
236
|
current = current.parent
|
|
235
|
-
|
|
237
|
+
|
|
236
238
|
# No project found, use the directory of the file
|
|
237
239
|
if Path(file_path).is_file():
|
|
238
240
|
return self.get_project_db(str(Path(file_path).parent))
|
|
239
241
|
else:
|
|
240
242
|
return self.get_project_db(file_path)
|
|
241
|
-
|
|
243
|
+
|
|
242
244
|
def close_all(self):
|
|
243
245
|
"""Close all project databases."""
|
|
244
246
|
for db in self.projects.values():
|
|
245
247
|
db.close()
|
|
246
|
-
self.projects.clear()
|
|
248
|
+
self.projects.clear()
|