nelgraph 1.0.0__tar.gz
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.
- nelgraph-1.0.0/.gitignore +21 -0
- nelgraph-1.0.0/PKG-INFO +94 -0
- nelgraph-1.0.0/README.md +66 -0
- nelgraph-1.0.0/nelgraph/__init__.py +185 -0
- nelgraph-1.0.0/nelgraph/cli.py +144 -0
- nelgraph-1.0.0/nelgraph/community/__init__.py +1 -0
- nelgraph-1.0.0/nelgraph/community/detector.py +68 -0
- nelgraph-1.0.0/nelgraph/community/summarizer.py +208 -0
- nelgraph-1.0.0/nelgraph/config.py +79 -0
- nelgraph-1.0.0/nelgraph/core/__init__.py +1 -0
- nelgraph-1.0.0/nelgraph/core/init_pipeline.py +341 -0
- nelgraph-1.0.0/nelgraph/core/sync_pipeline.py +212 -0
- nelgraph-1.0.0/nelgraph/docker-compose.yml +24 -0
- nelgraph-1.0.0/nelgraph/embeddings/__init__.py +1 -0
- nelgraph-1.0.0/nelgraph/embeddings/chroma_client.py +222 -0
- nelgraph-1.0.0/nelgraph/embeddings/embedder.py +92 -0
- nelgraph-1.0.0/nelgraph/extractors/__init__.py +1 -0
- nelgraph-1.0.0/nelgraph/extractors/llm_extractor.py +182 -0
- nelgraph-1.0.0/nelgraph/extractors/testing_enricher.py +327 -0
- nelgraph-1.0.0/nelgraph/graph/__init__.py +1 -0
- nelgraph-1.0.0/nelgraph/graph/builder.py +274 -0
- nelgraph-1.0.0/nelgraph/graph/neo4j_client.py +161 -0
- nelgraph-1.0.0/nelgraph/graph/schema.py +43 -0
- nelgraph-1.0.0/nelgraph/initialize_graph.py +174 -0
- nelgraph-1.0.0/nelgraph/knowledge_base.py +255 -0
- nelgraph-1.0.0/nelgraph/parsers/__init__.py +1 -0
- nelgraph-1.0.0/nelgraph/parsers/ast_parser.py +11 -0
- nelgraph-1.0.0/nelgraph/parsers/base_parser.py +529 -0
- nelgraph-1.0.0/nelgraph/parsers/doc_parser.py +44 -0
- nelgraph-1.0.0/nelgraph/parsers/git_parser.py +105 -0
- nelgraph-1.0.0/nelgraph/parsers/php_parser.py +396 -0
- nelgraph-1.0.0/nelgraph/query/__init__.py +1 -0
- nelgraph-1.0.0/nelgraph/query/engine.py +157 -0
- nelgraph-1.0.0/nelgraph/updater/__init__.py +1 -0
- nelgraph-1.0.0/nelgraph/updater/git_hook.py +109 -0
- nelgraph-1.0.0/nelgraph/updater/watcher.py +70 -0
- nelgraph-1.0.0/pyproject.toml +42 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
chroma_db/
|
|
3
|
+
data/
|
|
4
|
+
.graphrag_data/
|
|
5
|
+
server_data/
|
|
6
|
+
workspace/
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.pyc
|
|
9
|
+
.env
|
|
10
|
+
node_modules/
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
*.log
|
|
14
|
+
.DS_Store
|
|
15
|
+
/updates/
|
|
16
|
+
ngrok.exe
|
|
17
|
+
ngrok_tmp.exe
|
|
18
|
+
|
|
19
|
+
# MCP compiled JS outputs
|
|
20
|
+
mcp/server.js
|
|
21
|
+
mcp/tools.js
|
nelgraph-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nelgraph
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: GraphRAG knowledge base for codebases
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: chromadb
|
|
8
|
+
Requires-Dist: click
|
|
9
|
+
Requires-Dist: fastapi
|
|
10
|
+
Requires-Dist: gitpython
|
|
11
|
+
Requires-Dist: igraph
|
|
12
|
+
Requires-Dist: json-repair
|
|
13
|
+
Requires-Dist: leidenalg
|
|
14
|
+
Requires-Dist: neo4j
|
|
15
|
+
Requires-Dist: networkx
|
|
16
|
+
Requires-Dist: openai
|
|
17
|
+
Requires-Dist: python-dotenv
|
|
18
|
+
Requires-Dist: requests
|
|
19
|
+
Requires-Dist: rich
|
|
20
|
+
Requires-Dist: tree-sitter
|
|
21
|
+
Requires-Dist: tree-sitter-javascript
|
|
22
|
+
Requires-Dist: tree-sitter-php
|
|
23
|
+
Requires-Dist: tree-sitter-python
|
|
24
|
+
Requires-Dist: tree-sitter-typescript
|
|
25
|
+
Requires-Dist: uvicorn
|
|
26
|
+
Requires-Dist: watchdog
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# nelgraph 🚀
|
|
30
|
+
|
|
31
|
+
An autonomous, zero-configuration Knowledge Graph builder and semantic search engine optimized for local codebases and AI testing agents. It ingests source code, AST Call Graphs, and Git history into a hybrid Graph-Vector database (**Neo4j** + **ChromaDB**) using **DeepSeek V4-Flash**.
|
|
32
|
+
|
|
33
|
+
## 🛠️ Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install nelgraph
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 🚀 CLI Usage
|
|
40
|
+
|
|
41
|
+
### 1. Initialize GraphRAG for your project
|
|
42
|
+
Navigate to your project directory and run:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
nelgraph init
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
During initialization, it will prompt you for your OpenRouter API key, configure the local `.env` and `.graphrag_data/` directory, start Neo4j inside a local Docker container, build structural nodes and indexes, and automatically install a git post-commit hook so the graph auto-syncs.
|
|
49
|
+
|
|
50
|
+
### 2. Manual Synchronization
|
|
51
|
+
If you want to manually trigger incremental synchronization:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
nelgraph sync
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 3. Check Status
|
|
58
|
+
View current database metrics, indexed function counts, and enrichment coverage:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
nelgraph status
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 4. Run Watcher
|
|
65
|
+
Run a file watcher that auto-syncs the graph in the background when files change:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
nelgraph watch
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 🔌 Programmatic Python API
|
|
72
|
+
|
|
73
|
+
You can import `nelgraph` directly into your scripts or AI testing agents:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import nelgraph
|
|
77
|
+
|
|
78
|
+
# Configure (if .env is not present or needs custom config)
|
|
79
|
+
nelgraph.configure(
|
|
80
|
+
codebase_path="/absolute/path/to/project",
|
|
81
|
+
openrouter_api_key="your-openrouter-key"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# 1. Get snapshot of prioritized functions
|
|
85
|
+
snapshot = nelgraph.get_snapshot()
|
|
86
|
+
print(f"Total functions: {snapshot['total']}")
|
|
87
|
+
|
|
88
|
+
# 2. Retrieve detailed context for a function
|
|
89
|
+
ctx = nelgraph.get_function_context("process_order")
|
|
90
|
+
print(ctx.get("function", {}).get("raw_code"))
|
|
91
|
+
|
|
92
|
+
# 3. Mark function as tested/verified
|
|
93
|
+
nelgraph.mark_tested("process_order")
|
|
94
|
+
```
|
nelgraph-1.0.0/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# nelgraph 🚀
|
|
2
|
+
|
|
3
|
+
An autonomous, zero-configuration Knowledge Graph builder and semantic search engine optimized for local codebases and AI testing agents. It ingests source code, AST Call Graphs, and Git history into a hybrid Graph-Vector database (**Neo4j** + **ChromaDB**) using **DeepSeek V4-Flash**.
|
|
4
|
+
|
|
5
|
+
## 🛠️ Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install nelgraph
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 🚀 CLI Usage
|
|
12
|
+
|
|
13
|
+
### 1. Initialize GraphRAG for your project
|
|
14
|
+
Navigate to your project directory and run:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
nelgraph init
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
During initialization, it will prompt you for your OpenRouter API key, configure the local `.env` and `.graphrag_data/` directory, start Neo4j inside a local Docker container, build structural nodes and indexes, and automatically install a git post-commit hook so the graph auto-syncs.
|
|
21
|
+
|
|
22
|
+
### 2. Manual Synchronization
|
|
23
|
+
If you want to manually trigger incremental synchronization:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
nelgraph sync
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 3. Check Status
|
|
30
|
+
View current database metrics, indexed function counts, and enrichment coverage:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
nelgraph status
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 4. Run Watcher
|
|
37
|
+
Run a file watcher that auto-syncs the graph in the background when files change:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
nelgraph watch
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 🔌 Programmatic Python API
|
|
44
|
+
|
|
45
|
+
You can import `nelgraph` directly into your scripts or AI testing agents:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import nelgraph
|
|
49
|
+
|
|
50
|
+
# Configure (if .env is not present or needs custom config)
|
|
51
|
+
nelgraph.configure(
|
|
52
|
+
codebase_path="/absolute/path/to/project",
|
|
53
|
+
openrouter_api_key="your-openrouter-key"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# 1. Get snapshot of prioritized functions
|
|
57
|
+
snapshot = nelgraph.get_snapshot()
|
|
58
|
+
print(f"Total functions: {snapshot['total']}")
|
|
59
|
+
|
|
60
|
+
# 2. Retrieve detailed context for a function
|
|
61
|
+
ctx = nelgraph.get_function_context("process_order")
|
|
62
|
+
print(ctx.get("function", {}).get("raw_code"))
|
|
63
|
+
|
|
64
|
+
# 3. Mark function as tested/verified
|
|
65
|
+
nelgraph.mark_tested("process_order")
|
|
66
|
+
```
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GraphRAG Knowledge Base — Internal Python Module
|
|
3
|
+
|
|
4
|
+
Cách dùng nhanh nhất:
|
|
5
|
+
|
|
6
|
+
import graphrag
|
|
7
|
+
graphrag.configure(codebase_path="/path/to/project", openrouter_api_key="sk-...")
|
|
8
|
+
graphrag.run_init()
|
|
9
|
+
|
|
10
|
+
ctx = graphrag.get_function_context("processOrder")
|
|
11
|
+
snap = graphrag.get_snapshot()
|
|
12
|
+
changes = graphrag.get_changes("abc123f")
|
|
13
|
+
graphrag.mark_tested("processOrder")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# --- Public API ---
|
|
17
|
+
from nelgraph.knowledge_base import (
|
|
18
|
+
get_function_context,
|
|
19
|
+
get_snapshot,
|
|
20
|
+
get_changes,
|
|
21
|
+
mark_tested,
|
|
22
|
+
search,
|
|
23
|
+
run_init,
|
|
24
|
+
run_sync,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__version__ = "1.0.0"
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"configure",
|
|
31
|
+
"get_function_context",
|
|
32
|
+
"get_snapshot",
|
|
33
|
+
"get_changes",
|
|
34
|
+
"mark_tested",
|
|
35
|
+
"search",
|
|
36
|
+
"run_init",
|
|
37
|
+
"run_sync",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def configure(
|
|
42
|
+
codebase_path: str = None,
|
|
43
|
+
openrouter_api_key: str = None,
|
|
44
|
+
neo4j_uri: str = None,
|
|
45
|
+
neo4j_password: str = None,
|
|
46
|
+
neo4j_user: str = None,
|
|
47
|
+
llm_model: str = None,
|
|
48
|
+
embedding_model: str = None,
|
|
49
|
+
embedding_dimensions: int = None,
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
Cấu hình graphrag bằng code thay vì .env file.
|
|
53
|
+
Gọi hàm này TRƯỚC khi dùng bất kỳ function nào khác.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
codebase_path: Đường dẫn tuyệt đối đến codebase cần analyze.
|
|
57
|
+
openrouter_api_key: API key của OpenRouter.
|
|
58
|
+
neo4j_uri: URI kết nối Neo4j (default: bolt://127.0.0.1:7687).
|
|
59
|
+
neo4j_password: Password Neo4j.
|
|
60
|
+
neo4j_user: Username Neo4j (default: neo4j).
|
|
61
|
+
llm_model: Model ID trên OpenRouter cho LLM enrichment.
|
|
62
|
+
embedding_model: Model ID trên OpenRouter cho embeddings.
|
|
63
|
+
embedding_dimensions: Số chiều vector (default: 512).
|
|
64
|
+
|
|
65
|
+
Ví dụ:
|
|
66
|
+
graphrag.configure(
|
|
67
|
+
codebase_path="/home/user/opensourcepos",
|
|
68
|
+
openrouter_api_key="sk-or-...",
|
|
69
|
+
)
|
|
70
|
+
"""
|
|
71
|
+
import os
|
|
72
|
+
import nelgraph.config as _cfg
|
|
73
|
+
|
|
74
|
+
if codebase_path:
|
|
75
|
+
import os as _os
|
|
76
|
+
codebase_path = _os.path.abspath(codebase_path).replace("\\", "/")
|
|
77
|
+
_cfg.CODEBASE_PATH = codebase_path
|
|
78
|
+
os.environ["CODEBASE_PATH"] = codebase_path
|
|
79
|
+
|
|
80
|
+
# Recalculate dependent paths
|
|
81
|
+
_cfg.GRAPHRAG_DATA_DIR = _os.path.join(codebase_path, ".graphrag_data").replace("\\", "/")
|
|
82
|
+
_cfg.NEO4J_DATA_DIR = _os.path.join(_cfg.GRAPHRAG_DATA_DIR, "neo4j", "data").replace("\\", "/")
|
|
83
|
+
_cfg.NEO4J_LOGS_DIR = _os.path.join(_cfg.GRAPHRAG_DATA_DIR, "neo4j", "logs").replace("\\", "/")
|
|
84
|
+
_cfg.CHROMA_PATH = _os.path.join(_cfg.GRAPHRAG_DATA_DIR, "chromadb").replace("\\", "/")
|
|
85
|
+
_cfg.SYNC_STATE_PATH = _os.path.join(_cfg.GRAPHRAG_DATA_DIR, "sync_state.json").replace("\\", "/")
|
|
86
|
+
|
|
87
|
+
if openrouter_api_key:
|
|
88
|
+
_cfg.OPENROUTER_API_KEY = openrouter_api_key
|
|
89
|
+
os.environ["OPENROUTER_API_KEY"] = openrouter_api_key
|
|
90
|
+
# Reset lazy clients so they pick up the new key
|
|
91
|
+
_reset_ai_clients()
|
|
92
|
+
|
|
93
|
+
if neo4j_uri:
|
|
94
|
+
_cfg.NEO4J_URI = neo4j_uri
|
|
95
|
+
os.environ["NEO4J_URI"] = neo4j_uri
|
|
96
|
+
|
|
97
|
+
if neo4j_password:
|
|
98
|
+
_cfg.NEO4J_PASSWORD = neo4j_password
|
|
99
|
+
os.environ["NEO4J_PASSWORD"] = neo4j_password
|
|
100
|
+
# Reset Neo4j singleton
|
|
101
|
+
import nelgraph.graph.neo4j_client as _nc
|
|
102
|
+
_nc._client = None
|
|
103
|
+
|
|
104
|
+
if neo4j_user:
|
|
105
|
+
_cfg.NEO4J_USER = neo4j_user
|
|
106
|
+
|
|
107
|
+
if llm_model:
|
|
108
|
+
_cfg.LLM_MODEL = llm_model
|
|
109
|
+
|
|
110
|
+
if embedding_model:
|
|
111
|
+
_cfg.EMBEDDING_MODEL = embedding_model
|
|
112
|
+
|
|
113
|
+
if embedding_dimensions:
|
|
114
|
+
_cfg.EMBEDDING_DIMENSIONS = embedding_dimensions
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _reset_ai_clients():
|
|
118
|
+
"""Reset tất cả lazy OpenAI client singletons để pick up config mới."""
|
|
119
|
+
try:
|
|
120
|
+
import nelgraph.embeddings.embedder as _emb
|
|
121
|
+
_emb._openai_client = None
|
|
122
|
+
except Exception:
|
|
123
|
+
pass
|
|
124
|
+
try:
|
|
125
|
+
import nelgraph.extractors.testing_enricher as _te
|
|
126
|
+
_te._client_ai = None
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
try:
|
|
130
|
+
import nelgraph.extractors.llm_extractor as _le
|
|
131
|
+
_le._client = None
|
|
132
|
+
except Exception:
|
|
133
|
+
pass
|
|
134
|
+
try:
|
|
135
|
+
import nelgraph.community.summarizer as _sm
|
|
136
|
+
_sm._client_ai = None
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def status() -> dict:
|
|
142
|
+
"""
|
|
143
|
+
Trả về trạng thái hiện tại của graph.
|
|
144
|
+
Không cần Neo4j đang chạy — nếu không kết nối được thì báo offline.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
{
|
|
148
|
+
"neo4j": "connected" | "offline",
|
|
149
|
+
"codebase_path": "...",
|
|
150
|
+
"last_sync": "...",
|
|
151
|
+
"total_functions": 0,
|
|
152
|
+
"enriched_functions": 0,
|
|
153
|
+
}
|
|
154
|
+
"""
|
|
155
|
+
import nelgraph.config as _cfg
|
|
156
|
+
from nelgraph.initialize_graph import _load_sync_state
|
|
157
|
+
|
|
158
|
+
result = {
|
|
159
|
+
"neo4j": "offline",
|
|
160
|
+
"codebase_path": _cfg.CODEBASE_PATH,
|
|
161
|
+
"last_sync": None,
|
|
162
|
+
"total_functions": 0,
|
|
163
|
+
"enriched_functions": 0,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
sync_state = _load_sync_state()
|
|
167
|
+
if sync_state:
|
|
168
|
+
result["last_sync"] = sync_state.get("last_sync_time")
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
from nelgraph.graph.neo4j_client import get_client
|
|
172
|
+
client = get_client()
|
|
173
|
+
stats = client.run("""
|
|
174
|
+
OPTIONAL MATCH (f:Function) WITH count(f) as total
|
|
175
|
+
OPTIONAL MATCH (f2:Function) WHERE f2.how_it_works IS NOT NULL
|
|
176
|
+
RETURN total, count(f2) as enriched
|
|
177
|
+
""")
|
|
178
|
+
if stats:
|
|
179
|
+
result["neo4j"] = "connected"
|
|
180
|
+
result["total_functions"] = stats[0]["total"]
|
|
181
|
+
result["enriched_functions"] = stats[0]["enriched"]
|
|
182
|
+
except Exception:
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
return result
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import requests
|
|
6
|
+
import nelgraph
|
|
7
|
+
|
|
8
|
+
if sys.platform.startswith("win"):
|
|
9
|
+
try:
|
|
10
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
11
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
12
|
+
except Exception:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
def _check_for_updates():
|
|
18
|
+
try:
|
|
19
|
+
# Check PyPI version dynamically (timeout after 2s to prevent CLI blocking)
|
|
20
|
+
res = requests.get("https://pypi.org/pypi/nelgraph/json", timeout=2)
|
|
21
|
+
latest = res.json()["info"]["version"]
|
|
22
|
+
from nelgraph import __version__
|
|
23
|
+
if latest != __version__:
|
|
24
|
+
console.print(
|
|
25
|
+
f"[yellow]Update available: {__version__} → {latest}[/yellow]\n"
|
|
26
|
+
f"Run: [bold]pip install --upgrade nelgraph[/bold]"
|
|
27
|
+
)
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@click.group()
|
|
32
|
+
def main():
|
|
33
|
+
"""nelgraph — Codebase Knowledge Graph & Semantic Search CLI"""
|
|
34
|
+
_check_for_updates()
|
|
35
|
+
|
|
36
|
+
def _install_git_hook(codebase_path: str):
|
|
37
|
+
git_dir = os.path.join(codebase_path, ".git")
|
|
38
|
+
if not os.path.exists(git_dir):
|
|
39
|
+
return
|
|
40
|
+
hooks_dir = os.path.join(git_dir, "hooks")
|
|
41
|
+
os.makedirs(hooks_dir, exist_ok=True)
|
|
42
|
+
hook_path = os.path.join(hooks_dir, "post-commit")
|
|
43
|
+
|
|
44
|
+
# Simple shell hook to run nelgraph sync in background silently
|
|
45
|
+
hook_content = """#!/bin/sh
|
|
46
|
+
# Auto-sync graph in background after commit
|
|
47
|
+
nelgraph sync --silent &
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
with open(hook_path, "w", newline="\n", encoding="utf-8") as f:
|
|
51
|
+
f.write(hook_content)
|
|
52
|
+
# Make hook executable
|
|
53
|
+
import stat
|
|
54
|
+
st = os.stat(hook_path)
|
|
55
|
+
os.chmod(hook_path, st.st_mode | stat.S_IEXEC)
|
|
56
|
+
console.print("[green]✓ Git post-commit hook installed successfully.[/green]")
|
|
57
|
+
except Exception as e:
|
|
58
|
+
console.print(f"[yellow]⚠ Warning: Could not install Git post-commit hook: {e}[/yellow]")
|
|
59
|
+
|
|
60
|
+
@main.command()
|
|
61
|
+
@click.option("--key", help="OpenRouter API key")
|
|
62
|
+
@click.option("--path", default=".", help="Path to codebase (default: current dir)")
|
|
63
|
+
def init(key, path):
|
|
64
|
+
"""
|
|
65
|
+
Khởi tạo GraphRAG cho project hiện tại.
|
|
66
|
+
Tạo .env, start Neo4j Docker, parse + enrich toàn bộ codebase.
|
|
67
|
+
"""
|
|
68
|
+
abs_path = os.path.abspath(path).replace("\\", "/")
|
|
69
|
+
|
|
70
|
+
# Load env from target path if exists
|
|
71
|
+
from dotenv import load_dotenv
|
|
72
|
+
env_path = os.path.join(abs_path, ".env")
|
|
73
|
+
if os.path.exists(env_path):
|
|
74
|
+
load_dotenv(env_path)
|
|
75
|
+
|
|
76
|
+
api_key = key or os.getenv("OPENROUTER_API_KEY")
|
|
77
|
+
if not api_key:
|
|
78
|
+
api_key = click.prompt("OpenRouter API key", hide_input=True)
|
|
79
|
+
|
|
80
|
+
# Write or update .env in target directory
|
|
81
|
+
lines = []
|
|
82
|
+
if os.path.exists(env_path):
|
|
83
|
+
with open(env_path, "r", encoding="utf-8") as f:
|
|
84
|
+
lines = f.readlines()
|
|
85
|
+
|
|
86
|
+
has_key = False
|
|
87
|
+
has_path = False
|
|
88
|
+
new_lines = []
|
|
89
|
+
for line in lines:
|
|
90
|
+
if line.strip().startswith("OPENROUTER_API_KEY="):
|
|
91
|
+
new_lines.append(f"OPENROUTER_API_KEY={api_key}\n")
|
|
92
|
+
has_key = True
|
|
93
|
+
elif line.strip().startswith("CODEBASE_PATH="):
|
|
94
|
+
new_lines.append(f"CODEBASE_PATH={abs_path}\n")
|
|
95
|
+
has_path = True
|
|
96
|
+
else:
|
|
97
|
+
new_lines.append(line)
|
|
98
|
+
|
|
99
|
+
if not has_key:
|
|
100
|
+
new_lines.append(f"OPENROUTER_API_KEY={api_key}\n")
|
|
101
|
+
if not has_path:
|
|
102
|
+
new_lines.append(f"CODEBASE_PATH={abs_path}\n")
|
|
103
|
+
|
|
104
|
+
with open(env_path, "w", encoding="utf-8") as f:
|
|
105
|
+
f.writelines(new_lines)
|
|
106
|
+
|
|
107
|
+
console.print(f"[green]✓ Configured {env_path}[/green]")
|
|
108
|
+
|
|
109
|
+
# Programmatically configure nelgraph
|
|
110
|
+
nelgraph.configure(codebase_path=abs_path, openrouter_api_key=api_key)
|
|
111
|
+
|
|
112
|
+
# Run full initialization pipeline
|
|
113
|
+
nelgraph.run_init()
|
|
114
|
+
|
|
115
|
+
# Install git post-commit hook
|
|
116
|
+
_install_git_hook(abs_path)
|
|
117
|
+
|
|
118
|
+
@main.command()
|
|
119
|
+
@click.option("--silent", is_flag=True, help="Run silently without printing to stdout")
|
|
120
|
+
def sync(silent):
|
|
121
|
+
"""Sync thủ công — parse files đã thay đổi kể từ lần sync cuối."""
|
|
122
|
+
if silent:
|
|
123
|
+
# Redirect stdout/stderr to devnull
|
|
124
|
+
sys.stdout = open(os.devnull, 'w')
|
|
125
|
+
sys.stderr = open(os.devnull, 'w')
|
|
126
|
+
|
|
127
|
+
# Run sync pipeline
|
|
128
|
+
nelgraph.run_sync()
|
|
129
|
+
|
|
130
|
+
@main.command()
|
|
131
|
+
def status():
|
|
132
|
+
"""Xem trạng thái graph hiện tại."""
|
|
133
|
+
# Run status helper
|
|
134
|
+
from nelgraph.initialize_graph import run_status
|
|
135
|
+
run_status()
|
|
136
|
+
|
|
137
|
+
@main.command()
|
|
138
|
+
def watch():
|
|
139
|
+
"""Chạy file watcher — tự sync khi có file thay đổi."""
|
|
140
|
+
from nelgraph.updater.watcher import start_watcher
|
|
141
|
+
start_watcher()
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# GraphRAG Community Detection and Summarization Package
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
import igraph as ig
|
|
3
|
+
import leidenalg
|
|
4
|
+
from nelgraph.graph.neo4j_client import get_client
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def build_networkx_graph() -> nx.Graph:
|
|
8
|
+
"""Convert Neo4j graph to NetworkX graph for algorithms."""
|
|
9
|
+
client = get_client()
|
|
10
|
+
|
|
11
|
+
G = nx.Graph()
|
|
12
|
+
|
|
13
|
+
# Get all nodes
|
|
14
|
+
nodes = client.run("MATCH (n) WHERE n.name IS NOT NULL RETURN elementId(n) as id, labels(n) as labels, n.name as name")
|
|
15
|
+
for record in nodes:
|
|
16
|
+
G.add_node(record["id"], name=record["name"], label=record["labels"][0] if record["labels"] else "Unknown")
|
|
17
|
+
|
|
18
|
+
# Get all edges
|
|
19
|
+
edges = client.run("MATCH (a)-[r]->(b) WHERE a.name IS NOT NULL AND b.name IS NOT NULL RETURN elementId(a) as from_id, elementId(b) as to_id, type(r) as rel_type")
|
|
20
|
+
for record in edges:
|
|
21
|
+
G.add_edge(record["from_id"], record["to_id"], rel_type=record["rel_type"])
|
|
22
|
+
|
|
23
|
+
print(f"[Community] NetworkX graph: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges")
|
|
24
|
+
return G
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def detect_communities() -> dict:
|
|
28
|
+
"""
|
|
29
|
+
Run Leiden algorithm via igraph/leidenalg.
|
|
30
|
+
Returns mapping node_id -> community_id.
|
|
31
|
+
Saves community_id to Neo4j nodes.
|
|
32
|
+
"""
|
|
33
|
+
G = build_networkx_graph()
|
|
34
|
+
if G.number_of_nodes() == 0:
|
|
35
|
+
print("[Community] Empty graph, skipping community detection.")
|
|
36
|
+
return {}
|
|
37
|
+
|
|
38
|
+
# Convert NetworkX to igraph
|
|
39
|
+
nx_nodes = list(G.nodes())
|
|
40
|
+
node_id_map = {n: i for i, n in enumerate(nx_nodes)}
|
|
41
|
+
|
|
42
|
+
ig_graph = ig.Graph()
|
|
43
|
+
ig_graph.add_vertices(len(nx_nodes))
|
|
44
|
+
|
|
45
|
+
for u, v in G.edges():
|
|
46
|
+
ig_graph.add_edge(node_id_map[u], node_id_map[v])
|
|
47
|
+
|
|
48
|
+
# Run Leiden algorithm
|
|
49
|
+
partition = leidenalg.find_partition(ig_graph, leidenalg.ModularityVertexPartition)
|
|
50
|
+
|
|
51
|
+
# Build result mapping: neo4j_node_id -> community_id
|
|
52
|
+
result = {}
|
|
53
|
+
for community_id, members in enumerate(partition):
|
|
54
|
+
for member_idx in members:
|
|
55
|
+
neo4j_id = nx_nodes[member_idx]
|
|
56
|
+
result[neo4j_id] = community_id
|
|
57
|
+
|
|
58
|
+
print(f"[Community] Detected {len(partition)} communities.")
|
|
59
|
+
|
|
60
|
+
# Save community IDs to Neo4j
|
|
61
|
+
client = get_client()
|
|
62
|
+
for node_id, community_id in result.items():
|
|
63
|
+
client.run("""
|
|
64
|
+
MATCH (n) WHERE elementId(n) = $node_id
|
|
65
|
+
SET n.community_id = $community_id
|
|
66
|
+
""", {"node_id": node_id, "community_id": community_id})
|
|
67
|
+
|
|
68
|
+
return result
|