jpskill.com
🛠️ 開発・MCP コミュニティ

gitnexus

ブラウザ上で大規模なコードを分析し、コード知識グラフを使ってインタラクティブな探索UIを構築したり、サーバー不要のコード解析ツールを開発したりするSkill。

📜 元の英語説明(参考)

Build client-side code knowledge graphs with built-in Graph RAG for code exploration. Use when: analyzing large codebases in the browser, building zero-server code intelligence tools, creating interactive code exploration UIs.

🇯🇵 日本人クリエイター向け解説

一言でいうと

ブラウザ上で大規模なコードを分析し、コード知識グラフを使ってインタラクティブな探索UIを構築したり、サーバー不要のコード解析ツールを開発したりするSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o gitnexus.zip https://jpskill.com/download/14946.zip && unzip -o gitnexus.zip && rm gitnexus.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/14946.zip -OutFile "$d\gitnexus.zip"; Expand-Archive "$d\gitnexus.zip" -DestinationPath $d -Force; ri "$d\gitnexus.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して gitnexus.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → gitnexus フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

GitNexus

概要

クライアントサイドで動作するコード知識グラフを構築します。サーバーは不要です。tree-sitter WASM でコードを解析し、ファイル、関数、クラス、および依存関係のグラフを構築し、力指向レイアウトで視覚化し、自然言語によるコード探索のために Graph RAG でクエリを実行します。

手順

ユーザーがコード知識グラフ、ブラウザベースのコードエクスプローラー、またはコード用の Graph RAG の構築を要求した場合:

  1. tree-sitter WASM のセットアップ — 対象言語の言語文法をロードします
  2. コードベースの解析 — AST ノード(関数、クラス、インポート、エクスポート)を抽出します
  3. グラフの構築 — コードの関係を表すノードとエッジを作成します
  4. 視覚化 — 力指向グラフ(D3 または force-graph ライブラリ)でレンダリングします
  5. Graph RAG の有効化 — グラフノードを埋め込み、自然言語クエリを許可します

Tree-sitter WASM を使用したコード解析

import Parser from "web-tree-sitter";

interface CodeNode {
  id: string;
  type: "file" | "function" | "class" | "method" | "import" | "export";
  name: string;
  filePath: string;
  startLine: number;
  endLine: number;
  code: string;
}

interface CodeEdge {
  source: string;
  target: string;
  type: "contains" | "calls" | "imports" | "extends" | "implements";
}

async function initParser(language: string): Promise<Parser> {
  await Parser.init();
  const parser = new Parser();
  const lang = await Parser.Language.load(`/tree-sitter-${language}.wasm`);
  parser.setLanguage(lang);
  return parser;
}

