Graphify là công cụ chạy kèm các trợ lý AI lập trình như Claude Code, Cursor, Gemini CLI — nên bài này hợp nhất với người đang code hoặc đang dùng các công cụ đó. Bạn không cần biết lập trình giỏi, nhưng cần thoải mái mở cửa sổ dòng lệnh (gọi là Terminal trên Mac/Linux, PowerShell trên Windows — nơi bạn gõ chữ thay vì bấm chuột) để gõ vài lệnh cài đặt. Mọi thuật ngữ kỹ thuật trong bài đều được giải thích lại bằng lời thường, để dù chưa quen bạn vẫn theo được.
Nếu bạn từng nhờ AI giải thích một dự án code lớn, bạn sẽ thấy: mỗi lần hỏi, AI phải đọc lại hàng trăm file từ đầu để hiểu mọi thứ liên quan với nhau thế nào. Việc này chậm, tốn rất nhiều token (đơn vị đo lượng chữ AI đọc/viết mỗi lần — token càng nhiều thì càng tốn thời gian và chi phí), và đôi khi câu trả lời vẫn sai vì AI không nhìn được toàn cảnh. Graphify ra đời để giải quyết đúng vấn đề này.
Bạn chỉ cần chạy lệnh /graphify . một lần — Graphify sẽ "đọc" toàn bộ dự án và vẽ lại thành một sơ đồ quan hệ giữa mọi thành phần (gọi là knowledge graph, tạm dịch đồ thị tri thức). Từ lần sau, mỗi khi bạn hỏi AI điều gì về dự án, AI sẽ tra sơ đồ này thay vì đọc lại toàn bộ code từ đầu. Kết quả đo thực tế: ít hơn 71,5 lần lượng token cho mỗi câu hỏi, so với cách đọc file trực tiếp. Đọc xong tài liệu này, bạn sẽ cài và dùng được Graphify ngay.
Trong tài liệu này
Graphify là gì và tại sao cần nó
Hãy tưởng tượng dự án của bạn như một thành phố lớn. Khi bạn hỏi AI "đường nào nối khu A với khu B?", cách cũ là AI phải đi bộ qua từng con phố để tìm hiểu (đọc từng file một). Graphify xây sẵn một tấm bản đồ toàn thành phố — sau đó AI chỉ cần tra bản đồ, không cần đi bộ nữa.
Nói đơn giản, Graphify là một skill — một "gói kỹ năng" bổ sung mà bạn cài thêm cho AI coding assistant của mình. Cài một lần, sau đó AI sẽ tự biết cách xây và tra cứu sơ đồ tri thức mỗi khi cần, không phải nhắc lại.
Graphify làm được gì cụ thể?
- Đọc hiểu cấu trúc code hoàn toàn trên máy bạn (kỹ thuật gọi là phân tích AST — xem box bên dưới) — không gửi code lên server nào
- Hỗ trợ 28 ngôn ngữ lập trình phổ biến: Python, JavaScript, TypeScript, Go, Rust, Java, C/C++, Ruby, và nhiều hơn
- Đọc được cả tài liệu ngoài code: PDF, Word, Excel, Markdown, HTML, ảnh, video (cần cài thêm gói hỗ trợ)
- Tự tìm ra những phần "trung tâm" của dự án — nơi mọi thứ khác đều phụ thuộc vào
- Phát hiện những kết nối bất ngờ giữa các phần tưởng chừng không liên quan
- Tự cập nhật sơ đồ mỗi khi bạn lưu code lên Git (qua "git hook" — một đoạn lệnh tự chạy sau mỗi lần lưu, không cần bạn nhớ gọi tay)
- Chia sẻ sơ đồ này cho cả nhóm dùng chung qua một máy chủ nhỏ (MCP server)
AST (Abstract Syntax Tree — cây cú pháp trừu tượng) là cách máy tính hiểu cấu trúc code, tương tự như sơ đồ tổ chức của một công ty — biết hàm nào gọi hàm nào, lớp nào kế thừa từ đâu, module nào phụ thuộc vào module nào. Graphify dùng thư viện tree-sitter để phân tích AST hoàn toàn trên máy bạn, không cần internet.
Cài đặt từng bước (dành cho người mới)
Graphify cần máy bạn đã có Python (ngôn ngữ lập trình, bản 3.10 trở lên) — hầu hết máy Mac đều có sẵn, Windows thường cần tải thêm. Cách cài đơn giản nhất là dùng uv, một công cụ cài đặt hiện đại tự lo phần khó nhất cho bạn: tự thêm lệnh vào PATH (danh sách "nơi máy tìm lệnh" — nếu thiếu bước này, máy sẽ báo "command not found" khi bạn gõ graphify) và tự tạo môi trường riêng để không làm rối các phần mềm Python khác trên máy.
- Cài uv — chạy lệnh này trong Terminal (macOS/Linux) hoặc PowerShell (Windows):
macOS/Linux:curl -LsSf https://astral.sh/uv/install.sh | sh
Windows:winget install astral-sh.uv - Cài Graphify bằng uv — uv sẽ tự thêm lệnh vào PATH:
uv tool install graphifyy
(lưu ý: tên package trên PyPI là graphifyy — hai chữ y, nhưng lệnh dùng vẫn là graphify) - Đăng ký skill với AI coding assistant của bạn:
graphify install
Lệnh này tự phát hiện Claude Code, Codex, Cursor... và ghi skill vào đúng chỗ. - Mở AI assistant và thử ngay — gõ trong Claude Code, Gemini CLI hoặc Cursor:
/graphify .
Cài nhanh bằng: brew install python@3.12 uv — sau đó tiếp tục bước 2.
Kiểm tra Python đã cài chưa
Mở Terminal (trên Mac/Linux) hoặc Command Prompt (trên Windows) và gõ:
python --version
# hoặc
python3 --version
Nếu thấy Python 3.10 trở lên là ổn. Nếu chưa có, tải từ python.org.
Cài uv và Graphify
uv — hiểu nôm na là "npm của Python" — giúp cài package nhanh và không gây xung đột môi trường. Sau khi cài uv xong, chạy tiếp:
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install graphifyy
winget install astral-sh.uv
uv tool install graphifyy
Đăng ký skill với AI assistant
Lệnh graphify install tự phát hiện bạn đang dùng công cụ nào (Claude Code, Cursor, Gemini CLI...) và ghi file cấu hình vào đúng vị trí. Chỉ cần chạy một lần cho mỗi máy:
# Cài cho tất cả platforms tự phát hiện:
graphify install
# Hoặc chỉ định platform cụ thể:
graphify install --platform codex # cho Codex
graphify cursor install # cho Cursor
graphify install --platform windows # cho Claude Code trên Windows
Sau khi chạy xong, bạn có thể dùng ngay trong AI assistant mà không cần cài thêm gì.
Tránh dùng pip install graphifyy trên Mac hoặc Windows nếu có thể. Graphify cần biết đường dẫn Python chính xác khi chạy — pip cài vào một môi trường khác dễ gây lỗi "ModuleNotFoundError" sau đó. Dùng uv hoặc pipx sẽ tránh vấn đề này hoàn toàn.
Cách dùng cơ bản — 3 lệnh quan trọng nhất
Cài đặt xong là phần "khó" duy nhất. Từ đây, bạn không cần đụng đến Terminal nữa — chỉ cần gõ lệnh ngay trong cửa sổ chat của AI assistant, giống như đang chat bình thường. Có 3 lệnh bạn sẽ dùng thường xuyên nhất:
Xây graph lần đầu
Mở Claude Code (hoặc Cursor, Gemini CLI...), điều hướng đến thư mục dự án, rồi gõ:
/graphify . # phân tích thư mục hiện tại
/graphify ./src # chỉ phân tích thư mục src
/graphify . --mode deep # phân tích sâu hơn (nhiều edge hơn)
Lần đầu sẽ tốn thời gian và token (AI đọc file, xây graph). Các lần sau rất nhanh vì chỉ cập nhật phần thay đổi.
PowerShell xử lý dấu / đầu câu là đường dẫn thư mục. Trên Windows, bỏ dấu slash: gõ graphify . thay vì /graphify .
Truy vấn graph (hỏi về codebase)
Sau khi graph đã được xây, bạn có thể hỏi bất kỳ câu nào về dự án — AI sẽ tra đồ thị thay vì đọc lại file:
/graphify query "auth flow hoạt động thế nào?"
/graphify query "cái gì connect database với user service?"
/graphify path "UserService" "DatabasePool" # đường ngắn nhất giữa 2 node
/graphify explain "RateLimiter" # giải thích một khái niệm
Cập nhật graph sau khi sửa code
Khi bạn đã thay đổi một số file, chỉ cần cập nhật phần thay đổi — nhanh hơn nhiều so với xây lại từ đầu:
/graphify ./src --update # chỉ re-extract file đã thay đổi
# Hoặc cài git hook để tự động rebuild mỗi khi commit:
graphify hook install
Git hook — hiểu nôm na là một "cái bẫy tự động" chạy sau mỗi lần bạn commit code. Với graphify hook install, graph của bạn luôn cập nhật mà bạn không cần nhớ gọi lệnh thủ công.
3 file output và ý nghĩa của chúng
Sau khi chạy /graphify ., Graphify tạo ra một thư mục graphify-out/ với 3 file chính:
graph.html — Bản đồ tương tác
Mở file này trong trình duyệt (Chrome, Firefox, Safari...) — bạn sẽ thấy một đồ thị có thể click vào node, filter theo module, tìm kiếm concept. Rất tiện để "khám phá" codebase mà không cần đọc code.
GRAPH_REPORT.md — Báo cáo dễ đọc
File Markdown này chứa những điểm nổi bật nhất: God nodes (các điểm trung tâm mà mọi thứ phụ thuộc), surprising connections (kết nối bất ngờ giữa các module), lý do thiết kế được trích từ comment trong code, và 4–5 câu hỏi mà graph đặc biệt phù hợp để trả lời.
graph.json — Dữ liệu thô cho AI
Đây là file AI assistant đọc khi bạn truy vấn. Mỗi quan hệ được đánh dấu EXTRACTED (lấy trực tiếp từ code), INFERRED (AI suy luận ra), hoặc AMBIGUOUS (không chắc chắn) — giúp bạn biết cái nào đáng tin cái nào cần kiểm tra lại.
Nên. Khi commit graphify-out/, tất cả thành viên trong team sẽ có bản đồ sẵn ngay sau khi git pull — không ai phải xây lại từ đầu. Chỉ cần thêm vào .gitignore: graphify-out/cost.json (chứa thông tin chi phí, không cần share).
Hỗ trợ 20+ nền tảng AI coding
Graphify hoạt động với hầu hết các AI coding assistant phổ biến hiện nay. Nếu bạn chỉ dùng một công cụ duy nhất (ví dụ Claude Code), bạn có thể bỏ qua bảng này — lệnh graphify install ở phần cài đặt đã tự nhận diện và cài đúng chỗ rồi. Bảng dưới đây chỉ để tham khảo khi bạn dùng công cụ khác hoặc muốn đổi sang dùng chung cho team:
| Platform | Lệnh cài | Ghi chú |
|---|---|---|
| Claude Code (macOS/Linux) | graphify install | Tạo CLAUDE.md + PreToolUse hook |
| Claude Code (Windows) | graphify install --platform windows | Dùng graphify . thay vì /graphify . |
| Cursor | graphify cursor install | Ghi vào .cursor/rules/ — tự động mọi cuộc trò chuyện |
| Gemini CLI | graphify install --platform gemini | Tạo GEMINI.md + BeforeTool hook |
| Codex | graphify install --platform codex | Dùng $graphify thay vì /graphify |
| GitHub Copilot CLI | graphify install --platform copilot | |
| Aider | graphify install --platform aider | Ghi vào AGENTS.md |
| OpenCode | graphify install --platform opencode | |
| Amp | graphify amp install | |
| Kiro IDE/CLI | graphify kiro install | Ghi vào .kiro/skills/ |
| Devin CLI | graphify devin install |
Thêm flag --project để skill được cài vào thư mục dự án thay vì profile cá nhân — tiện cho team và có thể commit vào git: graphify install --project hoặc graphify claude install --project
Tính năng nâng cao: MCP, PDF, video, PostgreSQL
Phần này có thể bỏ qua nếu bạn chỉ cần dùng Graphify cho code thông thường — mặc định Graphify đã xử lý tốt code và file Markdown rồi. Đây là các tùy chọn dành cho nhu cầu đặc biệt hơn: đọc thêm PDF/Word/Excel, transcribe video, kết nối database, hoặc chia sẻ graph cho cả team. Mỗi tính năng cần cài thêm một "extra" (gói mở rộng) tương ứng:
Đọc PDF và tài liệu Word/Excel
uv tool install "graphifyy[pdf]" # thêm hỗ trợ PDF
uv tool install "graphifyy[office]" # thêm hỗ trợ .docx và .xlsx
Transcribe video và audio
Graphify dùng faster-whisper để transcribe video/audio hoàn toàn cục bộ — không cần API key, không gửi dữ liệu đi đâu:
uv tool install "graphifyy[video]"
# Sau đó trong AI assistant:
/graphify add https://youtube.com/watch?v=... # tải và thêm video YouTube
Kết nối PostgreSQL trực tiếp
Không cần dump schema ra file — Graphify có thể kết nối thẳng vào database và map schema:
uv tool install "graphifyy[postgres]"
graphify extract --postgres "postgresql://user:pass@localhost/mydb"
Phục vụ graph qua MCP server cho cả team
MCP (Model Context Protocol) — hiểu nôm na là một "API" để AI assistant kết nối với công cụ ngoài. Bạn có thể chạy một MCP server để cả team dùng chung một graph:
uv tool install "graphifyy[mcp]"
# Chạy server MCP cục bộ:
python -m graphify.serve graphify-out/graph.json
# Hoặc phục vụ cho cả team qua HTTP:
python -m graphify.serve graphify-out/graph.json --transport http --host 0.0.0.0 --port 8080 --api-key "$SECRET"
Cài tất cả mọi thứ cùng lúc
uv tool install "graphifyy[all]"
Tải skill về máy
Bên dưới là toàn bộ skill Graphify chính thức (SKILL.md + thư mục references/). Đây chính là những gì lệnh graphify install sao chép vào AI assistant của bạn — bạn có thể tải nguyên bộ về để xem, commit vào project, hoặc cài thủ công.
Graphify Skill — bộ file gốc từ GitHub
Bộ skill chính thức từ repo safishamsi/graphify (branch v8) — gồm SKILL.md và 8 file trong references/. Đây là bản gốc không sửa đổi. Khi graphify install chạy, nó copy toàn bộ thư mục này vào đúng vị trí cho AI assistant của bạn. Bấm "Tải Skill" để tải về một file .zip chứa đầy đủ cấu trúc thư mục.
---
name: graphify
description: "Use for any question about a codebase, its architecture, file relationships, or project content — especially when graphify-out/ exists, where the question should be treated as a graphify query first. Turns any input (code, docs, papers, images, videos) into a persistent knowledge graph with god nodes, community detection, and query/path/explain tools."
---
# /graphify
Turn any folder of files into a navigable knowledge graph with community detection, an honest audit trail, and three outputs: interactive HTML, GraphRAG-ready JSON, and a plain-language GRAPH_REPORT.md.
## Usage
```
/graphify # full pipeline on current directory → Obsidian vault
/graphify <path> # full pipeline on specific path
/graphify https://github.com/<owner>/<repo> # clone repo then run full pipeline on it
/graphify https://github.com/<owner>/<repo> --branch <branch> # clone a specific branch
/graphify <url1> <url2> ... # clone multiple repos, build each, merge into one cross-repo graph
/graphify <path> --mode deep # thorough extraction, richer INFERRED edges
/graphify <path> --update # incremental - re-extract only new/changed files
/graphify <path> --directed # build directed graph (preserves edge direction: source→target)
/graphify <path> --whisper-model medium # use a larger Whisper model for better transcription accuracy
/graphify <path> --cluster-only # rerun clustering on existing graph
/graphify <path> --no-viz # skip visualization, just report + JSON
/graphify <path> --html # (HTML is generated by default - this flag is a no-op)
/graphify <path> --svg # also export graph.svg (embeds in Notion, GitHub)
/graphify <path> --graphml # export graph.graphml (Gephi, yEd)
/graphify <path> --neo4j # generate graphify-out/cypher.txt for Neo4j
/graphify <path> --neo4j-push bolt://localhost:7687 # push directly to Neo4j
/graphify <path> --falkordb # generate graphify-out/cypher.txt for FalkorDB
/graphify <path> --falkordb-push falkordb://localhost:6379 # push directly to FalkorDB
/graphify <path> --mcp # start MCP stdio server for agent access
/graphify <path> --watch # watch folder, auto-rebuild on code changes (no LLM needed)
/graphify <path> --wiki # build agent-crawlable wiki (index.md + one article per community)
/graphify <path> --obsidian --obsidian-dir ~/vaults/my-project # write vault to custom path (e.g. existing vault)
/graphify add <url> # fetch URL, save to ./raw, update graph
/graphify add <url> --author "Name" # tag who wrote it
/graphify add <url> --contributor "Name" # tag who added it to the corpus
/graphify query "<question>" # BFS traversal - broad context
/graphify query "<question>" --dfs # DFS - trace a specific path
/graphify query "<question>" --budget 1500 # cap answer at N tokens
/graphify path "AuthModule" "Database" # shortest path between two concepts
/graphify explain "SwinTransformer" # plain-language explanation of a node
```
## What graphify is for
Drop any folder of code, docs, papers, images, or video into graphify and get a queryable knowledge graph. Persistent across sessions, honest audit trail (EXTRACTED/INFERRED/AMBIGUOUS), community detection surfaces cross-document connections you wouldn't think to ask about.
## What You Must Do When Invoked
If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return.
**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query "<question>"` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it.
If no path was given, use `.` (current directory). Do not ask the user for a path.
If the path argument starts with `https://github.com/` or `http://github.com/`, treat it as a GitHub URL - run Step 0 before anything else, then continue with the resolved local path.
Follow these steps in order. Do not skip steps.
### Step 0 - GitHub repos and multi-path merge (only if a URL or several paths)
Only when the path is one or more `https://github.com/...` URLs, or several local subfolders to merge. See `references/github-and-merge.md` for the clone, cross-repo merge, and monorepo flow, then continue with the resolved local path. A plain local path skips this step.
### Step 1 - Ensure graphify is installed
```bash
# Detect the correct Python interpreter (handles uv tool, pipx, venv, system installs)
PYTHON=""
GRAPHIFY_BIN=$(which graphify 2>/dev/null)
# 1. uv tool installs — most reliable on modern Mac/Linux
if [ -z "$PYTHON" ] && command -v uv >/dev/null 2>&1; then
_UV_PY=$(uv tool run graphifyy python -c "import sys; print(sys.executable)" 2>/dev/null)
if [ -n "$_UV_PY" ]; then PYTHON="$_UV_PY"; fi
fi
# 2. Read shebang from graphify binary (pipx and direct pip installs)
if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then
_SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!')
case "$_SHEBANG" in
*[!a-zA-Z0-9/_.-]*) ;;
*) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;;
esac
fi
# 3. Fall back to python3
if [ -z "$PYTHON" ]; then PYTHON="python3"; fi
if ! "$PYTHON" -c "import graphify" 2>/dev/null; then
if command -v uv >/dev/null 2>&1; then
uv tool install --upgrade graphifyy -q 2>&1 | tail -3
_UV_PY=$(uv tool run graphifyy python -c "import sys; print(sys.executable)" 2>/dev/null)
if [ -n "$_UV_PY" ]; then PYTHON="$_UV_PY"; fi
else
"$PYTHON" -m pip install graphifyy -q 2>/dev/null \
|| "$PYTHON" -m pip install graphifyy -q --break-system-packages 2>&1 | tail -3
fi
fi
# Write interpreter path for all subsequent steps (persists across invocations)
mkdir -p graphify-out
"$PYTHON" -c "import sys; open('graphify-out/.graphify_python', 'w', encoding='utf-8').write(sys.executable)"
# Save scan root so `graphify update` (no args) knows where to look next time
echo "$(cd INPUT_PATH && pwd)" > graphify-out/.graphify_root
```
If the import succeeds, print nothing and move straight to Step 2.
**In every subsequent bash block, replace `python3` with `$(cat graphify-out/.graphify_python)` to use the correct interpreter.**
### Step 2 - Detect files
```bash
$(cat graphify-out/.graphify_python) -c "
import json
from graphify.detect import detect
from pathlib import Path
result = detect(Path('INPUT_PATH'))
print(json.dumps(result, ensure_ascii=False))
" > graphify-out/.graphify_detect.json
```
Replace INPUT_PATH with the actual path the user provided. Do NOT cat or print the JSON - read it silently and present a clean summary instead:
```
Corpus: X files · ~Y words
code: N files (.py .ts .go ...)
docs: N files (.md .txt ...)
papers: N files (.pdf ...)
images: N files
video: N files (.mp4 .mp3 ...)
```
Omit any category with 0 files from the summary.
Then act on it:
- If `total_files` is 0: stop with "No supported files found in [path]."
- If `skipped_sensitive` is non-empty: mention file count skipped, not the file names.
- If `total_words` > 2,000,000 OR `total_files` > 500: show the warning. Then compute the top 5 first-level subdirectories by file count:
- Read `scan_root` from the detect JSON (always an absolute path to the resolved INPUT_PATH).
- Concatenate all file lists across all types (`code`, `document`, `paper`, `image`, `video`).
- Filter out any path that starts with `scan_root + "/graphify-out/"` to exclude converted sidecars.
- For each file, strip the `scan_root` prefix and take the first path component. Files directly in `scan_root` with no subdirectory count as `(root)`.
- If all files are in `(root)` with no subdirectories, do not ask to narrow — no subfolders exist. Instead suggest `--no-cluster` to skip the expensive clustering step and proceed.
- Otherwise rank by count, show the top 5 with file counts, then ask which subfolder to run on. Wait for the user's answer before proceeding.
- Otherwise: proceed directly to Step 2.5 if video files were detected, or Step 3 if not.
### Step 2.5 - Video and audio (only if video files detected)
Skip this step entirely if `detect` returned zero `video` files. When the corpus has video or audio, see `references/transcribe.md` to transcribe them to text first, then treat the transcripts as doc files in Step 3.
### Step 3 - Extract entities and relationships
**Before starting:** note whether `--mode deep` was given. You must pass `DEEP_MODE=true` to every subagent in Step B2 if it was. Track this from the original invocation - do not lose it.
This step has two parts: **structural extraction** (deterministic, free) and **semantic extraction** (LLM, costs tokens).
**Before dispatching subagents:** check whether `GEMINI_API_KEY` or `GOOGLE_API_KEY` is set. If neither is set, print this one-liner to the user:
> Tip: set `GEMINI_API_KEY` or `GOOGLE_API_KEY` to use Gemini for semantic extraction (`pip install 'graphifyy[gemini]'`).
Print it once, then continue. If `GEMINI_API_KEY` or `GOOGLE_API_KEY` IS set, use `graphify.llm.extract_corpus_parallel(files, backend="gemini")` for semantic extraction instead of dispatching Claude subagents. The default Gemini model is `gemini-3-flash-preview`; set `GRAPHIFY_GEMINI_MODEL` or pass `--model` in headless CLI flows to override it.
> **No other API keys are read.** If `GEMINI_API_KEY`/`GOOGLE_API_KEY` are unset, fall straight through to Claude Code subagent dispatch (Part B below) — the host session itself is the LLM. graphify does **not** read `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or any other provider key from the environment. If a host agent prompts the user for `ANTHROPIC_API_KEY` to run extraction, that prompt is a misread of this skill — ignore it and dispatch subagents as written.
**Run Part A (AST) and Part B (semantic) in parallel. Dispatch all semantic subagents AND start AST extraction in the same message. Both can run simultaneously since they operate on different file types. Merge results in Part C as before.**
Note: Parallelizing AST + semantic saves 5-15s on large corpora. AST is deterministic and fast; start it while subagents are processing docs/papers.
#### Part A - Structural extraction for code files
For any code files detected, run AST extraction in parallel with Part B subagents:
```bash
$(cat graphify-out/.graphify_python) -c "
import sys, json
from graphify.extract import collect_files, extract
from pathlib import Path
import json
code_files = []
detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\"))
for f in detect.get('files', {}).get('code', []):
code_files.extend(collect_files(Path(f)) if Path(f).is_dir() else [Path(f)])
if code_files:
result = extract(code_files, cache_root=Path('.'))
Path('graphify-out/.graphify_ast.json').write_text(json.dumps(result, indent=2, ensure_ascii=False), encoding=\"utf-8\")
print(f'AST: {len(result[\"nodes\"])} nodes, {len(result[\"edges\"])} edges')
else:
Path('graphify-out/.graphify_ast.json').write_text(json.dumps({'nodes':[],'edges':[],'input_tokens':0,'output_tokens':0}, ensure_ascii=False), encoding=\"utf-8\")
print('No code files - skipping AST extraction')
"
```
#### Part B - Semantic extraction (parallel subagents)
**Fast path:** If detection found zero docs, papers, and images (code-only corpus), skip Part B entirely and go straight to Part C. AST handles code - there is nothing for semantic subagents to do.
**MANDATORY: You MUST use the Agent tool here. Reading files yourself one-by-one is forbidden - it is 5-10x slower. If you do not use the Agent tool you are doing this wrong.**
Before dispatching subagents, print a timing estimate:
- Load `total_words` and file counts from `graphify-out/.graphify_detect.json`
- Estimate agents needed: `ceil(uncached_non_code_files / 22)` (chunk size is 20-25)
- Estimate time: ~45s per agent batch (they run in parallel, so total ≈ 45s × ceil(agents/parallel_limit))
- Print: "Semantic extraction: ~N files → X agents, estimated ~Ys"
**Step B0 - Check extraction cache first**
Before dispatching any subagents, check which files already have cached extraction results:
```bash
$(cat graphify-out/.graphify_python) -c "
import json
from graphify.cache import check_semantic_cache
from pathlib import Path
detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\"))
all_files = [f for files in detect['files'].values() for f in files]
cached_nodes, cached_edges, cached_hyperedges, uncached = check_semantic_cache(all_files)
if cached_nodes or cached_edges or cached_hyperedges:
Path('graphify-out/.graphify_cached.json').write_text(json.dumps({'nodes': cached_nodes, 'edges': cached_edges, 'hyperedges': cached_hyperedges}, ensure_ascii=False), encoding=\"utf-8\")
Path('graphify-out/.graphify_uncached.txt').write_text('\n'.join(uncached), encoding=\"utf-8\")
print(f'Cache: {len(all_files)-len(uncached)} files hit, {len(uncached)} files need extraction')
"
```
Only dispatch subagents for files listed in `graphify-out/.graphify_uncached.txt`. If all files are cached, skip to Part C directly.
**Step B1 - Split into chunks**
Load files from `graphify-out/.graphify_uncached.txt`. Split into chunks of 20-25 files each. Each image gets its own chunk (vision needs separate context). When splitting, group files from the same directory together so related artifacts land in the same chunk and cross-file relationships are more likely to be extracted.
**Step B2 - Dispatch ALL subagents in a single message**
Call the Agent tool multiple times IN THE SAME RESPONSE - one call per chunk. This is the only way they run in parallel. If you make one Agent call, wait, then make another, you are doing it sequentially and defeating the purpose.
**IMPORTANT - subagent type:** Always use `subagent_type="general-purpose"`. Do NOT use `Explore` - it is read-only and cannot write chunk files to disk, which silently drops extraction results. General-purpose has Write and Bash access which the subagent needs.
Concrete example for 3 chunks:
```
[Agent tool call 1: files 1-15, subagent_type="general-purpose"]
[Agent tool call 2: files 16-30, subagent_type="general-purpose"]
[Agent tool call 3: files 31-45, subagent_type="general-purpose"]
```
All three in one message. Not three separate messages.
Each subagent receives this exact prompt (substitute FILE_LIST, CHUNK_NUM, TOTAL_CHUNKS, DEEP_MODE, and CHUNK_PATH).
CHUNK_PATH must be an **absolute** path — derive it before dispatching:
```bash
PROJECT_ROOT=$(cat graphify-out/.graphify_root)
# Then for chunk N: CHUNK_PATH="${PROJECT_ROOT}/graphify-out/.graphify_chunk_0N.json"
```
Subagent prompt template:
See `references/extraction-spec.md` for the exact subagent prompt (JSON schema, node-ID rules, confidence rubric, frontmatter, hyperedge, and vision rules). Load it only here, only when at least one chunk holds a doc, paper, or image; a pure-code corpus has skipped Part B and never reads it. Pass each subagent that prompt verbatim with FILE_LIST, CHUNK_NUM, TOTAL_CHUNKS, DEEP_MODE, and CHUNK_PATH substituted, and have it write the result to CHUNK_PATH.
**Step B3 - Collect, cache, and merge**
Wait for all subagents. For each result:
- Check that `graphify-out/.graphify_chunk_NN.json` exists on disk — this is the success signal
- If the file exists and contains valid JSON with `nodes` and `edges`, include it and save to cache
- If the file is missing, the subagent was likely dispatched as read-only (Explore type) — print a warning: "chunk N missing from disk — subagent may have been read-only. Re-run with general-purpose agent." Do not silently skip.
- If a subagent failed or returned invalid JSON, print a warning and skip that chunk - do not abort
If more than half the chunks failed or are missing, stop and tell the user to re-run and ensure `subagent_type="general-purpose"` is used.
Merge all chunk files into `.graphify_semantic_new.json`. **After each Agent call completes, read the real token counts from the Agent tool result's `usage` field and write them back into the chunk JSON before merging** — the chunk JSON itself always has placeholder zeros. Then run:
```bash
$(cat graphify-out/.graphify_python) -c "
import json, glob
from pathlib import Path
chunks = sorted(glob.glob('graphify-out/.graphify_chunk_*.json'))
all_nodes, all_edges, all_hyperedges = [], [], []
total_in, total_out = 0, 0
for c in chunks:
d = json.loads(Path(c).read_text(encoding=\"utf-8\"))
all_nodes += d.get('nodes', [])
all_edges += d.get('edges', [])
all_hyperedges += d.get('hyperedges', [])
total_in += d.get('input_tokens', 0)
total_out += d.get('output_tokens', 0)
Path('graphify-out/.graphify_semantic_new.json').write_text(json.dumps({
'nodes': all_nodes, 'edges': all_edges, 'hyperedges': all_hyperedges,
'input_tokens': total_in, 'output_tokens': total_out,
}, indent=2, ensure_ascii=False), encoding=\"utf-8\")
print(f'Merged {len(chunks)} chunks: {total_in:,} in / {total_out:,} out tokens')
"
```
Save new results to cache:
```bash
$(cat graphify-out/.graphify_python) -c "
import json
from graphify.cache import save_semantic_cache
from pathlib import Path
new = json.loads(Path('graphify-out/.graphify_semantic_new.json').read_text(encoding=\"utf-8\")) if Path('graphify-out/.graphify_semantic_new.json').exists() else {'nodes':[],'edges':[],'hyperedges':[]}
saved = save_semantic_cache(new.get('nodes', []), new.get('edges', []), new.get('hyperedges', []))
print(f'Cached {saved} files')
"
```
Merge cached + new results into `graphify-out/.graphify_semantic.json`:
```bash
$(cat graphify-out/.graphify_python) -c "
import json
from pathlib import Path
cached = json.loads(Path('graphify-out/.graphify_cached.json').read_text(encoding=\"utf-8\")) if Path('graphify-out/.graphify_cached.json').exists() else {'nodes':[],'edges':[],'hyperedges':[]}
new = json.loads(Path('graphify-out/.graphify_semantic_new.json').read_text(encoding=\"utf-8\")) if Path('graphify-out/.graphify_semantic_new.json').exists() else {'nodes':[],'edges':[],'hyperedges':[]}
all_nodes = cached['nodes'] + new.get('nodes', [])
all_edges = cached['edges'] + new.get('edges', [])
all_hyperedges = cached.get('hyperedges', []) + new.get('hyperedges', [])
seen = set()
deduped = []
for n in all_nodes:
if n['id'] not in seen:
seen.add(n['id'])
deduped.append(n)
merged = {
'nodes': deduped,
'edges': all_edges,
'hyperedges': all_hyperedges,
'input_tokens': new.get('input_tokens', 0),
'output_tokens': new.get('output_tokens', 0),
}
Path('graphify-out/.graphify_semantic.json').write_text(json.dumps(merged, indent=2, ensure_ascii=False), encoding=\"utf-8\")
print(f'Extraction complete - {len(deduped)} nodes, {len(all_edges)} edges ({len(cached[\"nodes\"])} from cache, {len(new.get(\"nodes\",[]))} new)')
"
```
Clean up temp files: `rm -f graphify-out/.graphify_cached.json graphify-out/.graphify_uncached.txt graphify-out/.graphify_semantic_new.json`
#### Part C - Merge AST + semantic into final extraction
```bash
$(cat graphify-out/.graphify_python) -c "
import sys, json
from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))
# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
for n in sem['nodes']:
if n['id'] not in seen:
merged_nodes.append(n)
seen.add(n['id'])
merged_edges = ast['edges'] + sem['edges']
merged_hyperedges = sem.get('hyperedges', [])
merged = {
'nodes': merged_nodes,
'edges': merged_edges,
'hyperedges': merged_hyperedges,
'input_tokens': sem.get('input_tokens', 0),
'output_tokens': sem.get('output_tokens', 0),
}
Path('graphify-out/.graphify_extract.json').write_text(json.dumps(merged, indent=2, ensure_ascii=False), encoding=\"utf-8\")
total = len(merged_nodes)
edges = len(merged_edges)
print(f'Merged: {total} nodes, {edges} edges ({len(ast[\"nodes\"])} AST + {len(sem[\"nodes\"])} semantic)')
"
```
### Step 4 - Build graph, cluster, analyze, generate outputs
**Before starting:** note whether `--directed` was given. If so, pass `directed=True` to `build_from_json()` in the code block below. This builds a `DiGraph` that preserves edge direction (source→target) instead of the default undirected `Graph`.
```bash
mkdir -p graphify-out
$(cat graphify-out/.graphify_python) -c "
import sys, json
from graphify.build import build_from_json
from graphify.cluster import cluster, score_all
from graphify.analyze import god_nodes, surprising_connections, suggest_questions
from graphify.report import generate
from graphify.export import to_json
from pathlib import Path
extraction = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\"))
detection = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\"))
G = build_from_json(extraction)
communities = cluster(G)
cohesion = score_all(G, communities)
tokens = {'input': extraction.get('input_tokens', 0), 'output': extraction.get('output_tokens', 0)}
gods = god_nodes(G)
surprises = surprising_connections(G, communities)
labels = {cid: 'Community ' + str(cid) for cid in communities}
# Placeholder questions - regenerated with real labels in Step 5
questions = suggest_questions(G, communities, labels)
report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, '.', suggested_questions=questions)
Path('graphify-out/GRAPH_REPORT.md').write_text(report, encoding=\"utf-8\")
to_json(G, communities, 'graphify-out/graph.json')
analysis = {
'communities': {str(k): v for k, v in communities.items()},
'cohesion': {str(k): v for k, v in cohesion.items()},
'gods': gods,
'surprises': surprises,
'questions': questions,
}
Path('graphify-out/.graphify_analysis.json').write_text(json.dumps(analysis, indent=2, ensure_ascii=False), encoding=\"utf-8\")
if G.number_of_nodes() == 0:
print('ERROR: Graph is empty - extraction produced no nodes.')
print('Possible causes: all files were skipped, binary-only corpus, or extraction failed.')
raise SystemExit(1)
print(f'Graph: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges, {len(communities)} communities')
"
```
If this step prints `ERROR: Graph is empty`, stop and tell the user what happened - do not proceed to labeling or visualization.
Replace INPUT_PATH with the actual path.
### Step 5 - Label communities
Read `graphify-out/.graphify_analysis.json`. For each community key, look at its node labels and write a 2-5 word plain-language name (e.g. "Attention Mechanism", "Training Pipeline", "Data Loading").
Then regenerate the report and save the labels for the visualizer:
```bash
$(cat graphify-out/.graphify_python) -c "
import sys, json
from graphify.build import build_from_json
from graphify.cluster import score_all
from graphify.analyze import god_nodes, surprising_connections, suggest_questions
from graphify.report import generate
from pathlib import Path
extraction = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\"))
detection = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\"))
analysis = json.loads(Path('graphify-out/.graphify_analysis.json').read_text(encoding=\"utf-8\"))
G = build_from_json(extraction)
communities = {int(k): v for k, v in analysis['communities'].items()}
cohesion = {int(k): v for k, v in analysis['cohesion'].items()}
tokens = {'input': extraction.get('input_tokens', 0), 'output': extraction.get('output_tokens', 0)}
# LABELS - replace these with the names you chose above
labels = LABELS_DICT
# Regenerate questions with real community labels (labels affect question phrasing)
questions = suggest_questions(G, communities, labels)
report = generate(G, communities, cohesion, labels, analysis['gods'], analysis['surprises'], detection, tokens, '.', suggested_questions=questions)
Path('graphify-out/GRAPH_REPORT.md').write_text(report, encoding=\"utf-8\")
Path('graphify-out/.graphify_labels.json').write_text(json.dumps({str(k): v for k, v in labels.items()}, ensure_ascii=False), encoding=\"utf-8\")
print('Report updated with community labels')
"
```
Replace `LABELS_DICT` with the actual dict you constructed (e.g. `{0: "Attention Mechanism", 1: "Training Pipeline"}`).
Replace INPUT_PATH with the actual path.
### Step 6 - Generate Obsidian vault (opt-in) + HTML
**Generate HTML always** (unless `--no-viz`). **Obsidian vault only if `--obsidian` was explicitly given** — skip it otherwise, it generates one file per node.
If `--obsidian` was given:
- If `--obsidian-dir <path>` was also given, pass it via `--dir`. Otherwise defaults to `graphify-out/obsidian`.
```bash
graphify export obsidian
# or with custom dir: graphify export obsidian --dir ~/vaults/my-project
```
Generate the HTML graph (always, unless `--no-viz`):
```bash
graphify export html # auto-aggregates to community view if graph > 5000 nodes
# or: graphify export html --no-viz
```
### Steps 6b-8 - Wiki, Neo4j, FalkorDB, SVG, GraphML, MCP, benchmark (only on their flags)
These run only when their flag is present (`--wiki`, `--neo4j`/`--neo4j-push`, `--falkordb`/`--falkordb-push`, `--svg`, `--graphml`, `--mcp`) or, for the token-reduction benchmark, when `total_words` exceeds 5,000. A default run with no export flags skips all of them. See `references/exports.md` for each one. Run any `--wiki` export before Step 9 cleanup so `.graphify_labels.json` is still available.
---
### Step 9 - Save manifest, update cost tracker, clean up, and report
```bash
$(cat graphify-out/.graphify_python) -c "
import json
from pathlib import Path
from datetime import datetime, timezone
from graphify.detect import save_manifest
# Save manifest for --update
detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\"))
# In --update mode, 'all_files' carries the full corpus; 'files' is the changed
# subset. Full-rebuild mode populates only 'files', so the fallback handles that.
save_manifest(detect.get('all_files') or detect['files'])
# Update cumulative cost tracker
extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\"))
input_tok = extract.get('input_tokens', 0)
output_tok = extract.get('output_tokens', 0)
cost_path = Path('graphify-out/cost.json')
if cost_path.exists():
cost = json.loads(cost_path.read_text(encoding=\"utf-8\"))
else:
cost = {'runs': [], 'total_input_tokens': 0, 'total_output_tokens': 0}
cost['runs'].append({
'date': datetime.now(timezone.utc).isoformat(),
'input_tokens': input_tok,
'output_tokens': output_tok,
'files': detect.get('total_files', 0),
})
cost['total_input_tokens'] += input_tok
cost['total_output_tokens'] += output_tok
cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\"utf-8\")
print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens')
print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)')
"
rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json
find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null
rm -f graphify-out/.needs_update 2>/dev/null || true
```
Tell the user (omit the obsidian line unless --obsidian was given):
```
Graph complete. Outputs in PATH_TO_DIR/graphify-out/
graph.html - interactive graph, open in browser
GRAPH_REPORT.md - audit report
graph.json - raw graph data
obsidian/ - Obsidian vault (only if --obsidian was given)
```
If graphify saved you time, consider supporting it: https://github.com/sponsors/safishamsi
Replace PATH_TO_DIR with the actual absolute path of the directory that was processed.
Then paste these sections from GRAPH_REPORT.md directly into the chat:
- God Nodes
- Surprising Connections
- Suggested Questions
Do NOT paste the full report - just those three sections. Keep it concise.
Then immediately offer to explore. Pick the single most interesting suggested question from the report - the one that crosses the most community boundaries or has the most surprising bridge node - and ask:
> "The most interesting question this graph can answer: **[question]**. Want me to trace it?"
If the user says yes, run `/graphify query "[question]"` on the graph and walk them through the answer using the graph structure - which nodes connect, which community boundaries get crossed, what the path reveals. Keep going as long as they want to explore. Each answer should end with a natural follow-up ("this connects to X - want to go deeper?") so the session feels like navigation, not a one-shot report.
The graph is the map. Your job after the pipeline is to be the guide.
---
## Interpreter guard for subcommands
Before running any subcommand below (`--update`, `--cluster-only`, `query`, `path`, `explain`, `add`), check that `.graphify_python` exists. If it's missing (e.g. user deleted `graphify-out/`), re-resolve the interpreter first:
```bash
if [ ! -f graphify-out/.graphify_python ]; then
GRAPHIFY_BIN=$(which graphify 2>/dev/null)
if [ -n "$GRAPHIFY_BIN" ]; then
PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!')
case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac
else
PYTHON="python3"
fi
mkdir -p graphify-out
"$PYTHON" -c "import sys; open('graphify-out/.graphify_python', 'w', encoding='utf-8').write(sys.executable)"
fi
```
## For --update and --cluster-only
Both are non-default subcommands. `--update` re-extracts only new or changed files; `--cluster-only` reruns clustering on the existing graph. See `references/update.md` for both flows.
---
## For /graphify query
When `graphify-out/graph.json` already exists and the user asks a question about the corpus, run the query directly:
```bash
graphify query "<question>"
```
Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. For that vocab-expansion step, the `--dfs` / `--budget` modes, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`.
---
## For /graphify add and --watch
Neither is part of the default build. When the user runs `/graphify add <url>` to fetch a URL into the corpus, or passes `--watch` to auto-rebuild on file changes, see `references/add-watch.md`.
---
## For the commit hook and native CLAUDE.md integration
When the user asks to install the post-commit auto-rebuild hook or wire graphify into a project's CLAUDE.md, see `references/hooks.md`.
---
## Honesty Rules
- Never invent an edge. If unsure, use AMBIGUOUS.
- Never skip the corpus check warning.
- Always show token cost in the report.
- Never hide cohesion scores behind symbols - show the raw number.
- Never run HTML viz on a graph with more than 5,000 nodes without warning the user.
graphify install)
- Tải file
graphify-skill.zipbằng nút "Tải Skill" ở phần Tải skill về máy phía trên. - Giải nén file zip — bạn sẽ có một thư mục
graphify/chứaSKILL.mdvà thư mục conreferences/(8 file). - Tạo thư mục
~/.claude/skills/graphify/(macOS/Linux) hoặc%APPDATA%\Claude\skills\graphify\(Windows). - Copy toàn bộ nội dung trong thư mục
graphify/vừa giải nén (gồmSKILL.mdvà cả thư mụcreferences/) vào đúng thư mục đã tạo ở bước trên — giữ nguyên cấu trúc thư mục. - Trong Claude Code, thêm vào file
~/.claude/CLAUDE.md:- **graphify** (~/.claude/skills/graphify/SKILL.md) — any input to knowledge graph. Trigger: /graphify - Khởi động lại Claude Code và thử
/graphify .
Xử lý lỗi thường gặp
Đây là mục "tra cứu khi cần" — bạn không cần đọc trước, chỉ quay lại đây nếu gặp đúng lỗi tương ứng.
"graphify: command not found" sau khi cài pip
pip cài scripts vào thư mục không nằm trong PATH của bạn. Sửa bằng cách thêm vào PATH:
- macOS: thêm
~/Library/Python/3.x/binvào~/.zshrc - Linux: thêm
~/.local/binvào~/.bashrc - Hoặc đơn giản hơn: chuyển sang dùng
uv tool install graphifyy
Graph có ít node hơn sau khi rebuild hoặc --update
Nếu bạn vừa refactor xóa bớt file, Graphify sẽ cảnh báo graph mới nhỏ hơn và không ghi đè để tránh mất dữ liệu. Dùng flag --force để bắt buộc ghi đè:
/graphify . --force # hoặc GRAPHIFY_FORCE=1 graphify extract .
Ollama hết VRAM / lỗi context window
Graphify tự tính toán kích thước KV-cache nhưng đôi khi quá lớn cho GPU của bạn. Giảm xuống:
GRAPHIFY_OLLAMA_NUM_CTX=8192 graphify extract ./docs --backend ollama --token-budget 4000
Skill version mismatch — cảnh báo trong IDE
Version graphify đã cài khác với file skill. Cập nhật cả hai:
uv tool upgrade graphifyy
graphify install # ghi đè file skill cũ
graph.json có conflict markers sau khi 2 người commit cùng lúc
Chạy lệnh này một lần để cài git merge driver — sau đó Graphify tự union-merge graph.json khi có xung đột:
graphify hook install