hanzo-mcp 0.7.6__py3-none-any.whl → 0.8.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 +7 -1
- 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.6.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.6.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -1,110 +1,109 @@
|
|
|
1
1
|
"""Mock implementation of infinity_embedded for testing on unsupported platforms."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
|
-
import hashlib
|
|
5
3
|
import random
|
|
4
|
+
from typing import Any, Dict, List
|
|
6
5
|
from pathlib import Path
|
|
7
|
-
from typing import Dict, List, Any, Optional
|
|
8
|
-
from datetime import datetime
|
|
9
6
|
|
|
10
7
|
|
|
11
8
|
class MockTable:
|
|
12
9
|
"""Mock implementation of an Infinity table."""
|
|
13
|
-
|
|
10
|
+
|
|
14
11
|
def __init__(self, name: str, schema: Dict[str, Any]):
|
|
15
12
|
self.name = name
|
|
16
13
|
self.schema = schema
|
|
17
14
|
self.data = []
|
|
18
15
|
self._id_counter = 0
|
|
19
|
-
|
|
16
|
+
|
|
20
17
|
def insert(self, records: List[Dict[str, Any]]):
|
|
21
18
|
"""Insert records into the table."""
|
|
22
19
|
for record in records:
|
|
23
20
|
# Add an internal ID if not present
|
|
24
|
-
if
|
|
25
|
-
record[
|
|
21
|
+
if "id" not in record:
|
|
22
|
+
record["_internal_id"] = self._id_counter
|
|
26
23
|
self._id_counter += 1
|
|
27
24
|
self.data.append(record)
|
|
28
|
-
|
|
25
|
+
|
|
29
26
|
def delete(self, condition: str):
|
|
30
27
|
"""Delete records matching condition."""
|
|
31
28
|
# Simple implementation - just clear for now
|
|
32
29
|
self.data = [r for r in self.data if not self._eval_condition(r, condition)]
|
|
33
|
-
|
|
30
|
+
|
|
34
31
|
def output(self, columns: List[str]):
|
|
35
32
|
"""Start a query chain."""
|
|
36
33
|
return MockQuery(self, columns)
|
|
37
|
-
|
|
34
|
+
|
|
38
35
|
def _eval_condition(self, record: Dict[str, Any], condition: str) -> bool:
|
|
39
36
|
"""Evaluate a simple condition."""
|
|
40
37
|
# Very basic implementation
|
|
41
|
-
if
|
|
42
|
-
field, value = condition.split(
|
|
38
|
+
if "=" in condition:
|
|
39
|
+
field, value = condition.split("=", 1)
|
|
43
40
|
field = field.strip()
|
|
44
41
|
value = value.strip().strip("'\"")
|
|
45
|
-
return str(record.get(field,
|
|
42
|
+
return str(record.get(field, "")) == value
|
|
46
43
|
return False
|
|
47
44
|
|
|
48
45
|
|
|
49
46
|
class MockQuery:
|
|
50
47
|
"""Mock query builder."""
|
|
51
|
-
|
|
48
|
+
|
|
52
49
|
def __init__(self, table: MockTable, columns: List[str]):
|
|
53
50
|
self.table = table
|
|
54
51
|
self.columns = columns
|
|
55
52
|
self.filters = []
|
|
56
53
|
self.vector_search = None
|
|
57
54
|
self.limit_value = None
|
|
58
|
-
|
|
55
|
+
|
|
59
56
|
def filter(self, condition: str):
|
|
60
57
|
"""Add a filter condition."""
|
|
61
58
|
self.filters.append(condition)
|
|
62
59
|
return self
|
|
63
|
-
|
|
64
|
-
def match_dense(
|
|
60
|
+
|
|
61
|
+
def match_dense(
|
|
62
|
+
self, column: str, vector: List[float], dtype: str, metric: str, limit: int
|
|
63
|
+
):
|
|
65
64
|
"""Add vector search."""
|
|
66
65
|
self.vector_search = {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
"column": column,
|
|
67
|
+
"vector": vector,
|
|
68
|
+
"dtype": dtype,
|
|
69
|
+
"metric": metric,
|
|
70
|
+
"limit": limit,
|
|
72
71
|
}
|
|
73
72
|
self.limit_value = limit
|
|
74
73
|
return self
|
|
75
|
-
|
|
74
|
+
|
|
76
75
|
def to_pl(self):
|
|
77
76
|
"""Execute query and return polars-like result."""
|
|
78
77
|
results = self.table.data.copy()
|
|
79
|
-
|
|
78
|
+
|
|
80
79
|
# Apply filters
|
|
81
80
|
for condition in self.filters:
|
|
82
81
|
results = [r for r in results if self.table._eval_condition(r, condition)]
|
|
83
|
-
|
|
82
|
+
|
|
84
83
|
# Apply vector search (mock similarity)
|
|
85
84
|
if self.vector_search:
|
|
86
85
|
# Add mock scores
|
|
87
86
|
for r in results:
|
|
88
|
-
r[
|
|
87
|
+
r["score"] = random.uniform(0.5, 1.0)
|
|
89
88
|
# Sort by score
|
|
90
|
-
results.sort(key=lambda x: x.get(
|
|
89
|
+
results.sort(key=lambda x: x.get("score", 0), reverse=True)
|
|
91
90
|
# Limit results
|
|
92
91
|
if self.limit_value:
|
|
93
|
-
results = results[:self.limit_value]
|
|
94
|
-
|
|
92
|
+
results = results[: self.limit_value]
|
|
93
|
+
|
|
95
94
|
# Return mock polars DataFrame
|
|
96
95
|
return MockDataFrame(results)
|
|
97
96
|
|
|
98
97
|
|
|
99
98
|
class MockDataFrame:
|
|
100
99
|
"""Mock polars DataFrame."""
|
|
101
|
-
|
|
100
|
+
|
|
102
101
|
def __init__(self, data: List[Dict[str, Any]]):
|
|
103
102
|
self.data = data
|
|
104
|
-
|
|
103
|
+
|
|
105
104
|
def __len__(self):
|
|
106
105
|
return len(self.data)
|
|
107
|
-
|
|
106
|
+
|
|
108
107
|
def iter_rows(self, named: bool = False):
|
|
109
108
|
"""Iterate over rows."""
|
|
110
109
|
if named:
|
|
@@ -119,17 +118,17 @@ class MockDataFrame:
|
|
|
119
118
|
|
|
120
119
|
class MockDatabase:
|
|
121
120
|
"""Mock implementation of an Infinity database."""
|
|
122
|
-
|
|
121
|
+
|
|
123
122
|
def __init__(self, name: str):
|
|
124
123
|
self.name = name
|
|
125
124
|
self.tables = {}
|
|
126
|
-
|
|
125
|
+
|
|
127
126
|
def create_table(self, name: str, schema: Dict[str, Any]) -> MockTable:
|
|
128
127
|
"""Create a new table."""
|
|
129
128
|
table = MockTable(name, schema)
|
|
130
129
|
self.tables[name] = table
|
|
131
130
|
return table
|
|
132
|
-
|
|
131
|
+
|
|
133
132
|
def get_table(self, name: str) -> MockTable:
|
|
134
133
|
"""Get an existing table."""
|
|
135
134
|
if name not in self.tables:
|
|
@@ -139,19 +138,19 @@ class MockDatabase:
|
|
|
139
138
|
|
|
140
139
|
class MockInfinity:
|
|
141
140
|
"""Mock implementation of Infinity connection."""
|
|
142
|
-
|
|
141
|
+
|
|
143
142
|
def __init__(self, path: str):
|
|
144
143
|
self.path = Path(path)
|
|
145
144
|
self.databases = {}
|
|
146
145
|
# Ensure directory exists
|
|
147
146
|
self.path.mkdir(parents=True, exist_ok=True)
|
|
148
|
-
|
|
147
|
+
|
|
149
148
|
def get_database(self, name: str) -> MockDatabase:
|
|
150
149
|
"""Get or create a database."""
|
|
151
150
|
if name not in self.databases:
|
|
152
151
|
self.databases[name] = MockDatabase(name)
|
|
153
152
|
return self.databases[name]
|
|
154
|
-
|
|
153
|
+
|
|
155
154
|
def disconnect(self):
|
|
156
155
|
"""Disconnect from Infinity."""
|
|
157
156
|
pass
|
|
@@ -159,4 +158,4 @@ class MockInfinity:
|
|
|
159
158
|
|
|
160
159
|
def connect(path: str) -> MockInfinity:
|
|
161
160
|
"""Connect to Infinity (mock implementation)."""
|
|
162
|
-
return MockInfinity(path)
|
|
161
|
+
return MockInfinity(path)
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
"""Project-aware vector database management for Hanzo AI."""
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Dict, List, Tuple, Optional
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from typing import Dict, List, Optional, Set, Tuple, Any
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
import asyncio
|
|
8
7
|
from concurrent.futures import ThreadPoolExecutor
|
|
9
8
|
|
|
10
|
-
from .
|
|
11
|
-
|
|
9
|
+
from hanzo_mcp.tools.config.index_config import IndexScope, IndexConfig
|
|
10
|
+
|
|
11
|
+
from .infinity_store import SearchResult, InfinityVectorStore
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@dataclass
|
|
15
15
|
class ProjectInfo:
|
|
16
16
|
"""Information about a detected project."""
|
|
17
|
+
|
|
17
18
|
root_path: Path
|
|
18
19
|
llm_md_path: Path
|
|
19
20
|
db_path: Path
|
|
@@ -22,7 +23,7 @@ class ProjectInfo:
|
|
|
22
23
|
|
|
23
24
|
class ProjectVectorManager:
|
|
24
25
|
"""Manages project-aware vector databases."""
|
|
25
|
-
|
|
26
|
+
|
|
26
27
|
def __init__(
|
|
27
28
|
self,
|
|
28
29
|
global_db_path: Optional[str] = None,
|
|
@@ -30,7 +31,7 @@ class ProjectVectorManager:
|
|
|
30
31
|
dimension: int = 1536,
|
|
31
32
|
):
|
|
32
33
|
"""Initialize the project vector manager.
|
|
33
|
-
|
|
34
|
+
|
|
34
35
|
Args:
|
|
35
36
|
global_db_path: Path for global vector store (default: ~/.config/hanzo/db)
|
|
36
37
|
embedding_model: Embedding model to use
|
|
@@ -38,26 +39,26 @@ class ProjectVectorManager:
|
|
|
38
39
|
"""
|
|
39
40
|
self.embedding_model = embedding_model
|
|
40
41
|
self.dimension = dimension
|
|
41
|
-
|
|
42
|
+
|
|
42
43
|
# Set up index configuration
|
|
43
44
|
self.index_config = IndexConfig()
|
|
44
|
-
|
|
45
|
+
|
|
45
46
|
# Set up global database path
|
|
46
47
|
if global_db_path:
|
|
47
48
|
self.global_db_path = Path(global_db_path)
|
|
48
49
|
else:
|
|
49
50
|
self.global_db_path = self.index_config.get_index_path("vector")
|
|
50
|
-
|
|
51
|
+
|
|
51
52
|
self.global_db_path.mkdir(parents=True, exist_ok=True)
|
|
52
|
-
|
|
53
|
+
|
|
53
54
|
# Cache for project info and vector stores
|
|
54
55
|
self.projects: Dict[str, ProjectInfo] = {}
|
|
55
56
|
self.vector_stores: Dict[str, InfinityVectorStore] = {}
|
|
56
57
|
self._global_store: Optional[InfinityVectorStore] = None
|
|
57
|
-
|
|
58
|
+
|
|
58
59
|
# Thread pool for parallel operations
|
|
59
60
|
self.executor = ThreadPoolExecutor(max_workers=4)
|
|
60
|
-
|
|
61
|
+
|
|
61
62
|
def _get_global_store(self) -> InfinityVectorStore:
|
|
62
63
|
"""Get or create the global vector store."""
|
|
63
64
|
if self._global_store is None:
|
|
@@ -67,56 +68,56 @@ class ProjectVectorManager:
|
|
|
67
68
|
dimension=self.dimension,
|
|
68
69
|
)
|
|
69
70
|
return self._global_store
|
|
70
|
-
|
|
71
|
+
|
|
71
72
|
def detect_projects(self, search_paths: List[str]) -> List[ProjectInfo]:
|
|
72
73
|
"""Detect projects by finding LLM.md files.
|
|
73
|
-
|
|
74
|
+
|
|
74
75
|
Args:
|
|
75
76
|
search_paths: List of paths to search for projects
|
|
76
|
-
|
|
77
|
+
|
|
77
78
|
Returns:
|
|
78
79
|
List of detected project information
|
|
79
80
|
"""
|
|
80
81
|
projects = []
|
|
81
|
-
|
|
82
|
+
|
|
82
83
|
for search_path in search_paths:
|
|
83
84
|
path = Path(search_path).resolve()
|
|
84
|
-
|
|
85
|
+
|
|
85
86
|
# Search for LLM.md files
|
|
86
87
|
for llm_md_path in path.rglob("LLM.md"):
|
|
87
88
|
project_root = llm_md_path.parent
|
|
88
89
|
project_name = project_root.name
|
|
89
|
-
|
|
90
|
+
|
|
90
91
|
# Create .hanzo/db directory in project
|
|
91
92
|
db_path = project_root / ".hanzo" / "db"
|
|
92
93
|
db_path.mkdir(parents=True, exist_ok=True)
|
|
93
|
-
|
|
94
|
+
|
|
94
95
|
project_info = ProjectInfo(
|
|
95
96
|
root_path=project_root,
|
|
96
97
|
llm_md_path=llm_md_path,
|
|
97
98
|
db_path=db_path,
|
|
98
99
|
name=project_name,
|
|
99
100
|
)
|
|
100
|
-
|
|
101
|
+
|
|
101
102
|
projects.append(project_info)
|
|
102
|
-
|
|
103
|
+
|
|
103
104
|
# Cache project info
|
|
104
105
|
project_key = str(project_root)
|
|
105
106
|
self.projects[project_key] = project_info
|
|
106
|
-
|
|
107
|
+
|
|
107
108
|
return projects
|
|
108
|
-
|
|
109
|
+
|
|
109
110
|
def get_project_for_path(self, file_path: str) -> Optional[ProjectInfo]:
|
|
110
111
|
"""Find the project that contains a given file path.
|
|
111
|
-
|
|
112
|
+
|
|
112
113
|
Args:
|
|
113
114
|
file_path: File path to check
|
|
114
|
-
|
|
115
|
+
|
|
115
116
|
Returns:
|
|
116
117
|
Project info if found, None otherwise
|
|
117
118
|
"""
|
|
118
119
|
path = Path(file_path).resolve()
|
|
119
|
-
|
|
120
|
+
|
|
120
121
|
# Check each known project
|
|
121
122
|
for project_key, project_info in self.projects.items():
|
|
122
123
|
try:
|
|
@@ -126,38 +127,40 @@ class ProjectVectorManager:
|
|
|
126
127
|
except ValueError:
|
|
127
128
|
# Path is not within this project
|
|
128
129
|
continue
|
|
129
|
-
|
|
130
|
+
|
|
130
131
|
# Try to find project by walking up the directory tree
|
|
131
132
|
current_path = path.parent if path.is_file() else path
|
|
132
|
-
|
|
133
|
+
|
|
133
134
|
while current_path != current_path.parent: # Stop at filesystem root
|
|
134
135
|
llm_md_path = current_path / "LLM.md"
|
|
135
136
|
if llm_md_path.exists():
|
|
136
137
|
# Found a project, create and cache it
|
|
137
138
|
db_path = current_path / ".hanzo" / "db"
|
|
138
139
|
db_path.mkdir(parents=True, exist_ok=True)
|
|
139
|
-
|
|
140
|
+
|
|
140
141
|
project_info = ProjectInfo(
|
|
141
142
|
root_path=current_path,
|
|
142
143
|
llm_md_path=llm_md_path,
|
|
143
144
|
db_path=db_path,
|
|
144
145
|
name=current_path.name,
|
|
145
146
|
)
|
|
146
|
-
|
|
147
|
+
|
|
147
148
|
project_key = str(current_path)
|
|
148
149
|
self.projects[project_key] = project_info
|
|
149
150
|
return project_info
|
|
150
|
-
|
|
151
|
+
|
|
151
152
|
current_path = current_path.parent
|
|
152
|
-
|
|
153
|
+
|
|
153
154
|
return None
|
|
154
|
-
|
|
155
|
-
def get_vector_store(
|
|
155
|
+
|
|
156
|
+
def get_vector_store(
|
|
157
|
+
self, project_info: Optional[ProjectInfo] = None
|
|
158
|
+
) -> InfinityVectorStore:
|
|
156
159
|
"""Get vector store for a project or global store.
|
|
157
|
-
|
|
160
|
+
|
|
158
161
|
Args:
|
|
159
162
|
project_info: Project to get store for, None for global store
|
|
160
|
-
|
|
163
|
+
|
|
161
164
|
Returns:
|
|
162
165
|
Vector store instance
|
|
163
166
|
"""
|
|
@@ -169,23 +172,25 @@ class ProjectVectorManager:
|
|
|
169
172
|
return self._get_global_store()
|
|
170
173
|
else:
|
|
171
174
|
return self._get_global_store()
|
|
172
|
-
|
|
175
|
+
|
|
173
176
|
# Use project-specific store
|
|
174
177
|
project_key = str(project_info.root_path)
|
|
175
|
-
|
|
178
|
+
|
|
176
179
|
if project_key not in self.vector_stores:
|
|
177
180
|
# Get index path based on configuration
|
|
178
|
-
index_path = self.index_config.get_index_path(
|
|
181
|
+
index_path = self.index_config.get_index_path(
|
|
182
|
+
"vector", str(project_info.root_path)
|
|
183
|
+
)
|
|
179
184
|
index_path.mkdir(parents=True, exist_ok=True)
|
|
180
|
-
|
|
185
|
+
|
|
181
186
|
self.vector_stores[project_key] = InfinityVectorStore(
|
|
182
187
|
data_path=str(index_path),
|
|
183
188
|
embedding_model=self.embedding_model,
|
|
184
189
|
dimension=self.dimension,
|
|
185
190
|
)
|
|
186
|
-
|
|
191
|
+
|
|
187
192
|
return self.vector_stores[project_key]
|
|
188
|
-
|
|
193
|
+
|
|
189
194
|
def add_file_to_appropriate_store(
|
|
190
195
|
self,
|
|
191
196
|
file_path: str,
|
|
@@ -194,26 +199,26 @@ class ProjectVectorManager:
|
|
|
194
199
|
metadata: Dict[str, Any] = None,
|
|
195
200
|
) -> Tuple[List[str], Optional[ProjectInfo]]:
|
|
196
201
|
"""Add a file to the appropriate vector store (project or global).
|
|
197
|
-
|
|
202
|
+
|
|
198
203
|
Args:
|
|
199
204
|
file_path: Path to file to add
|
|
200
205
|
chunk_size: Chunk size for text splitting
|
|
201
206
|
chunk_overlap: Overlap between chunks
|
|
202
207
|
metadata: Additional metadata
|
|
203
|
-
|
|
208
|
+
|
|
204
209
|
Returns:
|
|
205
210
|
Tuple of (document IDs, project info or None for global)
|
|
206
211
|
"""
|
|
207
212
|
# Check if indexing is enabled
|
|
208
213
|
if not self.index_config.is_indexing_enabled("vector"):
|
|
209
214
|
return [], None
|
|
210
|
-
|
|
215
|
+
|
|
211
216
|
# Find project for this file
|
|
212
217
|
project_info = self.get_project_for_path(file_path)
|
|
213
|
-
|
|
218
|
+
|
|
214
219
|
# Get appropriate vector store based on scope configuration
|
|
215
220
|
vector_store = self.get_vector_store(project_info)
|
|
216
|
-
|
|
221
|
+
|
|
217
222
|
# Add file metadata
|
|
218
223
|
file_metadata = metadata or {}
|
|
219
224
|
if project_info:
|
|
@@ -225,7 +230,7 @@ class ProjectVectorManager:
|
|
|
225
230
|
else:
|
|
226
231
|
file_metadata["project_name"] = "global"
|
|
227
232
|
file_metadata["index_scope"] = "global"
|
|
228
|
-
|
|
233
|
+
|
|
229
234
|
# Add file to store
|
|
230
235
|
doc_ids = vector_store.add_file(
|
|
231
236
|
file_path=file_path,
|
|
@@ -233,9 +238,9 @@ class ProjectVectorManager:
|
|
|
233
238
|
chunk_overlap=chunk_overlap,
|
|
234
239
|
metadata=file_metadata,
|
|
235
240
|
)
|
|
236
|
-
|
|
241
|
+
|
|
237
242
|
return doc_ids, project_info
|
|
238
|
-
|
|
243
|
+
|
|
239
244
|
async def search_all_projects(
|
|
240
245
|
self,
|
|
241
246
|
query: str,
|
|
@@ -245,49 +250,53 @@ class ProjectVectorManager:
|
|
|
245
250
|
project_filter: Optional[List[str]] = None,
|
|
246
251
|
) -> Dict[str, List[SearchResult]]:
|
|
247
252
|
"""Search across all projects in parallel.
|
|
248
|
-
|
|
253
|
+
|
|
249
254
|
Args:
|
|
250
255
|
query: Search query
|
|
251
256
|
limit_per_project: Maximum results per project
|
|
252
257
|
score_threshold: Minimum similarity score
|
|
253
258
|
include_global: Whether to include global store
|
|
254
259
|
project_filter: List of project names to search (None for all)
|
|
255
|
-
|
|
260
|
+
|
|
256
261
|
Returns:
|
|
257
262
|
Dictionary mapping project names to search results
|
|
258
263
|
"""
|
|
259
264
|
search_tasks = []
|
|
260
265
|
project_names = []
|
|
261
|
-
|
|
266
|
+
|
|
262
267
|
# Add global store if requested
|
|
263
268
|
if include_global:
|
|
264
269
|
global_store = self._get_global_store()
|
|
265
270
|
search_tasks.append(
|
|
266
271
|
asyncio.get_event_loop().run_in_executor(
|
|
267
272
|
self.executor,
|
|
268
|
-
lambda: global_store.search(
|
|
273
|
+
lambda: global_store.search(
|
|
274
|
+
query, limit_per_project, score_threshold
|
|
275
|
+
),
|
|
269
276
|
)
|
|
270
277
|
)
|
|
271
278
|
project_names.append("global")
|
|
272
|
-
|
|
279
|
+
|
|
273
280
|
# Add project stores
|
|
274
|
-
for
|
|
281
|
+
for _project_key, project_info in self.projects.items():
|
|
275
282
|
# Apply project filter
|
|
276
283
|
if project_filter and project_info.name not in project_filter:
|
|
277
284
|
continue
|
|
278
|
-
|
|
285
|
+
|
|
279
286
|
vector_store = self.get_vector_store(project_info)
|
|
280
287
|
search_tasks.append(
|
|
281
288
|
asyncio.get_event_loop().run_in_executor(
|
|
282
289
|
self.executor,
|
|
283
|
-
lambda vs=vector_store: vs.search(
|
|
290
|
+
lambda vs=vector_store: vs.search(
|
|
291
|
+
query, limit_per_project, score_threshold
|
|
292
|
+
),
|
|
284
293
|
)
|
|
285
294
|
)
|
|
286
295
|
project_names.append(project_info.name)
|
|
287
|
-
|
|
296
|
+
|
|
288
297
|
# Execute all searches in parallel
|
|
289
298
|
results = await asyncio.gather(*search_tasks, return_exceptions=True)
|
|
290
|
-
|
|
299
|
+
|
|
291
300
|
# Combine results
|
|
292
301
|
combined_results = {}
|
|
293
302
|
for i, result in enumerate(results):
|
|
@@ -295,14 +304,15 @@ class ProjectVectorManager:
|
|
|
295
304
|
if isinstance(result, Exception):
|
|
296
305
|
# Log error but continue
|
|
297
306
|
import logging
|
|
307
|
+
|
|
298
308
|
logger = logging.getLogger(__name__)
|
|
299
309
|
logger.error(f"Error searching project {project_name}: {result}")
|
|
300
310
|
combined_results[project_name] = []
|
|
301
311
|
else:
|
|
302
312
|
combined_results[project_name] = result
|
|
303
|
-
|
|
313
|
+
|
|
304
314
|
return combined_results
|
|
305
|
-
|
|
315
|
+
|
|
306
316
|
def search_project_by_path(
|
|
307
317
|
self,
|
|
308
318
|
file_path: str,
|
|
@@ -311,33 +321,33 @@ class ProjectVectorManager:
|
|
|
311
321
|
score_threshold: float = 0.0,
|
|
312
322
|
) -> List[SearchResult]:
|
|
313
323
|
"""Search the project containing a specific file path.
|
|
314
|
-
|
|
324
|
+
|
|
315
325
|
Args:
|
|
316
326
|
file_path: File path to determine project
|
|
317
327
|
query: Search query
|
|
318
328
|
limit: Maximum results
|
|
319
329
|
score_threshold: Minimum similarity score
|
|
320
|
-
|
|
330
|
+
|
|
321
331
|
Returns:
|
|
322
332
|
Search results from the appropriate project store
|
|
323
333
|
"""
|
|
324
334
|
project_info = self.get_project_for_path(file_path)
|
|
325
335
|
vector_store = self.get_vector_store(project_info)
|
|
326
|
-
|
|
336
|
+
|
|
327
337
|
return vector_store.search(
|
|
328
338
|
query=query,
|
|
329
339
|
limit=limit,
|
|
330
340
|
score_threshold=score_threshold,
|
|
331
341
|
)
|
|
332
|
-
|
|
342
|
+
|
|
333
343
|
def get_project_stats(self) -> Dict[str, Dict[str, Any]]:
|
|
334
344
|
"""Get statistics for all projects.
|
|
335
|
-
|
|
345
|
+
|
|
336
346
|
Returns:
|
|
337
347
|
Dictionary mapping project names to stats
|
|
338
348
|
"""
|
|
339
349
|
stats = {}
|
|
340
|
-
|
|
350
|
+
|
|
341
351
|
# Global store stats
|
|
342
352
|
try:
|
|
343
353
|
global_store = self._get_global_store()
|
|
@@ -348,9 +358,9 @@ class ProjectVectorManager:
|
|
|
348
358
|
}
|
|
349
359
|
except Exception as e:
|
|
350
360
|
stats["global"] = {"error": str(e)}
|
|
351
|
-
|
|
361
|
+
|
|
352
362
|
# Project store stats
|
|
353
|
-
for
|
|
363
|
+
for _project_key, project_info in self.projects.items():
|
|
354
364
|
try:
|
|
355
365
|
vector_store = self.get_vector_store(project_info)
|
|
356
366
|
project_files = vector_store.list_files()
|
|
@@ -362,24 +372,24 @@ class ProjectVectorManager:
|
|
|
362
372
|
}
|
|
363
373
|
except Exception as e:
|
|
364
374
|
stats[project_info.name] = {"error": str(e)}
|
|
365
|
-
|
|
375
|
+
|
|
366
376
|
return stats
|
|
367
|
-
|
|
377
|
+
|
|
368
378
|
def cleanup(self):
|
|
369
379
|
"""Close all vector stores and cleanup resources."""
|
|
370
380
|
# Close all project stores
|
|
371
381
|
for vector_store in self.vector_stores.values():
|
|
372
382
|
try:
|
|
373
383
|
vector_store.close()
|
|
374
|
-
except:
|
|
384
|
+
except Exception:
|
|
375
385
|
pass
|
|
376
|
-
|
|
386
|
+
|
|
377
387
|
# Close global store
|
|
378
388
|
if self._global_store:
|
|
379
389
|
try:
|
|
380
390
|
self._global_store.close()
|
|
381
|
-
except:
|
|
391
|
+
except Exception:
|
|
382
392
|
pass
|
|
383
|
-
|
|
393
|
+
|
|
384
394
|
# Shutdown executor
|
|
385
|
-
self.executor.shutdown(wait=False)
|
|
395
|
+
self.executor.shutdown(wait=False)
|