Build and query knowledge graphs from any folder (code + docs + PDFs + images). MCP-backed. Activates on /graphify and on natural-language requests to graph a project.
Installs into .claude/skills of the current project.
Are you the author of Graphify?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/nikolanovoselec-graphify)
---
name: graphify
description: Build and query knowledge graphs from any folder (code + docs + PDFs + images). MCP-backed. Activates on /graphify and on natural-language requests to graph a project.
trigger: /graphify
---
# /graphify (Codeflare-managed skill)
This skill drives `/graphify` knowledge-graph extraction inside the Codeflare container. The `graphifyy` Python tool is pre-installed at build time (`uv tool install graphifyy[mcp,sql,pdf]`); the `graphify` CLI is on PATH at `/root/.local/bin/graphify` and the MCP server is pre-registered in `~/.claude.json` as `/root/.local/share/uv/tools/graphifyy/bin/python -m graphify.serve`. You do not need to install or wire anything.
## Graphify 0.9.73/0.9.74 compatibility
The image and managed update wrappers set `GRAPHIFY_NO_AUTO_REFRESH=1`: signed curation, not an upstream CLI refresh, owns these skill files. Do not unset the guard to resolve a version warning.
Verify the installed CLI version independently of plugin metadata. The 0.9.73 enum `case_of` edges, inherited Java-call resolution and improved Astro parsing are additive extraction changes; preserve the official graph schema and existing consent requirements. A warning about the 20,000-character semantic file cap means incomplete coverage: read the remaining source in bounded chunks rather than treating the warning as successful full-file analysis. If `graphify hook status` reports older hooks, do not overwrite Codeflare-managed hooks with an upstream installer without an explicitly requested source change.
Graphify 0.9.74 prefers authored workspace sources, filters Rust builtins and reclassifies Rust constructor edges as `references` with `context: "constructor"`. Callflow, hubs and counts can change on an explicitly authorized refresh; this is not permission to rebuild, normalize graphs or replace managed skills.
## Graphify 0.9.92 compatibility
The [reviewed release](https://github.com/Graphify-Labs/graphify/blob/86c57be8204673c2a1b9849a3292a7dd3ab373d8/CHANGELOG.md) changes stable IDs and grammar-versioned AST caches; an authorized update may re-extract code and change counts or callflow. Preserve optional `schema_version`/`graphify_version` metadata and parallel relations; do not normalize existing graphs. Qualify ambiguous path endpoints rather than guessing. `.graphify_labels.pending.json` is a regenerable worklist, not the curated labels file. Upstream hook installation now adds post-merge/post-rewrite rebuilds: do not install these hooks or refresh graphs without explicit authorization. Retain `GRAPHIFY_NO_AUTO_REFRESH=1`, local optional labels and the external-provider prohibition; no extra Pi guard or backend setting is required.
## Quick Reference
**Python interpreter:** All `python3` code blocks in this skill must use graphify's own interpreter: `/root/.local/share/uv/tools/graphifyy/bin/python`. System `python3` cannot import graphify. The CLI at `/root/.local/bin/graphify` is on PATH.
### Recipe 1: AST-only update (free, no tokens)
```
bash /home/user/.claude/plugins/graphify/scripts/safe-graphify-update.sh .
```
Re-extracts code structure only. Use after source code changes. Memory-safe (OOM-guarded).
### Recipe 2: Full semantic update (existing repo)
1. Detect files (Step 1 below)
2. Check semantic cache (Step B0) - note cached vs uncached counts
3. Present AskUserQuestion: AST-only vs Full (Note 8). Include subagent count: `ceil(uncached_doc_paper_files / 22) + uncached_image_count` (images get own chunk)
4. If Full chosen: start AST (Part A) in background, dispatch first semantic wave in parallel
5. Split uncached non-code files into chunks of 22 (images get own chunk)
6. Dispatch waves of at most 10 Sonnet subagents (Note 9: `model: "sonnet"`). All agents in one wave go in a SINGLE message. Wait for wave completion before next wave.
7. Collect results, save to cache, merge cached + new (Step B3)
8. Merge AST + semantic (Part C)
9. Build, cluster, and optionally label communities (Steps 3-4)
10. Generate HTML + Obsidian vault (Step 5)
11. Benchmark + manifest + cleanup (Steps 6-7)
12. Commit `graphify-out/graph.json`, `GRAPH_REPORT.md`, `graph.html` + merge into global graph (Note 3)
**If interrupted mid-wave:** re-run from step 2. Cache (Step B0) skips already-extracted files. Only uncached files get re-dispatched.
### Recipe 3: First-time build on new repo
Follow the full canonical protocol below (Steps 1-7). Note 8 fires to ask build mode. After build, set up `.gitignore` and `.gitattributes` per Note 3.
### Recipe 4: Re-cluster existing graph
```
graphify cluster-only .
```
Reruns community detection on existing `graph.json`. No extraction, no tokens.
### Recipe 5: Optionally name/relabel communities in-session (NO backend)
Community labels are optional and never block graph publication. **NEVER run `graphify label` and NEVER pass `--backend`.** That command calls an external LLM provider and re-clusters, which renumbers communities and wipes existing labels. When the user requests labels, community naming is done by THIS session reading the member nodes:
1. **Prepare** a worklist from the graph's existing community assignments (no recluster, no LLM):
```
bash /home/user/.claude/plugins/graphify/scripts/local-graphify-labels.sh prepare .
```
Writes `graphify-out/.graphify_community_label_worklist.json` and `graphify-out/.graphify_community_label_batches/batch_*.md` (40 communities/batch), each community listed with its top member node labels + source files.
2. **Name** every community in `graphify-out/.graphify_labels.json` as `{"<id>":"<Name>"}`. Infer each name from that community's top nodes/sources. For a large graph, fan out one subagent per `batch_*.md` (Agent tool, `run_in_background: true`) and merge their `{id:name}` maps. Rules: unique, specific, 2-6 words, Title Case; NO placeholders (`Community 12`), NO numeric suffixes (`Auth 2`) - qualify by source/domain instead (`Vault Crypto`, `Vault Proxy Routing`). Every current community id must be present.
3. **Apply** (validates uniqueness, regenerates `GRAPH_REPORT.md` + `graph.html` with `community_labels` + `callflow.html` from existing communities - no recluster, no backend):
```
bash /home/user/.claude/plugins/graphify/scripts/local-graphify-labels.sh apply .
```
The labeled `graph.html` is where names appear in the viz. If apply reports `duplicate_exact` / `numbered_duplicate` / `duplicate_base` / `placeholder`, fix those ids in `.graphify_labels.json` and re-run apply.
4. **Merge into the global graph** (so cross-repo MCP queries see this repo's nodes/edges), then commit:
```
flock -w 5 /run/codeflare/locks/graphify-global.lock graphify global add graphify-out/graph.json --as "$(basename "$PWD")"
```
`global add` is hash-keyed on node/edge content, so it no-ops when only labels changed - that is expected, not a failure. Community **names** live in `graphify-out/.graphify_labels.json` and the regenerated `graph.html`, NOT in the global graph: `graph_stats` reporting `Communities: 0` for the global graph is its normal state and is not "fixed" by labeling. Commit `graph.json`, `GRAPH_REPORT.md`, `graph.html`, and `callflow.html`; include `.graphify_labels.json` only when optional labeling was requested.
When labeling is skipped, do not run prepare/apply. Keep Graphify's official report/html, remove `.graphify_labels.json` from the committed update, and generate `callflow.html` directly with `graphify export callflow-html --graph graphify-out/graph.json --output graphify-out/callflow.html`.
## Codeflare-specific operational notes
1. **MCP query tools are always available.** Even before any graph is built, you can call `mcp__graphify__query_graph`, `mcp__graphify__get_node`, `mcp__graphify__get_neighbors`, and `mcp__graphify__shortest_path`. They return useful errors when no graph is present. After a build, point them at `graphify-out/graph.json` in the current cwd.
2. **Use only local or in-session Graphify workflows; never run `graphify label`.** Do not pass `--backend` to any command. Provider-backed labeling also re-clusters, which renumbers communities and discards existing labels. Semantic extraction uses in-session Claude subagents. Optional community naming uses the in-session `local-graphify-labels.sh` flow (Recipe 5); unlabeled graphs keep official Graphify report/html output.
3. **Persistence lives in git, not R2.** The graph travels with the repo. After your first `/graphify` build in a repo the user has push permission to:
- Add to the repo's `.gitignore` (create if absent):
```
graphify-out/cache/
graphify-out/.cache/
graphify-out/.chunks/
graphify-out/manifest.json
graphify-out/obsidian/
.graphify_ast.json
.graphify_semantic.json
.graphify_semantic_new.json
.graphify_extract.json
.graphify_detect.json
.graphify_analysis.json
.graphify_cached.json
.graphify_uncached.txt
.graphify_chunk_*.txt
.graphify_old.json
.graphify_labels.pending.json
.graphify_community_label_worklist.json
.graphify_community_label_batches/
.graphify_root
/.graphify_labels.json
```
All patterns are regenerable; only `graph.json`, `GRAPH_REPORT.md`, `graph.html`, and `callflow.html` are always committed (plus optional `wiki/`). `.graphify_labels.json` is committed only when optional community labeling was requested. The leading `/` on `/.graphify_labels.json` ignores only a stray root-level marker. The `local-graphify-labels.sh prepare` worklist + batches are working intermediates - never commit them.
- Add to the repo's `.gitattributes` (create if absent):
```
graphify-out/graph.json merge=graphify
```
This wires the graphify semantic merge driver for `graph.json`. The driver itself is registered globally in the container image, so this `.gitattributes` line is the only per-repo setup needed. Without it, concurrent edits produce corrupt JSON on merge.
- Stage and commit `graphify-out/graph.json`, `GRAPH_REPORT.md`, `graph.html`, `callflow.html`, optional `.graphify_labels.json` when labeling was requested, and optional `wiki/`.
- For repos the user does NOT have push permission to (cloned open-source projects, read-only forks): graphify-out/ stays in the working tree only, ephemeral, no R2 fallback. Do not try to persist via bisync.
- **Before the commit step, merge this repo's graph into the unified global graph** so `mcp__graphify__*` tool calls see it alongside the vault and any other active repos: `flock -w 5 /run/codeflare/locks/graphify-global.lock graphify global add graphify-out/graph.json --as <repo-basename>`. Hash-keyed and idempotent. The `flock -w 5` serialises against the capture agent and the vault-extract agent; the 5s timeout prevents a wedged writer from blocking the queue.
4. **Bias toward `--update` and `cluster-only` for repeat runs.** Full LLM extraction is expensive. After the first build:
- For source changes: `bash /home/user/.claude/plugins/graphify/scripts/safe-graphify-update.sh .` (AST-only, free, no token cost; wraps `graphify update` with `GRAPHIFY_MAX_WORKERS=1` + `ulimit -v 1500000` so a runaway rebuild on a large repo cannot OOM-kill the codeflare session).
- For repos larger than 2000 files: `graphify cluster-only . --no-viz` (AST-only first build).
5. **Context boundedness.** Graphify's own subagent-chunking model bounds the main session context, so extraction works without context-mode or `ctx_*` tools. No per-tier branching is needed in this skill.
6. **AskUserQuestion on clone - never auto-update.** A PostToolUse hook (`graphify-clone-prompt.sh`) injects a directive after `git clone` / `gh repo clone`. At clone time, if no graph exists, ask the user which graph action they want before running any build: **AST-only**, **Full semantic**, or **No graph action**. If an existing graph is stale/unknown, ask before running any update and offer **Use existing graph as-is**, **AST-only update**, or **Full semantic refresh**. If an existing graph is fresh, only print an informational note and use it. A clone-time AST-only choice is a final build-mode choice for this skill after detection. A clone-time Full semantic choice is intent only: after detection, show the actual uncached file/subagent counts and ask for confirmation before dispatching semantic subagents. Respect a NO without arguing.
7. **Discipline rule.** When `graphify-out/graph.json` exists, the "Graph first" section of `~/.claude/rules/engineering-constitution.md` applies: prefer focused MCP queries over Grep for architecture, dependency, and call-flow questions.
8. **Mandatory build-mode choice before any extraction.** Before dispatching Part B subagents (Step B2 of the upstream protocol), ALWAYS present the user with an `AskUserQuestion` offering exactly two modes unless the user already chose AST-only in the current clone-time triage prompt. If the clone-time triage captured Full semantic intent, do not dispatch semantic subagents yet; after detection, present a confirmation with the same Full-mode cost/count details below:
- **AST-only** - free, no token cost; code structure + call/import/contains edges only; no semantic concepts from docs / papers / images.
- **Full (AST + semantic)** - AST plus N parallel Sonnet subagents extracting concepts from docs / papers / images. Include the actual subagent count (`ceil(uncached_doc_paper_files / 22) + uncached_image_count`) and a wall-time estimate (~45s per parallel batch).
Choose by intent, not size. **AST-only** when testing the pipeline, exploring for a one-off question, or cost-capping. **Full** when this is a long-term project and the user wants semantic concepts from docs/images in MCP queries.
Skip the mode question only when (a) the corpus has zero docs / papers / images (code-only fast path makes the choice moot), (b) `--no-semantic` was passed explicitly, or (c) the user already chose AST-only in the current clone-time triage prompt. If AST-only is chosen, skip Part B entirely and treat AST as the full extraction (same flow as the code-only fast path). If Full semantic was chosen at clone time, ask only the post-detection cost/count confirmation; a Yes proceeds to Part B, and a No falls back to AST-only/no graph per the user's answer.
This choice is separate from the "split by subfolder" question the upstream protocol asks on > 200 files - ask both in sequence (subfolder first, then build mode against the chosen scope).
9. **Spawn Part B semantic subagents with `model: "sonnet"`.** Graphify semantic extraction requires reliable schema compliance - each subagent must emit valid JSON with correct `id`, `source_file`, and `confidence_score` fields. Haiku produced 57% malformed nodes on the codeflare corpus (288/504 dropped during post-filter); Sonnet's structured-output fidelity eliminates this waste. The `Task` calls in Step B2 must include `model: "sonnet"`. Never escalate to Opus from this skill.
10. **The on-disk edge key is `links`, not `edges`.** Every graph graphify writes - `graphify-out/graph.json`, `~/Vault/graphify-out/vault-graph.json`, and `~/.graphify/global-graph.json` - is NetworkX node-link JSON in this shape:
```json
{"directed": true, "multigraph": false, "graph": {},
"nodes": [{"id": "...", "type": "...", "repo": "<tag>", "...": "..."}],
"links": [{"source": "<node id>", "target": "<node id>", "type": "..."}]}
```
A script that reads `data["edges"]` gets nothing and reports an edgeless graph, which is a lookup bug and never a real finding. NetworkX 3.6 flipped the default key from `links` to `edges`, so anything calling `node_link_data` / `node_link_graph` directly must pass `edges="links"` to stay compatible with what is already on disk; graphify pins that explicitly. Nodes in the global graph carry a `repo` attribute holding the tag they were added under (`user_vault`, `codeflare`, ...), and that attribute is what `graphify global remove <tag>` prunes on. Prefer the `mcp__graphify__*` tools over parsing these files by hand; they answer against the global graph and cannot get the key wrong.
---
The upstream graphify extraction pipeline is reproduced below in full. It is the canonical algorithm; do not improvise on it. The two operational notes above (#8 mandatory build-mode question, #9 Sonnet subagents) are codeflare-specific overrides that bind on top of the upstream Step 1 + Step B2 below - apply them even where the upstream text does not mention them.
---
graphify turns 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.
## What You Must Do When Invoked
If no path was given, use `.` (current directory). Do not ask the user for a path.
Follow these steps in order. Do not skip steps.
### Step 1 - Detect files
```bash
/root/.local/share/uv/tools/graphifyy/bin/python -c "
import json
from graphify.detect import detect
from pathlib import Path
result = detect(Path('INPUT_PATH'))
print(json.dumps(result))
" > .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 ...)
document: N files (.md .txt ...)
paper: N files (.pdf ...)
image: N files
```
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` > 200: show the warning and the top 5 subdirectories by file count, then ask which subfolder to run on. Wait for the user's answer before proceeding.
- Otherwise: proceed directly to Step 2 - no need to ask anything.
### Step 2 - Extract entities and relationships
This step runs structural (AST, free) and semantic (Claude subagents, costs tokens) extraction in parallel, then merges them. The two codeflare overrides bind here: the mandatory build-mode question (operational note 8) before Part B, and `model: "sonnet"` subagents (operational note 9). For the full Part A / Part B / Part C pipeline, the wave structure, the exact subagent prompt, and the extraction JSON schema, see `references/extraction-spec.md`.
### Steps 3-7 - Build, cluster, label, visualize, report
After extraction produces `.graphify_extract.json`, build the graph, cluster it, optionally label communities, generate the Obsidian vault + HTML + callflow, benchmark (only if `total_words > 5000`), save the manifest, clean up, and report. See `references/build.md` for all five steps.
## Incremental Updates and --cluster-only
For repeat runs, bias toward `--update` (re-extracts only new/changed files) and `cluster-only` (reruns clustering on the existing graph) over a full LLM rebuild (operational note 4). The cheap AST-only refresh is `bash /home/user/.claude/plugins/graphify/scripts/safe-graphify-update.sh .` (wraps `graphify update`). For the full `--update` procedure (code-only fast path, merge, graph diff) and the `--cluster-only` flow, see `references/update.md`.
## Query, Path, Explain
For graph queries, prefer the MCP tools directly (`mcp__graphify__query_graph`, `mcp__graphify__get_node`, `mcp__graphify__get_neighbors`, `mcp__graphify__shortest_path`, `mcp__graphify__get_community`, `mcp__graphify__god_nodes`) - always available when a graph exists. After answering a query, path, or explain, persist the Q&A back into the graph with `graphify save-result` so the next update extracts it as a node. For the save-result feedback loop (`--type query` / `path_query` / `explain`), the CLI/NetworkX fallback, and the `/graphify add`, `--watch`, and export flows, see `references/query.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 skip HTML viz. Codeflare sets `GRAPHIFY_VIZ_NODE_LIMIT=100000` globally in entrypoint.sh; if a build ever logs `Skipped graph.html`, re-export the limit and rebuild before reporting done.