mcpField
G
graphify

graphify

github.com/sammcjデータ・データベース16739 回インストール2 日前に更新
リポジトリ

graphify について

graphifyは、コードなどのファイルからナビゲート可能なナレッジグラフを作るエージェントスキルです。関係を抽出・推論・曖昧として記録し、インタラクティブなHTML、GraphRAG向けJSON、平易なレポートを出力します。

主な機能

  1. 01ファイルからグラフを構築
  2. 02コミュニティ検出
  3. 03抽出・推論・曖昧の区別
  4. 04インタラクティブHTML出力
  5. 05GraphRAG向けJSONとレポート

活用シーン

  1. 01コードベース内の関係を把握
  2. 02文書間のつながりを発見
  3. 03ファイル変更後のグラフ更新

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/sammcj/agentic-coding --skill graphify

インストール後、Claude Code で/graphifyを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

.graphify_version
references/
add-watch.md
exports.md
extraction-spec.md
github-and-merge.md
hooks.md
query.md
transcribe.md
update.md

詳細

/graphify

任意のフォルダー内のファイルを、コミュニティ検出機能、正直な監査証跡、そしてインタラクティブ HTML、GraphRAG 対応 JSON、平易な言葉で書かれた GRAPH_REPORT.md の 3 つの出力を備えた、ナビゲーション可能なナレッジグラフに変換します。

使用方法

/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

graphify の用途

コード、ドキュメント、論文、画像、動画などが入った任意のフォルダーを graphify に渡すと、クエリ可能なナレッジグラフを作成できます。セッションをまたいで保持され、正直な監査証跡(EXTRACTED/INFERRED/AMBIGUOUS)を備えています。コミュニティ検出によって、自分では思いつかなかったようなドキュメント間のつながりが明らかになります。

呼び出されたときに必ず行うこと

ユーザーが /graphify --help または /graphify -h を(ほかの引数を付けずに)呼び出した場合は、上記の ## 使用方法 セクションの内容をそのまま出力して終了します。コマンドの実行、ファイルの検出、パスの . へのデフォルト設定は行わないでください。使用方法のブロックだけを出力して戻ります。

高速パス — 既存のグラフ: 何よりも先に、graphify-out/graph.json が存在するか確認してください。想定される場所は、現在の作業ディレクトリ(つまり、コマンドを実行しているプロジェクトのルート)からの相対パス graphify-out/graph.json です。ファイルが存在し、かつユーザーのリクエストがコードベースに関する自然言語の質問(例:「X はどのように動作しますか?」「Y を呼び出しているのはどれですか?」「Z を通るデータフローを追跡してください」)であり、明示的な再構築コマンド(--update、--cluster-only、または新規抽出を示すパスや URL のみの指定)ではない場合は、ステップ 1~5 をすべて省略し、## /graphify query の場合 に進んでください。 直ちに graphify query "<question>" を実行します。detect は実行しないでください。コーパスのサイズも確認しないでください。対象を絞るようユーザーに求めないでください。グラフはすでに構築されています。これを使ってください。

パスが指定されていない場合は、.(現在のディレクトリ)を使用します。ユーザーにパスを尋ねないでください。

パス引数が https://github.com/ または http://github.com/ で始まる場合は、GitHub URL として扱います。何よりも先にステップ 0 を実行し、その後、解決されたローカルパスを使って続行してください。

以下のステップを順番に実行してください。ステップを省略しないでください。

ステップ 0 - GitHub リポジトリと複数パスのマージ(URL または複数のパスがある場合のみ)

パスが 1 つ以上の https://github.com/... URL、またはマージする複数のローカルサブフォルダーの場合にのみ実行します。クローン、リポジトリ間のマージ、モノレポの手順については references/github-and-merge.md を参照し、その後、解決されたローカルパスを使って続行してください。通常のローカルパスの場合は、このステップを省略します。

ステップ 1 - graphify がインストールされていることを確認する
# 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

