Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Signatures below are the real ones - `tests/test_architecture_doc.py` imports ev
| `cache.py` | `check_semantic_cache(files, root)`, `save_semantic_cache(nodes, edges, ...)` | files → cached nodes / edges / hyperedges + the list of files still needing extraction |
| `security.py` | `validate_url`, `safe_fetch`, `validate_graph_path`, `sanitize_label` | URL / path / label → validated value, or raises |
| `validate.py` | `validate_extraction(data)`, `assert_valid(data)` | extraction dict → **list of schema error strings** (`validate_extraction` returns them; `assert_valid` raises) |
| `storage.py` | `init_db / ingest_extraction / ingest_communities` | extraction dict → NeuG `graph.db` (optional, requires `neug`) |
| `serve.py` | `serve(graph_path)`, `serve_http(graph_path, *, host, port, ...)` | graph file path → MCP stdio server / HTTP server |
| `watch.py` | `watch(watch_path, debounce=3.0)`, `check_update(watch_path)` | directory → rebuild on change; `check_update` reports whether a re-extraction is pending |
| `benchmark.py` | `run_benchmark(graph_path)` | graph file → corpus vs subgraph token comparison |
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ Codex users also need `multi_agent = true` under `[features]` in `~/.codex/confi
| `mcp` | MCP stdio server | `uv tool install "graphifyy[mcp]"` |
| `neo4j` | Neo4j push support | `uv tool install "graphifyy[neo4j]"` |
| `falkordb` | FalkorDB push support | `uv tool install "graphifyy[falkordb]"` |
| `neug` | [NeuG](https://github.com/alibaba/neug) embedded graph database — Cypher queries on your graph | `uv tool install "graphifyy[neug]"` |
| `svg` | SVG graph export | `uv tool install "graphifyy[svg]"` |
| `leiden` | Leiden community detection (Python < 3.13 only) | `uv tool install "graphifyy[leiden]"` |
| `ollama` | Ollama local inference | `uv tool install "graphifyy[ollama]"` |
Expand Down Expand Up @@ -788,6 +789,15 @@ graphify cluster-only ./my-project --backend=gemini # backend for com
graphify cluster-only ./my-project --backend=gemini --model gemini-2.5-pro # specific model
graphify label ./my-project # (re)name communities with the configured backend
graphify label ./my-project --backend=openai --model gpt-4o # force a specific backend and model

# NeuG embedded graph DB (requires the neug extra)
GRAPHIFY_NEUG=1 graphify extract ./raw # opt-in NeuG pipeline: build graph.db + cluster with neug GDS Leiden (stays active once graph.db exists, no env var needed after that)
GRAPHIFY_NEUG=1 graphify extract ./raw --resolution 1.2 # tune Leiden resolution
GRAPHIFY_NEUG=1 graphify extract ./raw --cluster-on-files # file-level communities
graphify delta-cluster ./raw # incremental community analysis on an existing graph.db
graphify delta-cluster ./raw --baseline communities.json # seed from an external clustering: old communities stay frozen, new nodes get assigned on top
graphify cypher "MATCH (n) RETURN n LIMIT 10" # query graph.db with Cypher
graphify cypher "MATCH (n:node)-[e:edge]->(m:node) RETURN n.id, e.relation, m.id LIMIT 10" --db path/to/graph.db # default: graphify-out/graph.db
```

> **Community names:** inside an agent (Claude Code, Gemini CLI) the agent names communities itself. When you run the bare CLI, `cluster-only` auto-names them with the configured backend (built-in or custom OpenAI-compatible provider) — pass `--no-label` to keep `Community N`, or run `graphify label` to (re)generate names on demand.
Expand Down
2 changes: 2 additions & 0 deletions graphify/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,8 @@ def _run_cli() -> None:
print(" --model=<name> model to use for community naming")
print(" --max-concurrency=N parallel labeling LLM calls (default 4; forced to 1 for ollama/claude-cli)")
print(" --batch-size=N communities per labeling LLM call (default 100)")
print(" cypher \"MATCH ...\" execute a Cypher query against graph.db (requires neug)")
print(" --db <path> path to graph.db (default graphify-out/graph.db)")
print(" query \"<question>\" BFS traversal of graph.json for a question")
print(" --dfs use depth-first instead of breadth-first")
print(" --context C explicit edge-context filter (repeatable)")
Expand Down
745 changes: 557 additions & 188 deletions graphify/cli.py

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions graphify/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -1533,6 +1533,21 @@ def _build_server(graph_path: str):
_default_graph_path = str(Path(graph_path).resolve())
_ctx_cache = _GraphContextCache(_max_server_contexts())

# NeuG embedded graph database for Cypher queries on graph.db
_neug_conn = None
_neug_db = None
_neug_execute = None
try:
from graphify.storage import init_db as _neug_init, execute_cypher as _neug_exec, close_db as _neug_close
_neug_db_path = str(Path(graph_path).parent / "graph.db")
if Path(_neug_db_path).exists():
_neug_db, _neug_conn = _neug_init(_neug_db_path)
_neug_execute = _neug_exec
except ImportError:
pass
except Exception:
pass

def _load_ctx(path: str):
"""Return the current default or project graph context as a tool error.

Expand Down Expand Up @@ -1706,6 +1721,20 @@ async def list_tools() -> list[types.Tool]:
},
},
),
types.Tool(
name="cypher_query",
description=(
"Execute a Cypher query against the NeuG graph database. "
"Returns tabular results. Requires neug to be installed and graph.db to exist."
),
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "Cypher query string"},
},
"required": ["query"],
},
),
]
# Multi-project support: every tool accepts an optional project_path.
# Injected here (rather than repeated in 11 literal schemas) so the set
Expand Down Expand Up @@ -1949,6 +1978,22 @@ def _tool_triage_prs(arguments: dict) -> str:
)
return "\n\n".join(lines)

def _tool_cypher_query(arguments: dict) -> str:
if _neug_conn is None:
return "NeuG not available (not installed or graph.db not found)."
query = arguments["query"]
from graphify.storage import execute_cypher as _exec_cypher
try:
results = _exec_cypher(_neug_conn, query)
except RuntimeError as exc:
return f"Cypher error: {exc}"
if not results:
return "No results."
lines = []
for row in results:
lines.append("\t".join(str(v) for v in row))
return "\n".join(lines)

_handlers = {
"query_graph": _tool_query_graph,
"get_node": _tool_get_node,
Expand All @@ -1960,6 +2005,7 @@ def _tool_triage_prs(arguments: dict) -> str:
"list_prs": _tool_list_prs,
"get_pr_impact": _tool_get_pr_impact,
"triage_prs": _tool_triage_prs,
"cypher_query": _tool_cypher_query,
}

def _load_community_labels() -> dict[int, str]:
Expand Down
Loading