function extractNodes(tree: Parser.Tree, filePath: string): CodeNode[] {
  const nodes: CodeNode[] = [];
  nodes.push({ id: `file:${filePath}`, type: "file", name: filePath.split("/").pop()!, filePath, startLine: 0, endLine: tree.rootNode.endPosition.row, code: "" });

  function walk(node: Parser.SyntaxNode) {
    const nameNode = node.childForFieldName("name");
    if ((node.type === "function_declaration" || node.type === "arrow_function") && nameNode) {
      nodes.push({ id: `fn:${filePath}:${nameNode.text}`, type: "function", name: nameNode.text, filePath, startLine: node.startPosition.row, endLine: node.endPosition.row, code: node.text.slice(0, 500) });
    }
    if (node.type === "class_declaration" && nameNode) {
      nodes.push({ id: `class:${filePath}:${nameNode.text}`, type: "class", name: nameNode.text, filePath, startLine: node.startPosition.row, endLine: node.endPosition.row, code: node.text.slice(0, 500) });
    }
    if (node.type === "import_statement") {
      const source = node.descendantsOfType("string")[0];
      if (source) nodes.push({ id: `import:${filePath}:${source.text}`, type: "import", name: source.text.replace(/['"]/g, ""), filePath, startLine: node.startPosition.row, endLine: node.endPosition.row, code: node.text });
    }
    for (const child of node.children) walk(child);
  }
  walk(tree.rootNode);
  return nodes;
}

グラフの構築

function buildGraph(fileNodes: Map<string, CodeNode[]>): { nodes: CodeNode[]; edges: CodeEdge[] } {
  const allNodes: CodeNode[] = [];
  const edges: CodeEdge[] = [];
  const functionIndex = new Map<string, string>();

  for (const [, nodes] of fileNodes) {
    allNodes.push(...nodes);
    for (const node of nodes) {
      if (node.type === "function" || node.type === "method") functionIndex.set(node.name, node.id);
    }
  }

  for (const node of allNodes) {
    const fileId = `file:${node.filePath}`;
    if (node.type !== "file") edges.push({ source: fileId, target: node.id, type: "contains" });
    if (node.type === "import") {
      const targetFile = resolveImport(node.name, node.filePath);
      if (targetFile) edges.push({ source: fileId, target: `file:${targetFile}`, type: "imports" });
    }
    if (node.type === "function" || node.type === "method") {
      for (const [fnName, fnId] of functionIndex) {
        if (fnId !== node.id && node.code.includes(fnName + "(")) edges.push({ source: node.id, target: fnId, type: "calls" });
      }
    }
  }
  return { nodes: allNodes, edges };
}

function resolveImport(importPath: string, fromFile: string): string | null {
  if (importPath.startsWith(".")) {
    return `${fromFile.split("/").slice(0, -1).join("/")}/${importPath.replace(/^\.\//, "")}.ts`;
  }
  return null;
}

Force-Graph による視覚化

import ForceGraph from "force-graph";

function renderGraph(container: HTMLElement, graph: { nodes: CodeNode[]; edges: CodeEdge[] }) {
  const colorMap: Record<string, string> = { file: "#4a9eff", function: "#50c878", class: "#ff6b6b", method: "#ffa500", import: "#888888", export: "#dda0dd" };
  ForceGraph()(container)
    .graphData({
      nodes: graph.nodes.map((n) => ({ id: n.id, name: n.name, type: n.type, val: n.type === "file" ? 8 : n.type === "class" ? 5 : 3 })),
      links: graph.edges.map((e) => ({ source: e.source, target: e.target, type: e.type })),
    })
    .nodeColor((node: any) => colorMap[node.type] || "#999")
    .nodeLabel((node: any) => `${node.type}: ${node.name}`)
    .linkDirectionalArrowLength(4);
}

Graph RAG クエリ


import { pipeline } from "@xenova/transformers";

async function embedNodes(graph: { nodes: CodeNode[] }): Promise<Map<string, number[]>> {
  const embeddings = new Map<string, number[]>();
  const embedder = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
  for (const node of graph.nodes) {
    const text = `${node.type} "${node.name}" in ${node.filePath}: ${node.code.slice(0, 200)}`;
    const result = await embedder(text, { pooling: "mean", normalize: true });
    embeddings.set(node.id, Array.from(result.data));
  }
  return embeddings;
}

function searchGraph(query: number[], embeddings: Map<string, number[]>, topK = 10): string[] {
  const scores: [string, number][] = [];
  for (const [id, emb] of embeddings) {
    let dot = 0, magA = 0, magB = 0;
    for (let i = 0; i < query.length; i++) { dot += query[i] * emb[i]; magA += query[i] ** 2; magB += emb[i]
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

GitNexus

Overview

Build client-side code knowledge graphs that run entirely in the browser — no server required. Parse code with tree-sitter WASM, construct a graph of files, functions, classes, and dependencies, visualize it with force-directed layouts, and query it with Graph RAG for natural-language code exploration.

Instructions

When a user asks to build a code knowledge graph, browser-based code explorer, or Graph RAG for code:

  1. Set up tree-sitter WASM — Load language grammars for the target languages
  2. Parse the codebase — Extract AST nodes (functions, classes, imports, exports)
  3. Build the graph — Create nodes and edges representing code relationships
  4. Visualize — Render with force-directed graph (D3 or force-graph library)
  5. Enable Graph RAG — Embed graph nodes, allow natural-language queries

Code Parsing with Tree-sitter WASM

import Parser from "web-tree-sitter";

interface CodeNode {
  id: string;
  type: "file" | "function" | "class" | "method" | "import" | "export";
  name: string;
  filePath: string;
  startLine: number;
  endLine: number;
  code: string;
}

interface CodeEdge {
  source: string;
  target: string;
  type: "contains" | "calls" | "imports" | "extends" | "implements";
}

async function initParser(language: string): Promise<Parser> {
  await Parser.init();
  const parser = new Parser();
  const lang = await Parser.Language.load(`/tree-sitter-${language}.wasm`);
  parser.setLanguage(lang);
  return parser;
}

function extractNodes(tree: Parser.Tree, filePath: string): CodeNode[] {
  const nodes: CodeNode[] = [];
  nodes.push({ id: `file:${filePath}`, type: "file", name: filePath.split("/").pop()!, filePath, startLine: 0, endLine: tree.rootNode.endPosition.row, code: "" });

  function walk(node: Parser.SyntaxNode) {
    const nameNode = node.childForFieldName("name");
    if ((node.type === "function_declaration" || node.type === "arrow_function") && nameNode) {
      nodes.push({ id: `fn:${filePath}:${nameNode.text}`, type: "function", name: nameNode.text, filePath, startLine: node.startPosition.row, endLine: node.endPosition.row, code: node.text.slice(0, 500) });
    }
    if (node.type === "class_declaration" && nameNode) {
      nodes.push({ id: `class:${filePath}:${nameNode.text}`, type: "class", name: nameNode.text, filePath, startLine: node.startPosition.row, endLine: node.endPosition.row, code: node.text.slice(0, 500) });
    }
    if (node.type === "import_statement") {
      const source = node.descendantsOfType("string")[0];
      if (source) nodes.push({ id: `import:${filePath}:${source.text}`, type: "import", name: source.text.replace(/['"]/g, ""), filePath, startLine: node.startPosition.row, endLine: node.endPosition.row, code: node.text });
    }
    for (const child of node.children) walk(child);
  }
  walk(tree.rootNode);
  return nodes;
}

Graph Construction

function buildGraph(fileNodes: Map<string, CodeNode[]>): { nodes: CodeNode[]; edges: CodeEdge[] } {
  const allNodes: CodeNode[] = [];
  const edges: CodeEdge[] = [];
  const functionIndex = new Map<string, string>();

  for (const [, nodes] of fileNodes) {
    allNodes.push(...nodes);
    for (const node of nodes) {
      if (node.type === "function" || node.type === "method") functionIndex.set(node.name, node.id);
    }
  }

  for (const node of allNodes) {
    const fileId = `file:${node.filePath}`;
    if (node.type !== "file") edges.push({ source: fileId, target: node.id, type: "contains" });
    if (node.type === "import") {
      const targetFile = resolveImport(node.name, node.filePath);
      if (targetFile) edges.push({ source: fileId, target: `file:${targetFile}`, type: "imports" });
    }
    if (node.type === "function" || node.type === "method") {
      for (const [fnName, fnId] of functionIndex) {
        if (fnId !== node.id && node.code.includes(fnName + "(")) edges.push({ source: node.id, target: fnId, type: "calls" });
      }
    }
  }
  return { nodes: allNodes, edges };
}

function resolveImport(importPath: string, fromFile: string): string | null {
  if (importPath.startsWith(".")) {
    return `${fromFile.split("/").slice(0, -1).join("/")}/${importPath.replace(/^\.\//, "")}.ts`;
  }
  return null;
}

Visualization with Force-Graph

import ForceGraph from "force-graph";

function renderGraph(container: HTMLElement, graph: { nodes: CodeNode[]; edges: CodeEdge[] }) {
  const colorMap: Record<string, string> = { file: "#4a9eff", function: "#50c878", class: "#ff6b6b", method: "#ffa500", import: "#888888", export: "#dda0dd" };
  ForceGraph()(container)
    .graphData({
      nodes: graph.nodes.map((n) => ({ id: n.id, name: n.name, type: n.type, val: n.type === "file" ? 8 : n.type === "class" ? 5 : 3 })),
      links: graph.edges.map((e) => ({ source: e.source, target: e.target, type: e.type })),
    })
    .nodeColor((node: any) => colorMap[node.type] || "#999")
    .nodeLabel((node: any) => `${node.type}: ${node.name}`)
    .linkDirectionalArrowLength(4);
}

Graph RAG Query

import { pipeline } from "@xenova/transformers";

async function embedNodes(graph: { nodes: CodeNode[] }): Promise<Map<string, number[]>> {
  const embeddings = new Map<string, number[]>();
  const embedder = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
  for (const node of graph.nodes) {
    const text = `${node.type} "${node.name}" in ${node.filePath}: ${node.code.slice(0, 200)}`;
    const result = await embedder(text, { pooling: "mean", normalize: true });
    embeddings.set(node.id, Array.from(result.data));
  }
  return embeddings;
}

function searchGraph(query: number[], embeddings: Map<string, number[]>, topK = 10): string[] {
  const scores: [string, number][] = [];
  for (const [id, emb] of embeddings) {
    let dot = 0, magA = 0, magB = 0;
    for (let i = 0; i < query.length; i++) { dot += query[i] * emb[i]; magA += query[i] ** 2; magB += emb[i] ** 2; }
    scores.push([id, dot / (Math.sqrt(magA) * Math.sqrt(magB))]);
  }
  return scores.sort((a, b) => b[1] - a[1]).slice(0, topK).map(([id]) => id);
}

Examples

Example 1: Build a Browser-Based Code Explorer for a React Project

npm create vite@latest code-nexus -- --template vanilla-ts
cd code-nexus
npm install web-tree-sitter force-graph @xenova/transformers
// main.ts — Parse a GitHub repo and render its knowledge graph
const parser = await initParser("typescript");
const files = await fetchRepoFiles("facebook/react", "packages/react/src");
const fileNodes = new Map<string, CodeNode[]>();
for (const file of files) {
  const tree = parser.parse(file.content);
  fileNodes.set(file.path, extractNodes(tree, file.path));
}
const graph = buildGraph(fileNodes);
renderGraph(document.getElementById("graph")!, graph);
// Result: interactive force-directed graph showing React's internal module structure

Example 2: Natural-Language Code Query with Graph RAG

// After building the graph, embed all nodes and query
const graph = buildGraph(fileNodes);
const embeddings = await embedNodes(graph);

// User asks a question about the codebase
const embedder = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
const qEmb = Array.from((await embedder("How does the authentication middleware work?", { pooling: "mean", normalize: true })).data);
const relevantIds = searchGraph(qEmb, embeddings, 5);
const context = relevantIds
  .map((id) => graph.nodes.find((n) => n.id === id))
  .filter(Boolean)
  .map((n) => `[${n!.type}] ${n!.name} (${n!.filePath})\n${n!.code.slice(0, 300)}`)
  .join("\n---\n");

// Pass context to LLM for a grounded answer about the codebase
const response = await fetch("/api/chat", {
  method: "POST",
  body: JSON.stringify({ messages: [
    { role: "system", content: `Answer using this code context:\n${context}` },
    { role: "user", content: "How does the authentication middleware work?" },
  ]}),
});

Guidelines

  1. Lazy-load grammars — Only load tree-sitter WASM grammars for languages present in the repo
  2. OPFS for large repos — Store cloned files in Origin Private File System for persistence
  3. Incremental parsing — Re-parse only changed files, not the entire repo
  4. Limit graph size — For repos with 1000+ files, allow filtering by directory or file type
  5. Web Workers — Run parsing and embedding in Web Workers to keep the UI responsive
  6. Cache embeddings — Store in IndexedDB so you don't re-embed on every page load