インポートに成功した場合は何も出力せず、ステップ 2 に進みます。

以降のすべての bash ブロックでは、適切なインタープリターを使用するため、python3 を $(cat graphify-out/.graphify_python) に置き換えてください。

ステップ 2 - ファイルを検出する
$(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

INPUT_PATH をユーザーが指定した実際のパスに置き換えます。JSON を cat したり出力したりしないでください。内容を表示せずに読み取り、代わりに整理した概要を提示します。

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 ...)

ファイルが 0 件のカテゴリは、概要から省略します。

その後、結果に応じて対応します。

  • total_files が 0 の場合:「[path] に対応ファイルが見つかりませんでした。」と表示して終了します。
  • skipped_sensitive が空でない場合は、スキップしたファイルの件数に触れます。ファイル名は挙げないでください。
  • total_words が 2,000,000 を超える、または total_files が 500 を超える場合は、警告を表示します。その後、ファイル数が多い上位 5 つの第 1 階層サブディレクトリを計算します。
    • detect JSON から scan_root を読み取ります(常に、解決済み INPUT_PATH の絶対パスです)。
    • すべての種類(code、document、paper、image、video)のファイル一覧を連結します。
    • 変換後のサイドカーを除外するため、scan_root + "/graphify-out/" で始まるパスを除外します。
    • 各ファイルについて、scan_root のプレフィックスを取り除き、最初のパス要素を取得します。scan_root 直下にあるファイルでサブディレクトリがない場合は、(root) として数えます。
    • すべてのファイルがサブディレクトリのない (root) にある場合は、対象を絞るよう求めません。サブフォルダーがないためです。代わりに、コストの高いクラスタリング手順を省略して処理を続けるため、--no-cluster を提案します。
    • 件数の多い順に上位 5 つをファイル数とともに表示し、どのサブフォルダーを対象に実行するか尋ねます。続行する前にユーザーの回答を待ちます。
  • それ以外の場合は、動画ファイルが検出されていればステップ 2.5 に進み、検出されていなければステップ 3 に直接進みます。
Step 2.5 - 動画と音声(動画ファイルが検出された場合のみ)

detect の結果、video ファイルがゼロ件の場合は、このステップ全体をスキップします。コーパスに動画または音声が含まれている場合は、まず references/transcribe.md を参照してテキストに文字起こししてから、Step 3 で文字起こしのテキストをドキュメントファイルとして扱います。

Step 3 - エンティティと関係性を抽出する

開始する前に: --mode deep が指定されていたか確認してください。指定されていた場合、Step B2 のすべてのサブエージェントに DEEP_MODE=true を渡す必要があります。元の呼び出し時点からこれを追跡し、忘れないでください。

このステップには、構造的抽出(決定論的、無料)と意味的抽出(LLM、トークンを消費)の 2 つのパートがあります。

graphify に API キーは必要ありません。ユーザーに求めたり、API キーがないために処理を止めたりしないでください。 コードは LLM も API キーも使わずに構造的に(AST)抽出されます。コードのみのコーパス(一般的な /graphify . のリポジトリに対する実行)は意味的抽出を完全にスキップするため、ここでは何も必要ありません。Part A に進み、Part B はスキップしてください。意味的抽出(ドキュメント、論文、画像のみ)では、GEMINI_API_KEY/GOOGLE_API_KEY がすでに設定されている場合に限り Gemini を使用し、設定されていない場合はホストエージェント自体が LLM になります。graphify は ANTHROPIC_API_KEY、OPENAI_API_KEY、その他のプロバイダーのキーを読み取りません。API キーがないために要求したり、待機したり、処理を停止したりしようとしている場合は、このスキルの誤解です。キーなしで続行してください。

意味的抽出の前に: GEMINI_API_KEY または GOOGLE_API_KEY が設定されているか確認してください。どちらも設定されていない場合は、次の一文をユーザーに表示してください。

