code-graph-builder 0.2.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.
- code_graph_builder/__init__.py +82 -0
- code_graph_builder/builder.py +366 -0
- code_graph_builder/cgb_cli.py +32 -0
- code_graph_builder/cli.py +564 -0
- code_graph_builder/commands_cli.py +1288 -0
- code_graph_builder/config.py +340 -0
- code_graph_builder/constants.py +708 -0
- code_graph_builder/embeddings/__init__.py +40 -0
- code_graph_builder/embeddings/qwen3_embedder.py +573 -0
- code_graph_builder/embeddings/vector_store.py +584 -0
- code_graph_builder/examples/__init__.py +0 -0
- code_graph_builder/examples/example_configuration.py +276 -0
- code_graph_builder/examples/example_kuzu_usage.py +109 -0
- code_graph_builder/examples/example_semantic_search_full.py +347 -0
- code_graph_builder/examples/generate_wiki.py +915 -0
- code_graph_builder/examples/graph_export_example.py +100 -0
- code_graph_builder/examples/rag_example.py +206 -0
- code_graph_builder/examples/test_cli_demo.py +129 -0
- code_graph_builder/examples/test_embedding_api.py +153 -0
- code_graph_builder/examples/test_kuzu_local.py +190 -0
- code_graph_builder/examples/test_rag_redis.py +390 -0
- code_graph_builder/graph_updater.py +605 -0
- code_graph_builder/guidance/__init__.py +1 -0
- code_graph_builder/guidance/agent.py +123 -0
- code_graph_builder/guidance/prompts.py +74 -0
- code_graph_builder/guidance/toolset.py +264 -0
- code_graph_builder/language_spec.py +536 -0
- code_graph_builder/mcp/__init__.py +21 -0
- code_graph_builder/mcp/api_doc_generator.py +764 -0
- code_graph_builder/mcp/file_editor.py +207 -0
- code_graph_builder/mcp/pipeline.py +777 -0
- code_graph_builder/mcp/server.py +161 -0
- code_graph_builder/mcp/tools.py +1800 -0
- code_graph_builder/models.py +115 -0
- code_graph_builder/parser_loader.py +344 -0
- code_graph_builder/parsers/__init__.py +7 -0
- code_graph_builder/parsers/call_processor.py +306 -0
- code_graph_builder/parsers/call_resolver.py +139 -0
- code_graph_builder/parsers/definition_processor.py +796 -0
- code_graph_builder/parsers/factory.py +119 -0
- code_graph_builder/parsers/import_processor.py +293 -0
- code_graph_builder/parsers/structure_processor.py +145 -0
- code_graph_builder/parsers/type_inference.py +143 -0
- code_graph_builder/parsers/utils.py +134 -0
- code_graph_builder/rag/__init__.py +68 -0
- code_graph_builder/rag/camel_agent.py +429 -0
- code_graph_builder/rag/client.py +298 -0
- code_graph_builder/rag/config.py +239 -0
- code_graph_builder/rag/cypher_generator.py +67 -0
- code_graph_builder/rag/llm_backend.py +210 -0
- code_graph_builder/rag/markdown_generator.py +352 -0
- code_graph_builder/rag/prompt_templates.py +440 -0
- code_graph_builder/rag/rag_engine.py +640 -0
- code_graph_builder/rag/review_report.md +172 -0
- code_graph_builder/rag/tests/__init__.py +3 -0
- code_graph_builder/rag/tests/test_camel_agent.py +313 -0
- code_graph_builder/rag/tests/test_client.py +221 -0
- code_graph_builder/rag/tests/test_config.py +177 -0
- code_graph_builder/rag/tests/test_markdown_generator.py +240 -0
- code_graph_builder/rag/tests/test_prompt_templates.py +160 -0
- code_graph_builder/services/__init__.py +39 -0
- code_graph_builder/services/graph_service.py +465 -0
- code_graph_builder/services/kuzu_service.py +665 -0
- code_graph_builder/services/memory_service.py +171 -0
- code_graph_builder/settings.py +75 -0
- code_graph_builder/tests/ACCEPTANCE_CRITERIA_PHASE2.md +401 -0
- code_graph_builder/tests/__init__.py +1 -0
- code_graph_builder/tests/run_acceptance_check.py +378 -0
- code_graph_builder/tests/test_api_find.py +231 -0
- code_graph_builder/tests/test_api_find_integration.py +226 -0
- code_graph_builder/tests/test_basic.py +78 -0
- code_graph_builder/tests/test_c_api_extraction.py +388 -0
- code_graph_builder/tests/test_call_resolution_scenarios.py +504 -0
- code_graph_builder/tests/test_embedder.py +411 -0
- code_graph_builder/tests/test_integration_semantic.py +434 -0
- code_graph_builder/tests/test_mcp_protocol.py +298 -0
- code_graph_builder/tests/test_mcp_user_flow.py +190 -0
- code_graph_builder/tests/test_rag.py +404 -0
- code_graph_builder/tests/test_settings.py +135 -0
- code_graph_builder/tests/test_step1_graph_build.py +264 -0
- code_graph_builder/tests/test_step2_api_docs.py +323 -0
- code_graph_builder/tests/test_step3_embedding.py +278 -0
- code_graph_builder/tests/test_vector_store.py +552 -0
- code_graph_builder/tools/__init__.py +40 -0
- code_graph_builder/tools/graph_query.py +495 -0
- code_graph_builder/tools/semantic_search.py +387 -0
- code_graph_builder/types.py +333 -0
- code_graph_builder/utils/__init__.py +0 -0
- code_graph_builder/utils/path_utils.py +30 -0
- code_graph_builder-0.2.0.dist-info/METADATA +321 -0
- code_graph_builder-0.2.0.dist-info/RECORD +93 -0
- code_graph_builder-0.2.0.dist-info/WHEEL +4 -0
- code_graph_builder-0.2.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Code Graph Builder - 代码知识图谱构建库.
|
|
2
|
+
|
|
3
|
+
This library provides functionality to build knowledge graphs from source code,
|
|
4
|
+
supporting multiple programming languages and multiple storage backends.
|
|
5
|
+
|
|
6
|
+
Backends:
|
|
7
|
+
- Kùzu (default): Embedded graph database, no Docker required
|
|
8
|
+
- Memgraph: Full-featured graph database (requires Docker)
|
|
9
|
+
- Memory: In-memory storage, no persistence
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
>>> from code_graph_builder import CodeGraphBuilder
|
|
13
|
+
>>>
|
|
14
|
+
>>> # Using Kùzu (recommended, no Docker)
|
|
15
|
+
>>> builder = CodeGraphBuilder("/path/to/repo", backend="kuzu")
|
|
16
|
+
>>> result = builder.build_graph()
|
|
17
|
+
>>>
|
|
18
|
+
>>> # Using Memory (for testing)
|
|
19
|
+
>>> builder = CodeGraphBuilder("/path/to/repo", backend="memory")
|
|
20
|
+
>>> data = builder.export_graph()
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from .builder import CodeGraphBuilder
|
|
24
|
+
from .config import (
|
|
25
|
+
ConfigValidator,
|
|
26
|
+
EmbeddingConfig,
|
|
27
|
+
KuzuConfig,
|
|
28
|
+
MemgraphConfig,
|
|
29
|
+
MemoryConfig,
|
|
30
|
+
OutputConfig,
|
|
31
|
+
ScanConfig,
|
|
32
|
+
)
|
|
33
|
+
from .embeddings import (
|
|
34
|
+
BaseEmbedder,
|
|
35
|
+
DummyEmbedder,
|
|
36
|
+
MemoryVectorStore,
|
|
37
|
+
QdrantVectorStore,
|
|
38
|
+
Qwen3Embedder,
|
|
39
|
+
SearchResult,
|
|
40
|
+
VectorRecord,
|
|
41
|
+
VectorStore,
|
|
42
|
+
cosine_similarity,
|
|
43
|
+
create_embedder,
|
|
44
|
+
create_vector_store,
|
|
45
|
+
last_token_pool,
|
|
46
|
+
)
|
|
47
|
+
from .services.kuzu_service import KuzuIngestor
|
|
48
|
+
from .services.memory_service import MemoryIngestor
|
|
49
|
+
from .types import BuildResult, GraphData, GraphSummary
|
|
50
|
+
|
|
51
|
+
__version__ = "0.1.0"
|
|
52
|
+
__all__ = [
|
|
53
|
+
# Main API
|
|
54
|
+
"CodeGraphBuilder",
|
|
55
|
+
"BuildResult",
|
|
56
|
+
"GraphData",
|
|
57
|
+
"GraphSummary",
|
|
58
|
+
# Backend implementations
|
|
59
|
+
"KuzuIngestor",
|
|
60
|
+
"MemoryIngestor",
|
|
61
|
+
# Configuration classes
|
|
62
|
+
"ConfigValidator",
|
|
63
|
+
"EmbeddingConfig",
|
|
64
|
+
"KuzuConfig",
|
|
65
|
+
"MemgraphConfig",
|
|
66
|
+
"MemoryConfig",
|
|
67
|
+
"OutputConfig",
|
|
68
|
+
"ScanConfig",
|
|
69
|
+
# Embeddings
|
|
70
|
+
"BaseEmbedder",
|
|
71
|
+
"DummyEmbedder",
|
|
72
|
+
"Qwen3Embedder",
|
|
73
|
+
"create_embedder",
|
|
74
|
+
"last_token_pool",
|
|
75
|
+
"VectorStore",
|
|
76
|
+
"MemoryVectorStore",
|
|
77
|
+
"QdrantVectorStore",
|
|
78
|
+
"VectorRecord",
|
|
79
|
+
"SearchResult",
|
|
80
|
+
"create_vector_store",
|
|
81
|
+
"cosine_similarity",
|
|
82
|
+
]
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""Code Graph Builder - Main API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from loguru import logger
|
|
10
|
+
|
|
11
|
+
from .config import ConfigValidator, EmbeddingConfig, KuzuConfig, MemgraphConfig, MemoryConfig, ScanConfig
|
|
12
|
+
from .constants import SupportedLanguage
|
|
13
|
+
from .graph_updater import GraphUpdater
|
|
14
|
+
from .parser_loader import load_parsers
|
|
15
|
+
from .services.graph_service import MemgraphIngestor
|
|
16
|
+
from .services.kuzu_service import KuzuIngestor
|
|
17
|
+
from .types import BuildResult, GraphData, GraphSummary, PropertyDict, ResultRow
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from tree_sitter import Parser
|
|
21
|
+
|
|
22
|
+
from .types import LanguageQueries
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CodeGraphBuilder:
|
|
26
|
+
"""Main API for building code knowledge graphs.
|
|
27
|
+
|
|
28
|
+
Supports multiple backends:
|
|
29
|
+
- Memgraph: Full-featured graph database (requires Docker)
|
|
30
|
+
- Kùzu: Embedded graph database (no Docker, recommended for local use)
|
|
31
|
+
- Memory: In-memory storage only (no persistence)
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
>>> # Method 1: Simple dict-based config (recommended for quick start)
|
|
35
|
+
>>> builder = CodeGraphBuilder(
|
|
36
|
+
... repo_path="/path/to/repo",
|
|
37
|
+
... backend="kuzu",
|
|
38
|
+
... backend_config={"db_path": "./graph.db"}
|
|
39
|
+
... )
|
|
40
|
+
>>> result = builder.build_graph()
|
|
41
|
+
|
|
42
|
+
>>> # Method 2: Using config dataclasses (type-safe)
|
|
43
|
+
>>> from code_graph_builder.config import KuzuConfig, ScanConfig
|
|
44
|
+
>>> builder = CodeGraphBuilder(
|
|
45
|
+
... repo_path="/path/to/repo",
|
|
46
|
+
... backend="kuzu",
|
|
47
|
+
... backend_config=KuzuConfig(db_path="./graph.db"),
|
|
48
|
+
... scan_config=ScanConfig(exclude_patterns={"tests", "docs"})
|
|
49
|
+
... )
|
|
50
|
+
|
|
51
|
+
>>> # Method 3: Memgraph backend
|
|
52
|
+
>>> builder = CodeGraphBuilder(
|
|
53
|
+
... repo_path="/path/to/repo",
|
|
54
|
+
... backend="memgraph",
|
|
55
|
+
... backend_config={"host": "localhost", "port": 7687}
|
|
56
|
+
... )
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
repo_path: str | Path,
|
|
62
|
+
backend: str = "kuzu",
|
|
63
|
+
backend_config: dict[str, Any] | KuzuConfig | MemgraphConfig | MemoryConfig | None = None,
|
|
64
|
+
scan_config: dict[str, Any] | ScanConfig | None = None,
|
|
65
|
+
embedding_config: dict[str, Any] | EmbeddingConfig | None = None,
|
|
66
|
+
# Backward compatibility
|
|
67
|
+
db_config: dict | None = None,
|
|
68
|
+
exclude_paths: frozenset[str] | None = None,
|
|
69
|
+
unignore_paths: frozenset[str] | None = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Initialize the code graph builder.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
repo_path: Path to the repository to analyze
|
|
75
|
+
backend: Database backend to use ("kuzu", "memgraph", or "memory")
|
|
76
|
+
backend_config: Backend configuration. Can be:
|
|
77
|
+
- dict: {"db_path": "...", "batch_size": 1000}
|
|
78
|
+
- KuzuConfig: Type-safe configuration for Kùzu
|
|
79
|
+
- MemgraphConfig: Type-safe configuration for Memgraph
|
|
80
|
+
- MemoryConfig: Type-safe configuration for Memory
|
|
81
|
+
scan_config: Scan configuration. Can be:
|
|
82
|
+
- dict: {"exclude_patterns": {"tests"}, "max_file_size": 1000000}
|
|
83
|
+
- ScanConfig: Type-safe scan configuration
|
|
84
|
+
# Deprecated (use backend_config and scan_config instead):
|
|
85
|
+
db_config: Deprecated, use backend_config
|
|
86
|
+
exclude_paths: Deprecated, use scan_config
|
|
87
|
+
unignore_paths: Deprecated, use scan_config
|
|
88
|
+
"""
|
|
89
|
+
self.repo_path = Path(repo_path).resolve()
|
|
90
|
+
self.backend = backend.lower()
|
|
91
|
+
|
|
92
|
+
# Handle backward compatibility
|
|
93
|
+
if db_config is not None:
|
|
94
|
+
logger.warning("db_config is deprecated, use backend_config instead")
|
|
95
|
+
if backend_config is None:
|
|
96
|
+
backend_config = db_config
|
|
97
|
+
|
|
98
|
+
# Validate and normalize backend config
|
|
99
|
+
self.backend_config = ConfigValidator.validate_backend_config(self.backend, backend_config)
|
|
100
|
+
|
|
101
|
+
# Validate and normalize scan config
|
|
102
|
+
if scan_config is None:
|
|
103
|
+
scan_config = ScanConfig()
|
|
104
|
+
elif isinstance(scan_config, dict):
|
|
105
|
+
scan_config = ScanConfig(**scan_config)
|
|
106
|
+
|
|
107
|
+
# Handle backward compatibility for exclude_paths/unignore_paths
|
|
108
|
+
if exclude_paths is not None:
|
|
109
|
+
logger.warning("exclude_paths is deprecated, use scan_config instead")
|
|
110
|
+
scan_config.exclude_patterns.update(exclude_paths)
|
|
111
|
+
if unignore_paths is not None:
|
|
112
|
+
logger.warning("unignore_paths is deprecated, use scan_config instead")
|
|
113
|
+
scan_config.unignore_paths.update(unignore_paths)
|
|
114
|
+
|
|
115
|
+
self.scan_config = scan_config
|
|
116
|
+
|
|
117
|
+
# Validate and normalize embedding config
|
|
118
|
+
if embedding_config is None:
|
|
119
|
+
embedding_config = EmbeddingConfig(enabled=False)
|
|
120
|
+
elif isinstance(embedding_config, dict):
|
|
121
|
+
embedding_config = EmbeddingConfig(**embedding_config)
|
|
122
|
+
self.embedding_config = embedding_config
|
|
123
|
+
|
|
124
|
+
self._parsers: dict[SupportedLanguage, Parser] | None = None
|
|
125
|
+
self._queries: dict[SupportedLanguage, LanguageQueries] | None = None
|
|
126
|
+
self._ingestor: MemgraphIngestor | KuzuIngestor | None = None
|
|
127
|
+
self._embedder: Any | None = None
|
|
128
|
+
self._vector_store: Any | None = None
|
|
129
|
+
|
|
130
|
+
def _load_parsers(self) -> None:
|
|
131
|
+
"""Load Tree-sitter parsers for supported languages."""
|
|
132
|
+
if self._parsers is None or self._queries is None:
|
|
133
|
+
self._parsers, self._queries = load_parsers()
|
|
134
|
+
logger.info(f"Loaded parsers for {len(self._parsers)} languages")
|
|
135
|
+
|
|
136
|
+
def _get_ingestor(self) -> MemgraphIngestor | KuzuIngestor | Any:
|
|
137
|
+
"""Get or create the graph ingestor based on backend."""
|
|
138
|
+
if self._ingestor is None:
|
|
139
|
+
if self.backend == "memgraph":
|
|
140
|
+
host = self.backend_config.get("host", "localhost")
|
|
141
|
+
port = self.backend_config.get("port", 7687)
|
|
142
|
+
batch_size = self.backend_config.get("batch_size", 1000)
|
|
143
|
+
self._ingestor = MemgraphIngestor(host, port, batch_size)
|
|
144
|
+
elif self.backend == "kuzu":
|
|
145
|
+
db_path = self.backend_config.get("db_path", f"./{self.repo_path.name}_graph.db")
|
|
146
|
+
batch_size = self.backend_config.get("batch_size", 1000)
|
|
147
|
+
self._ingestor = KuzuIngestor(db_path, batch_size)
|
|
148
|
+
elif self.backend == "memory":
|
|
149
|
+
from .services.memory_service import MemoryIngestor
|
|
150
|
+
|
|
151
|
+
self._ingestor = MemoryIngestor()
|
|
152
|
+
else:
|
|
153
|
+
raise ValueError(f"Unknown backend: {self.backend}. Use 'memgraph', 'kuzu', or 'memory'")
|
|
154
|
+
return self._ingestor
|
|
155
|
+
|
|
156
|
+
def _get_embedder_and_store(self) -> tuple[Any | None, Any | None]:
|
|
157
|
+
"""Get or create the embedder and vector store if embedding is enabled."""
|
|
158
|
+
if not self.embedding_config.enabled:
|
|
159
|
+
return None, None
|
|
160
|
+
|
|
161
|
+
if self._embedder is None or self._vector_store is None:
|
|
162
|
+
from .embeddings.qwen3_embedder import create_embedder
|
|
163
|
+
from .embeddings.vector_store import create_vector_store
|
|
164
|
+
|
|
165
|
+
# Create embedder
|
|
166
|
+
self._embedder = create_embedder(
|
|
167
|
+
model_name=self.embedding_config.model_name,
|
|
168
|
+
device=self.embedding_config.device,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Get embedding dimension
|
|
172
|
+
dimension = self.embedding_config.vector_dimension
|
|
173
|
+
if dimension is None:
|
|
174
|
+
dimension = self._embedder.get_embedding_dimension()
|
|
175
|
+
|
|
176
|
+
# Create vector store
|
|
177
|
+
self._vector_store = create_vector_store(
|
|
178
|
+
backend=self.embedding_config.vector_store_backend,
|
|
179
|
+
dimension=dimension,
|
|
180
|
+
db_path=self.embedding_config.vector_store_path,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return self._embedder, self._vector_store
|
|
184
|
+
|
|
185
|
+
def build_graph(self, clean: bool = False) -> BuildResult:
|
|
186
|
+
"""Build the code knowledge graph.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
clean: If True, clean the database before building
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
BuildResult with statistics about the build
|
|
193
|
+
"""
|
|
194
|
+
self._load_parsers()
|
|
195
|
+
ingestor = self._get_ingestor()
|
|
196
|
+
|
|
197
|
+
with ingestor:
|
|
198
|
+
if clean:
|
|
199
|
+
ingestor.clean_database()
|
|
200
|
+
|
|
201
|
+
# Get embedder and vector store if embedding is enabled
|
|
202
|
+
embedder, vector_store = self._get_embedder_and_store()
|
|
203
|
+
|
|
204
|
+
updater = GraphUpdater(
|
|
205
|
+
ingestor=ingestor,
|
|
206
|
+
repo_path=self.repo_path,
|
|
207
|
+
parsers=self._parsers,
|
|
208
|
+
queries=self._queries,
|
|
209
|
+
unignore_paths=frozenset(self.scan_config.unignore_paths),
|
|
210
|
+
exclude_paths=frozenset(self.scan_config.exclude_patterns),
|
|
211
|
+
embedder=embedder,
|
|
212
|
+
vector_store=vector_store,
|
|
213
|
+
embedding_config=self.embedding_config.to_dict(),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
updater.run()
|
|
217
|
+
|
|
218
|
+
# Get statistics
|
|
219
|
+
if hasattr(ingestor, 'get_statistics'):
|
|
220
|
+
stats = ingestor.get_statistics()
|
|
221
|
+
total_nodes = stats.get("node_count", 0)
|
|
222
|
+
total_rels = stats.get("relationship_count", 0)
|
|
223
|
+
else:
|
|
224
|
+
# Fallback for ingestors without get_statistics
|
|
225
|
+
total_nodes = 0
|
|
226
|
+
total_rels = 0
|
|
227
|
+
|
|
228
|
+
return BuildResult(
|
|
229
|
+
project_name=self.repo_path.name,
|
|
230
|
+
nodes_created=total_nodes,
|
|
231
|
+
relationships_created=total_rels,
|
|
232
|
+
functions_found=0, # Will be updated from stats
|
|
233
|
+
classes_found=0,
|
|
234
|
+
files_processed=0,
|
|
235
|
+
errors=[],
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def export_graph(self) -> GraphData:
|
|
239
|
+
"""Export the graph data as a dictionary.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
GraphData containing nodes, relationships, and metadata
|
|
243
|
+
"""
|
|
244
|
+
ingestor = self._get_ingestor()
|
|
245
|
+
with ingestor:
|
|
246
|
+
if hasattr(ingestor, 'export_graph'):
|
|
247
|
+
return ingestor.export_graph()
|
|
248
|
+
elif hasattr(ingestor, 'export_graph_to_dict'):
|
|
249
|
+
return ingestor.export_graph_to_dict()
|
|
250
|
+
else:
|
|
251
|
+
return {"nodes": [], "relationships": [], "metadata": {}}
|
|
252
|
+
|
|
253
|
+
def get_statistics(self) -> dict:
|
|
254
|
+
"""Get statistics about the graph.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Dictionary with node and relationship counts
|
|
258
|
+
"""
|
|
259
|
+
ingestor = self._get_ingestor()
|
|
260
|
+
with ingestor:
|
|
261
|
+
if hasattr(ingestor, 'get_statistics'):
|
|
262
|
+
stats = ingestor.get_statistics()
|
|
263
|
+
if isinstance(stats, dict):
|
|
264
|
+
return {
|
|
265
|
+
"total_nodes": stats.get("node_count", 0),
|
|
266
|
+
"total_relationships": stats.get("relationship_count", 0),
|
|
267
|
+
"node_labels": stats.get("node_labels", {}),
|
|
268
|
+
"relationship_types": stats.get("relationship_types", {}),
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# Fallback to export
|
|
272
|
+
if hasattr(ingestor, 'export_graph_to_dict'):
|
|
273
|
+
data = ingestor.export_graph_to_dict()
|
|
274
|
+
elif hasattr(ingestor, 'export_graph'):
|
|
275
|
+
data = ingestor.export_graph()
|
|
276
|
+
else:
|
|
277
|
+
data = {"nodes": [], "relationships": []}
|
|
278
|
+
|
|
279
|
+
# Count node labels
|
|
280
|
+
node_labels: dict[str, int] = {}
|
|
281
|
+
for node in data.get("nodes", []):
|
|
282
|
+
labels = node.get("labels", [])
|
|
283
|
+
for label in labels:
|
|
284
|
+
node_labels[label] = node_labels.get(label, 0) + 1
|
|
285
|
+
|
|
286
|
+
# Count relationship types
|
|
287
|
+
rel_types: dict[str, int] = {}
|
|
288
|
+
for rel in data.get("relationships", []):
|
|
289
|
+
rel_type = rel.get("type", "UNKNOWN")
|
|
290
|
+
rel_types[rel_type] = rel_types.get(rel_type, 0) + 1
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
"total_nodes": len(data.get("nodes", [])),
|
|
294
|
+
"total_relationships": len(data.get("relationships", [])),
|
|
295
|
+
"node_labels": node_labels,
|
|
296
|
+
"relationship_types": rel_types,
|
|
297
|
+
"metadata": data.get("metadata", {}),
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
def query(self, cypher_query: str, params: PropertyDict | None = None) -> list[ResultRow]:
|
|
301
|
+
"""Execute a Cypher query against the graph.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
cypher_query: The Cypher query to execute
|
|
305
|
+
params: Optional query parameters
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
List of result rows as dictionaries
|
|
309
|
+
"""
|
|
310
|
+
ingestor = self._get_ingestor()
|
|
311
|
+
with ingestor:
|
|
312
|
+
if hasattr(ingestor, 'query'):
|
|
313
|
+
return ingestor.query(cypher_query, params)
|
|
314
|
+
elif hasattr(ingestor, 'fetch_all'):
|
|
315
|
+
return ingestor.fetch_all(cypher_query, params)
|
|
316
|
+
else:
|
|
317
|
+
return []
|
|
318
|
+
|
|
319
|
+
def get_function_source(self, qualified_name: str) -> str | None:
|
|
320
|
+
"""Get the source code of a function by its qualified name.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
qualified_name: The fully qualified name of the function
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
The source code as a string, or None if not found
|
|
327
|
+
"""
|
|
328
|
+
results = self.query(
|
|
329
|
+
"""
|
|
330
|
+
MATCH (n)
|
|
331
|
+
WHERE n.qualified_name = $qn
|
|
332
|
+
RETURN n.name AS name, n.start_line AS start, n.end_line AS end
|
|
333
|
+
LIMIT 1
|
|
334
|
+
""",
|
|
335
|
+
{"qn": qualified_name},
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
if not results:
|
|
339
|
+
return None
|
|
340
|
+
|
|
341
|
+
result = results[0]
|
|
342
|
+
return f"Function: {result.get('name')} (lines {result.get('start')}-{result.get('end')})"
|
|
343
|
+
|
|
344
|
+
def list_projects(self) -> list[str]:
|
|
345
|
+
"""List all projects in the database.
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
List of project names
|
|
349
|
+
"""
|
|
350
|
+
ingestor = self._get_ingestor()
|
|
351
|
+
with ingestor:
|
|
352
|
+
if hasattr(ingestor, 'list_projects'):
|
|
353
|
+
return ingestor.list_projects()
|
|
354
|
+
return []
|
|
355
|
+
|
|
356
|
+
def delete_project(self, project_name: str | None = None) -> None:
|
|
357
|
+
"""Delete a project from the database.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
project_name: Name of the project to delete (default: current project)
|
|
361
|
+
"""
|
|
362
|
+
name = project_name or self.repo_path.name
|
|
363
|
+
ingestor = self._get_ingestor()
|
|
364
|
+
with ingestor:
|
|
365
|
+
if hasattr(ingestor, 'delete_project'):
|
|
366
|
+
ingestor.delete_project(name)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Global CLI entry point for CodeGraphWiki custom commands.
|
|
3
|
+
|
|
4
|
+
This file lives at ~/.claude/commands/code-graph/cgb_cli.py and acts as
|
|
5
|
+
a thin wrapper that delegates to the installed code_graph_builder package.
|
|
6
|
+
|
|
7
|
+
Usage (called by the .md command files in this directory):
|
|
8
|
+
python3 ~/.claude/commands/code-graph/cgb_cli.py <subcommand> [args...]
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
try:
|
|
16
|
+
from code_graph_builder.commands_cli import main as cli_main
|
|
17
|
+
except ImportError:
|
|
18
|
+
print(
|
|
19
|
+
"ERROR: code_graph_builder package is not installed.\n"
|
|
20
|
+
"Run the following to install:\n"
|
|
21
|
+
" pip install /path/to/CodeGraphWiki\n"
|
|
22
|
+
"Or:\n"
|
|
23
|
+
" pip install -e /path/to/CodeGraphWiki",
|
|
24
|
+
file=sys.stderr,
|
|
25
|
+
)
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
cli_main()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
main()
|