hanzo-mcp 0.7.7__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 +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.0.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.0.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.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
"""Graph statistics tool for analyzing the graph database."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
import sqlite3
|
|
5
|
-
from typing import
|
|
6
|
-
from collections import
|
|
4
|
+
from typing import Unpack, Optional, Annotated, TypedDict, final, override
|
|
5
|
+
from collections import defaultdict
|
|
7
6
|
|
|
8
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
9
7
|
from pydantic import Field
|
|
8
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
10
9
|
|
|
11
10
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
12
11
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
13
12
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
14
13
|
from hanzo_mcp.tools.database.database_manager import DatabaseManager
|
|
15
14
|
|
|
16
|
-
|
|
17
15
|
ProjectPath = Annotated[
|
|
18
16
|
Optional[str],
|
|
19
17
|
Field(
|
|
@@ -60,7 +58,9 @@ class GraphStatsParams(TypedDict, total=False):
|
|
|
60
58
|
class GraphStatsTool(BaseTool):
|
|
61
59
|
"""Tool for getting graph database statistics."""
|
|
62
60
|
|
|
63
|
-
def __init__(
|
|
61
|
+
def __init__(
|
|
62
|
+
self, permission_manager: PermissionManager, db_manager: DatabaseManager
|
|
63
|
+
):
|
|
64
64
|
"""Initialize the graph stats tool.
|
|
65
65
|
|
|
66
66
|
Args:
|
|
@@ -128,8 +128,9 @@ Examples:
|
|
|
128
128
|
project_db = self.db_manager.get_project_db(project_path)
|
|
129
129
|
else:
|
|
130
130
|
import os
|
|
131
|
+
|
|
131
132
|
project_db = self.db_manager.get_project_for_path(os.getcwd())
|
|
132
|
-
|
|
133
|
+
|
|
133
134
|
if not project_db:
|
|
134
135
|
return "Error: Could not find project database"
|
|
135
136
|
|
|
@@ -138,7 +139,9 @@ Examples:
|
|
|
138
139
|
except Exception as e:
|
|
139
140
|
return f"Error accessing project database: {str(e)}"
|
|
140
141
|
|
|
141
|
-
await tool_ctx.info(
|
|
142
|
+
await tool_ctx.info(
|
|
143
|
+
f"Getting graph statistics for project: {project_db.project_path}"
|
|
144
|
+
)
|
|
142
145
|
|
|
143
146
|
# Get graph connection
|
|
144
147
|
graph_conn = project_db.get_graph_connection()
|
|
@@ -150,69 +153,80 @@ Examples:
|
|
|
150
153
|
output.append(f"Project: {project_db.project_path}")
|
|
151
154
|
output.append(f"Database: {project_db.graph_path}")
|
|
152
155
|
output.append("")
|
|
153
|
-
|
|
156
|
+
|
|
154
157
|
# Basic counts
|
|
155
158
|
if node_type_filter:
|
|
156
|
-
cursor.execute(
|
|
159
|
+
cursor.execute(
|
|
160
|
+
"SELECT COUNT(*) FROM nodes WHERE type = ?", (node_type_filter,)
|
|
161
|
+
)
|
|
157
162
|
node_count = cursor.fetchone()[0]
|
|
158
163
|
output.append(f"Nodes (type='{node_type_filter}'): {node_count:,}")
|
|
159
164
|
else:
|
|
160
165
|
cursor.execute("SELECT COUNT(*) FROM nodes")
|
|
161
166
|
node_count = cursor.fetchone()[0]
|
|
162
167
|
output.append(f"Total Nodes: {node_count:,}")
|
|
163
|
-
|
|
168
|
+
|
|
164
169
|
if relationship_filter:
|
|
165
|
-
cursor.execute(
|
|
170
|
+
cursor.execute(
|
|
171
|
+
"SELECT COUNT(*) FROM edges WHERE relationship = ?",
|
|
172
|
+
(relationship_filter,),
|
|
173
|
+
)
|
|
166
174
|
edge_count = cursor.fetchone()[0]
|
|
167
|
-
output.append(
|
|
175
|
+
output.append(
|
|
176
|
+
f"Edges (relationship='{relationship_filter}'): {edge_count:,}"
|
|
177
|
+
)
|
|
168
178
|
else:
|
|
169
179
|
cursor.execute("SELECT COUNT(*) FROM edges")
|
|
170
180
|
edge_count = cursor.fetchone()[0]
|
|
171
181
|
output.append(f"Total Edges: {edge_count:,}")
|
|
172
|
-
|
|
182
|
+
|
|
173
183
|
if node_count == 0:
|
|
174
184
|
output.append("\nGraph is empty.")
|
|
175
185
|
return "\n".join(output)
|
|
176
|
-
|
|
186
|
+
|
|
177
187
|
output.append("")
|
|
178
|
-
|
|
188
|
+
|
|
179
189
|
# Node type distribution
|
|
180
190
|
output.append("=== Node Types ===")
|
|
181
|
-
cursor.execute(
|
|
191
|
+
cursor.execute(
|
|
192
|
+
"SELECT type, COUNT(*) as count FROM nodes GROUP BY type ORDER BY count DESC"
|
|
193
|
+
)
|
|
182
194
|
node_types = cursor.fetchall()
|
|
183
|
-
|
|
195
|
+
|
|
184
196
|
for n_type, count in node_types[:10]:
|
|
185
197
|
pct = (count / node_count) * 100
|
|
186
198
|
output.append(f"{n_type}: {count:,} ({pct:.1f}%)")
|
|
187
|
-
|
|
199
|
+
|
|
188
200
|
if len(node_types) > 10:
|
|
189
201
|
output.append(f"... and {len(node_types) - 10} more types")
|
|
190
|
-
|
|
202
|
+
|
|
191
203
|
output.append("")
|
|
192
|
-
|
|
204
|
+
|
|
193
205
|
# Relationship distribution
|
|
194
206
|
output.append("=== Relationship Types ===")
|
|
195
|
-
cursor.execute(
|
|
207
|
+
cursor.execute(
|
|
208
|
+
"SELECT relationship, COUNT(*) as count FROM edges GROUP BY relationship ORDER BY count DESC"
|
|
209
|
+
)
|
|
196
210
|
rel_types = cursor.fetchall()
|
|
197
|
-
|
|
211
|
+
|
|
198
212
|
if rel_types:
|
|
199
213
|
for rel, count in rel_types[:10]:
|
|
200
214
|
pct = (count / edge_count) * 100 if edge_count > 0 else 0
|
|
201
215
|
output.append(f"{rel}: {count:,} ({pct:.1f}%)")
|
|
202
|
-
|
|
216
|
+
|
|
203
217
|
if len(rel_types) > 10:
|
|
204
218
|
output.append(f"... and {len(rel_types) - 10} more types")
|
|
205
219
|
else:
|
|
206
220
|
output.append("No edges in graph")
|
|
207
|
-
|
|
221
|
+
|
|
208
222
|
output.append("")
|
|
209
|
-
|
|
223
|
+
|
|
210
224
|
# Degree statistics
|
|
211
225
|
output.append("=== Connectivity ===")
|
|
212
|
-
|
|
226
|
+
|
|
213
227
|
# Calculate degrees
|
|
214
228
|
degrees = defaultdict(int)
|
|
215
|
-
|
|
229
|
+
|
|
216
230
|
# Out-degree
|
|
217
231
|
query = "SELECT source, COUNT(*) FROM edges"
|
|
218
232
|
if relationship_filter:
|
|
@@ -220,10 +234,10 @@ Examples:
|
|
|
220
234
|
cursor.execute(query + " GROUP BY source", (relationship_filter,))
|
|
221
235
|
else:
|
|
222
236
|
cursor.execute(query + " GROUP BY source")
|
|
223
|
-
|
|
237
|
+
|
|
224
238
|
for node, out_degree in cursor.fetchall():
|
|
225
239
|
degrees[node] += out_degree
|
|
226
|
-
|
|
240
|
+
|
|
227
241
|
# In-degree
|
|
228
242
|
query = "SELECT target, COUNT(*) FROM edges"
|
|
229
243
|
if relationship_filter:
|
|
@@ -231,72 +245,85 @@ Examples:
|
|
|
231
245
|
cursor.execute(query + " GROUP BY target", (relationship_filter,))
|
|
232
246
|
else:
|
|
233
247
|
cursor.execute(query + " GROUP BY target")
|
|
234
|
-
|
|
248
|
+
|
|
235
249
|
for node, in_degree in cursor.fetchall():
|
|
236
250
|
degrees[node] += in_degree
|
|
237
|
-
|
|
251
|
+
|
|
238
252
|
if degrees:
|
|
239
253
|
degree_values = list(degrees.values())
|
|
240
254
|
avg_degree = sum(degree_values) / len(degree_values)
|
|
241
255
|
max_degree = max(degree_values)
|
|
242
256
|
min_degree = min(degree_values)
|
|
243
|
-
|
|
257
|
+
|
|
244
258
|
output.append(f"Average degree: {avg_degree:.2f}")
|
|
245
259
|
output.append(f"Max degree: {max_degree}")
|
|
246
260
|
output.append(f"Min degree: {min_degree}")
|
|
247
|
-
|
|
261
|
+
|
|
248
262
|
# Most connected nodes
|
|
249
263
|
output.append("\nMost connected nodes:")
|
|
250
264
|
sorted_nodes = sorted(degrees.items(), key=lambda x: x[1], reverse=True)
|
|
251
|
-
|
|
265
|
+
|
|
252
266
|
for node, degree in sorted_nodes[:5]:
|
|
253
267
|
cursor.execute("SELECT type FROM nodes WHERE id = ?", (node,))
|
|
254
268
|
node_type = cursor.fetchone()
|
|
255
269
|
type_str = f" ({node_type[0]})" if node_type else ""
|
|
256
270
|
output.append(f" {node}{type_str}: {degree} connections")
|
|
257
|
-
|
|
271
|
+
|
|
258
272
|
# Orphaned nodes
|
|
259
|
-
cursor.execute(
|
|
273
|
+
cursor.execute(
|
|
274
|
+
"""
|
|
260
275
|
SELECT COUNT(*) FROM nodes n
|
|
261
276
|
WHERE NOT EXISTS (SELECT 1 FROM edges WHERE source = n.id OR target = n.id)
|
|
262
|
-
"""
|
|
277
|
+
"""
|
|
278
|
+
)
|
|
263
279
|
orphan_count = cursor.fetchone()[0]
|
|
264
280
|
if orphan_count > 0:
|
|
265
281
|
orphan_pct = (orphan_count / node_count) * 100
|
|
266
282
|
output.append(f"\nOrphaned nodes: {orphan_count} ({orphan_pct:.1f}%)")
|
|
267
|
-
|
|
283
|
+
|
|
268
284
|
if detailed:
|
|
269
285
|
output.append("\n=== Detailed Analysis ===")
|
|
270
|
-
|
|
286
|
+
|
|
271
287
|
# Node properties usage
|
|
272
|
-
cursor.execute(
|
|
288
|
+
cursor.execute(
|
|
289
|
+
"SELECT COUNT(*) FROM nodes WHERE properties IS NOT NULL"
|
|
290
|
+
)
|
|
273
291
|
nodes_with_props = cursor.fetchone()[0]
|
|
274
292
|
if nodes_with_props > 0:
|
|
275
293
|
props_pct = (nodes_with_props / node_count) * 100
|
|
276
|
-
output.append(
|
|
277
|
-
|
|
294
|
+
output.append(
|
|
295
|
+
f"Nodes with properties: {nodes_with_props} ({props_pct:.1f}%)"
|
|
296
|
+
)
|
|
297
|
+
|
|
278
298
|
# Edge properties usage
|
|
279
|
-
cursor.execute(
|
|
299
|
+
cursor.execute(
|
|
300
|
+
"SELECT COUNT(*) FROM edges WHERE properties IS NOT NULL"
|
|
301
|
+
)
|
|
280
302
|
edges_with_props = cursor.fetchone()[0]
|
|
281
303
|
if edges_with_props > 0 and edge_count > 0:
|
|
282
304
|
props_pct = (edges_with_props / edge_count) * 100
|
|
283
|
-
output.append(
|
|
284
|
-
|
|
305
|
+
output.append(
|
|
306
|
+
f"Edges with properties: {edges_with_props} ({props_pct:.1f}%)"
|
|
307
|
+
)
|
|
308
|
+
|
|
285
309
|
# Weight distribution
|
|
286
|
-
cursor.execute(
|
|
310
|
+
cursor.execute(
|
|
311
|
+
"SELECT MIN(weight), MAX(weight), AVG(weight) FROM edges"
|
|
312
|
+
)
|
|
287
313
|
weight_stats = cursor.fetchone()
|
|
288
314
|
if weight_stats[0] is not None:
|
|
289
315
|
output.append(f"\nEdge weights:")
|
|
290
316
|
output.append(f" Min: {weight_stats[0]}")
|
|
291
317
|
output.append(f" Max: {weight_stats[1]}")
|
|
292
318
|
output.append(f" Avg: {weight_stats[2]:.2f}")
|
|
293
|
-
|
|
319
|
+
|
|
294
320
|
# Most common patterns
|
|
295
321
|
if not relationship_filter:
|
|
296
322
|
output.append("\n=== Common Patterns ===")
|
|
297
|
-
|
|
323
|
+
|
|
298
324
|
# Most common node type connections
|
|
299
|
-
cursor.execute(
|
|
325
|
+
cursor.execute(
|
|
326
|
+
"""
|
|
300
327
|
SELECT n1.type, e.relationship, n2.type, COUNT(*) as count
|
|
301
328
|
FROM edges e
|
|
302
329
|
JOIN nodes n1 ON e.source = n1.id
|
|
@@ -304,33 +331,38 @@ Examples:
|
|
|
304
331
|
GROUP BY n1.type, e.relationship, n2.type
|
|
305
332
|
ORDER BY count DESC
|
|
306
333
|
LIMIT 10
|
|
307
|
-
"""
|
|
308
|
-
|
|
334
|
+
"""
|
|
335
|
+
)
|
|
336
|
+
|
|
309
337
|
patterns = cursor.fetchall()
|
|
310
338
|
if patterns:
|
|
311
339
|
output.append("Most common connections:")
|
|
312
340
|
for src_type, rel, tgt_type, count in patterns:
|
|
313
|
-
output.append(
|
|
314
|
-
|
|
341
|
+
output.append(
|
|
342
|
+
f" {src_type} --[{rel}]--> {tgt_type}: {count} times"
|
|
343
|
+
)
|
|
344
|
+
|
|
315
345
|
# Component analysis (simplified)
|
|
316
346
|
output.append("\n=== Graph Structure ===")
|
|
317
|
-
|
|
347
|
+
|
|
318
348
|
# Check if graph is fully connected (simplified)
|
|
319
|
-
cursor.execute(
|
|
349
|
+
cursor.execute(
|
|
350
|
+
"""
|
|
320
351
|
SELECT COUNT(DISTINCT node_id) FROM (
|
|
321
352
|
SELECT source as node_id FROM edges
|
|
322
353
|
UNION
|
|
323
354
|
SELECT target as node_id FROM edges
|
|
324
355
|
)
|
|
325
|
-
"""
|
|
356
|
+
"""
|
|
357
|
+
)
|
|
326
358
|
connected_nodes = cursor.fetchone()[0]
|
|
327
|
-
|
|
359
|
+
|
|
328
360
|
if connected_nodes < node_count:
|
|
329
361
|
output.append(f"Connected nodes: {connected_nodes} / {node_count}")
|
|
330
362
|
output.append("Graph has disconnected components")
|
|
331
363
|
else:
|
|
332
364
|
output.append("All nodes are connected")
|
|
333
|
-
|
|
365
|
+
|
|
334
366
|
return "\n".join(output)
|
|
335
367
|
|
|
336
368
|
except sqlite3.Error as e:
|