ヒント:GEMINI_API_KEY または GOOGLE_API_KEY を設定すると、意味的抽出に Gemini を使用できます(pip install 'graphifyy[gemini]')。

この一文を一度表示したら、そのまま続行してください。ユーザーがキーを提供するのを待ってはいけません。GEMINI_API_KEY または GOOGLE_API_KEY が設定されている場合は、サブエージェントを起動する代わりに、意味的抽出には graphify.llm.extract_corpus_parallel(files, backend="gemini") を使用してください。Gemini のデフォルトモデルは gemini-3-flash-preview です。ヘッドレス CLI のフローでモデルを変更するには、GRAPHIFY_GEMINI_MODEL を設定するか、--model を渡してください。

その他の API キーは読み取られません。 GEMINI_API_KEY/GOOGLE_API_KEY が設定されていない場合、意味的抽出はホストエージェント自体が行います。実行中のセッションが LLM です。サブエージェントを起動できるホスト(例:Claude Code)では、Part B に記載のとおりサブエージェントを起動してください。ターミナルで CLI を直接実行し、サブエージェントを起動できないホストでは、処理を停滞させないでください。コードのみのコーパスには意味的処理がないため、空の意味ファイルを書き込み(Part B「ファストパス」)、Part C に進みます。ドキュメント、論文、画像を含むコーパスでは、Gemini キーを設定するか、それらを自分でインライン抽出してください。ただし、いかなる場合も ANTHROPIC_API_KEY を要求してはいけません。その要求は、このスキルの誤解です。

Part A(AST)と Part B(意味的抽出)を並行して実行してください。 すべての意味的抽出サブエージェントを起動すると同時に、同じメッセージ内で AST 抽出も開始します。異なる種類のファイルを処理するため、どちらも同時に実行できます。これまでどおり、Part C で結果を統合します。

注:AST と意味的抽出を並行化すると、大規模なコーパスでは 5~15 秒短縮できます。AST は決定論的で高速です。サブエージェントがドキュメントや論文を処理している間に開始してください。

Part A - コードファイルの構造的抽出

コードファイルが検出された場合は、Part B のサブエージェントと並行して AST 抽出を実行します。

$(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('INPUT_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 - 意味的抽出(並行サブエージェント)

ファストパス: 検出結果にドキュメント、論文、画像がゼロ件(コードのみのコーパス)の場合は、Part B 全体をスキップして Part C に進みます。コードは AST で処理されるため、意味的抽出サブエージェントの作業はありません。**まず空の意味ファイルを書き込んでください。**Part C の統合処理は入力ファイルとしてこれを必ず読み込みます(.graphify_semantic.json を無条件に読み取るため、これがないとコードのみの実行で FileNotFoundError が発生します)。

$(cat graphify-out/.graphify_python) -c "
import json
from pathlib import Path
Path('graphify-out/.graphify_semantic.json').write_text(json.dumps({'nodes':[],'edges':[],'hyperedges':[],'input_tokens':0,'output_tokens':0}), encoding='utf-8')
"

必須:ここでは必ず Agent ツールを使用してください。ファイルを 1 つずつ自分で読むことは禁止です。これは 5~10 倍遅くなります。Agent ツールを使用しない場合、誤った方法で作業しています。 サブエージェントをディスパッチする前に、所要時間の見積もりを出力します。

  • graphify-out/.graphify_detect.json から total_words とファイル数を読み込みます
  • 必要なエージェント数を見積もります:ceil(uncached_non_code_files / 22)(チャンクサイズは20〜25ファイルです)
  • 所要時間を見積もります:約45秒 × エージェントのバッチ数(並列実行されるため、合計は約45秒 × ceil(agents/parallel_limit) です)
  • 次の形式で出力します:「Semantic extraction: ~N files → X agents, estimated ~Ys」

Step B0 - まず抽出キャッシュを確認

サブエージェントをディスパッチする前に、抽出結果がすでにキャッシュされているファイルを確認します。

$(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\"))
# Only content files go to semantic extraction. Code is already covered structurally
# by the AST pass (Part A); flattening every category here makes subagents re-read
# every source file (#1392). Video is transcribed to a document in Step 2.5 first.
all_files = [f for cat in ('document', 'paper', 'image') for f in detect['files'].get(cat, [])]

cached_nodes, cached_edges, cached_hyperedges, uncached = check_semantic_cache(all_files, root='INPUT_PATH')

# Always (re)write the cache file: write hits, else DELETE any leftover from a prior
# run so Part C never merges a stale .graphify_cached.json (#1392).
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\")
else:
    Path('graphify-out/.graphify_cached.json').unlink(missing_ok=True)
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')
"

graphify-out/.graphify_uncached.txt に記載されたファイルのみ、サブエージェントをディスパッチします。すべてのファイルがキャッシュ済みの場合は、Part C に進みます。

Step B1 - チャンクに分割

graphify-out/.graphify_uncached.txt からファイルを読み込みます。20〜25ファイルずつチャンクに分割します。画像はそれぞれ個別のチャンクにします(画像の処理には別のコンテキストが必要です)。分割する際は、同じディレクトリのファイルをまとめ、関連するアーティファクトが同じチャンクに含まれるようにします。これにより、ファイル間の関係が抽出されやすくなります。

Step B2 - 1つのメッセージで全サブエージェントをディスパッチ

同じ応答内で Agent ツールを複数回呼び出します。チャンクごとに1回呼び出してください。並列実行するには、この方法しかありません。Agent の呼び出しを1回行って結果を待ち、それからもう1回呼び出すと、逐次実行になり、本来の目的が損なわれます。

重要 - サブエージェントの種類: 常に subagent_type="general-purpose" を使用します。Explore は使用しないでください。読み取り専用で、チャンクファイルをディスクに書き込めないため、抽出結果が通知なく失われます。サブエージェントが必要とする Write と Bash へのアクセス権があるのは general-purpose です。

3つのチャンクの場合の具体例:

[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"]

3つすべてを1つのメッセージで呼び出します。別々のメッセージで呼び出してはいけません。

各サブエージェントには、次の正確なプロンプトを渡します(FILE_LIST、CHUNK_NUM、TOTAL_CHUNKS、DEEP_MODE、CHUNK_PATH を置き換えてください)。

CHUNK_PATH は絶対パスでなければなりません。ディスパッチ前にパスを導き出してください。

PROJECT_ROOT=$(pwd)  # cwd — where Part C globs graphify-out/ (NOT .graphify_root/scan dir, #1392)
# Then for chunk N: CHUNK_PATH="${PROJECT_ROOT}/graphify-out/.graphify_chunk_0N.json"

サブエージェントのプロンプトテンプレート:

サブエージェントに渡す正確なプロンプト(JSON スキーマ、ノード ID のルール、信頼度の基準、フロントマター、ハイパーエッジ、画像のルールを含む)については、references/extraction-spec.md を参照してください。このファイルを読み込むのは、ドキュメント、論文、または画像を含むチャンクが少なくとも1つある場合に限ります。純粋なコードのコーパスでは Part B がスキップされ、このファイルも読み込まれません。各サブエージェントには、そのプロンプトを変更せずに渡し、FILE_LIST、CHUNK_NUM、TOTAL_CHUNKS、DEEP_MODE、CHUNK_PATH を置き換えたうえで、結果を CHUNK_PATH に書き込ませます。

Step B3 - 収集、キャッシュ、マージ

すべてのサブエージェントを待ちます。各結果について:

  • graphify-out/.graphify_chunk_NN.json がディスク上に存在することを確認します。これが成功の合図です。
  • ファイルが存在し、nodes と edges を含む有効な JSON であれば、それを採用してキャッシュに保存します。
  • ファイルが存在しない場合、サブエージェントが読み取り専用(Explore タイプ)としてディスパッチされた可能性があります。次の警告を出力します:「chunk N missing from disk — subagent may have been read-only. Re-run with general-purpose agent.」黙ってスキップしてはいけません。
  • サブエージェントが失敗した場合、または無効な JSON を返した場合は、警告を出力してそのチャンクをスキップします。中断してはいけません。

失敗または欠落したチャンクが半数を超える場合は、処理を停止し、subagent_type="general-purpose" が使用されていることを確認して再実行するようユーザーに伝えます。

すべてのチャンクファイルを .graphify_semantic_new.json にマージします。Agent の各呼び出しが完了した後、Agent ツールの結果にある usage フィールドから実際のトークン数を読み取り、マージ前にチャンク JSON に書き戻してください。 チャンク JSON 内のトークン数は常にプレースホルダーのゼロです。その後、次を実行します。

$(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')
"

新しい結果をキャッシュに保存します。

$(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', []), root='INPUT_PATH')
print(f'Cached {saved} files')
"

キャッシュ済みの結果と新しい結果を graphify-out/.graphify_semantic.json にマージします。

$(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)')
"

一時ファイルをクリーンアップします:rm -f graphify-out/.graphify_cached.json graphify-out/.graphify_uncached.txt graphify-out/.graphify_semantic_new.json

Part C - AST とセマンティック情報をマージして最終的な抽出結果を作成
$(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)')
"
ステップ 4 - グラフを構築し、クラスタリング、分析、出力を生成する

開始前に: 以下のコードブロックでは、build_from_json() に directed=IS_DIRECTED を渡しています。--directed が指定されていた場合は IS_DIRECTED を True に置き換えます(エッジの方向(ソース→ターゲット)を保持する DiGraph を構築します)。指定されていない場合は False(デフォルトの無向 Graph)にします。INPUT_PATH と同じ方法で置き換えてください。コードに IS_DIRECTED をそのまま残さないでください。

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\"))

# root= mirrors the --update runbook (#1361): relativize source_file to the same
# base so the full build and incremental --update never drift apart on re-extract.
G = build_from_json(extraction, root='INPUT_PATH', directed=IS_DIRECTED)
# Guard BEFORE any write: an empty extraction must not clobber a good graph.json /
# GRAPH_REPORT.md / analysis sidecar. Check immediately after build (#1392).
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)
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)

# Export FIRST and honor the #479 shrink-guard: to_json returns False (writing
# nothing) when the new graph is smaller than the existing graph.json. Only write
# GRAPH_REPORT.md + the analysis sidecar when the graph was actually written, so
# they never describe a graph that graph.json doesn't contain (#1392).
wrote = to_json(G, communities, 'graphify-out/graph.json')
if not wrote:
    print('ERROR: refused to shrink graphify-out/graph.json (existing graph has more nodes; #479).')
    print('If this shrink is intentional (you deleted files), re-run a full build with --force.')
    raise SystemExit(1)
report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, 'INPUT_PATH', suggested_questions=questions)
Path('graphify-out/GRAPH_REPORT.md').write_text(report, encoding=\"utf-8\")
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\")
print(f'Graph: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges, {len(communities)} communities')
"

このステップで ERROR: Graph is empty が出力された場合は、そこで停止して何が起きたかをユーザーに伝えてください。ラベリングや可視化には進まないでください。

INPUT_PATH を実際のパスに置き換えてください。

ステップ 4.5 - グラフの健全性チェック(読み取り専用の整合性ゲート)

ラベリング前に、抽出結果を非破壊で診断します。エッジの統合、接続先の欠落や不一致、自己ループなど、増分更新や AST/LLM の ID 不一致による気づきにくい破損を検出します。読み取り専用で、処理を中断することはありません。

$(cat graphify-out/.graphify_python) -c "
import json
from pathlib import Path
from graphify.diagnostics import diagnose_extraction, format_diagnostic_report

extraction = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\"))
summary = diagnose_extraction(extraction, directed=IS_DIRECTED, root='INPUT_PATH')
print(format_diagnostic_report(summary))
flags = [f'{summary[k]} {label}' for k, label in (
    ('dangling_endpoint_edges', 'dangling-endpoint edges'),
    ('missing_endpoint_edges', 'missing-endpoint edges'),
    ('self_loop_edges', 'self-loop edges'),
    ('directed_same_endpoint_collapsed_edges', 'collapsed (directed) edges'),
    ('undirected_same_endpoint_collapsed_edges', 'collapsed (undirected) edges'),
) if summary.get(k, 0)]
print('GRAPH HEALTH WARNING: ' + '; '.join(flags) + ' - graph may be incomplete/corrupt.' if flags else 'Graph health: OK (no dangling/missing/collapsed edges).')
"

ステップ 4 と同様に IS_DIRECTED と INPUT_PATH を置き換えてください。GRAPH HEALTH WARNING が出力された場合は、最終サマリーにその内容を記載してください(グラフは引き続き利用できますが、誠実性のルールに従い、整合性の問題を明示する必要があります。処理は中断しないでください)。

ステップ 5 - コミュニティにラベルを付ける

graphify-out/.graphify_analysis.json を読み込みます。各コミュニティキーについて、ノードのラベルを確認し、平易な言葉で 2~5 語の名前を付けます(例:「Attention Mechanism」、「Training Pipeline」、「Data Loading」)。

次に、レポートを再生成し、ビジュアライザー用のラベルを保存します。

$(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\"))

# root= as in Step 4 / the --update runbook (#1361) — same base for node-key parity.
G = build_from_json(extraction, root='INPUT_PATH', directed=IS_DIRECTED)
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, 'INPUT_PATH', 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')
"

LABELS_DICT を、作成した実際の辞書に置き換えてください(例:{0: "Attention Mechanism", 1: "Training Pipeline"})。 INPUT_PATH を実際のパスに置き換えてください。

ステップ 6 - Obsidian ボルトの生成(オプトイン)+ HTML

HTML は常に生成します(--no-viz が指定されている場合を除く)。Obsidian ボルトを生成するのは --obsidian が明示的に指定された場合だけです。それ以外の場合はスキップしてください。ノードごとに 1 ファイルが生成されます。

--obsidian が指定されている場合:

  • --obsidian-dir <path> も指定されている場合は、--dir 経由で渡してください。指定されていない場合のデフォルトは graphify-out/obsidian です。
graphify export obsidian
# or with custom dir: graphify export obsidian --dir ~/vaults/my-project

HTML グラフを生成します(--no-viz が指定されている場合を除き、常に生成します)。

graphify export html  # auto-aggregates to community view if graph > 5000 nodes
# or: graphify export html --no-viz
ステップ 6b~8 - Wiki、Neo4j、FalkorDB、SVG、GraphML、MCP、ベンチマーク(各フラグが指定された場合のみ)

これらは、該当するフラグ(--wiki、--neo4j/--neo4j-push、--falkordb/--falkordb-push、--svg、--graphml、--mcp)が指定された場合、またはトークン削減ベンチマークの場合は total_words が 5,000 を超えた場合にのみ実行されます。エクスポートフラグを指定しないデフォルトの実行では、すべてスキップされます。それぞれについては references/exports.md を参照してください。.graphify_labels.json がまだ利用できるよう、Step 9 のクリーンアップ前に --wiki エクスポートを実行してください。


ステップ 9 - マニフェストを保存し、コストトラッカーを更新して、クリーンアップし、結果を報告する
$(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.
# root= relativizes the manifest keys to the scan root (same base as the build),
# so the on-disk manifest is portable across clones/machines and a later --update
# matches cached files instead of missing every one (#1417).
save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH')

# 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

INPUT_PATH を実際のパス(ステップ 4〜5 で使用した値と同じ)に置き換え、マニフェストがスキャンルートを基準にした相対パスになるようにします。

ユーザーに次の内容を伝えます(--obsidian が指定されていない場合は obsidian の行を省略します)。

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)

graphify によって時間を節約できた場合は、支援をご検討ください:https://github.com/sponsors/safishamsi

PATH_TO_DIR を、処理したディレクトリの実際の絶対パスに置き換えます。

次に、GRAPH_REPORT.md の以下のセクションをチャットにそのまま貼り付けます。

  • 重要なノード
  • 意外なつながり
  • 提案された質問

レポート全体は貼り付けないでください。この 3 つのセクションだけにします。簡潔にまとめてください。

その直後に、探索を続けることを提案します。レポート内で最も興味深い質問を 1 つ選びます。複数のコミュニティ境界をまたぐもの、または最も意外なブリッジノードを含むものを選び、次のように尋ねます。

「このグラフで答えられる、最も興味深い質問です:[質問]。たどってみますか?」

ユーザーが「はい」と答えた場合は、グラフに対して /graphify query "[question]" を実行し、グラフ構造を使って回答を説明します。どのノードがつながっているか、どのコミュニティ境界をまたいでいるか、その経路から何が分かるかを説明します。ユーザーが探索を続けたい限り、続けます。各回答の最後には、「これは X につながっています。さらに深掘りしますか?」のように自然なフォローアップを入れ、セッションが一度きりのレポートではなく、探索として感じられるようにします。

グラフは地図です。パイプライン実行後のあなたの役割は、案内役です。


サブコマンドのインタープリター確認

以下のサブコマンド(--update、--cluster-only、query、path、explain、add)を実行する前に、.graphify_python が存在するか確認します。存在しない場合(たとえば、ユーザーが graphify-out/ を削除した場合)は、インタープリターを再度解決します。

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

--update と --cluster-only について

どちらもデフォルト以外のサブコマンドです。--update は新規または変更されたファイルのみを再抽出し、--cluster-only は既存のグラフでクラスタリングを再実行します。両方のフローについては、references/update.md を参照してください。


/graphify query について

graphify-out/graph.json がすでに存在し、ユーザーがコーパスに関する質問をした場合は、グラフを再構築せず、グラフに基づいて回答します。

graphify query "<question>"

たどり始める前に、質問の表現が一致しないために回答がノイズに埋もれないよう、グラフ固有の語彙に照らして質問を展開します。graphify query CLI が利用できない場合は、graphify-out/graph.json をインラインの NetworkX 探索に使用します。グラフの出力に含まれる情報だけを使って回答し、特定の事実を引用する際は source_location を引用します。語彙展開、BFS/DFS 探索モード、--budget 上限、NetworkX フォールバック、save-result フィードバック、/graphify path および /graphify explain のフローについては、references/query.md を参照してください。


/graphify add と --watch について

どちらもデフォルトのビルドには含まれません。ユーザーが /graphify add <url> を実行して URL をコーパスに取得する場合、またはファイル変更時に自動で再ビルドするために --watch を指定する場合は、references/add-watch.md を参照してください。


コミットフックとネイティブな CLAUDE.md 連携について

ユーザーがコミット後の自動再ビルドフックのインストール、またはプロジェクトの CLAUDE.md への graphify の組み込みを求めた場合は、references/hooks.md を参照してください。


誠実さに関するルール

  • エッジを決して捏造しないでください。確信が持てない場合は、AMBIGUOUS を使用します。
  • コーパス確認の警告を決して省略しないでください。
  • レポートには必ずトークンコストを表示してください。
  • 凝集度スコアを記号で隠さず、生の数値を表示してください。
  • ユーザーに警告せず、5,000 ノードを超えるグラフの HTML 可視化を決して実行しないでください。

コメント