From c56d8558b285a9d33bf74885f9764615786cbc8d Mon Sep 17 00:00:00 2001 From: Vignesh Date: Thu, 23 Apr 2026 22:04:03 +0530 Subject: [PATCH 1/3] feat: add GitHub Copilot platform support - Add 'copilot' entry to PLATFORMS dict in skills.py - Config: .vscode/mcp.json with 'servers' key (VS Code MCP format) - Detection: checks ~/.vscode exists - needs_type: True (VS Code requires type: stdio) - Add .github/copilot-instructions.md to _PLATFORM_INSTRUCTION_FILES - Add 'copilot' to _PLATFORM_CHOICES in cli.py - Add TestCopilotPlatform test class with 8 tests covering platform entry, config install, idempotency, dry-run, and instruction file injection - Update inject_platform_instructions tests to include new file - Update README.md platform lists and install example Usage: code-review-graph install --platform copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 38 +++++++++++++ README.md | 5 +- code_review_graph/cli.py | 2 +- code_review_graph/skills.py | 9 +++ tests/test_skills.py | 99 ++++++++++++++++++++++++++++++++- uv.lock | 1 + 6 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..d3dc9b48 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,38 @@ + +## MCP Tools: code-review-graph + +**IMPORTANT: This project has a knowledge graph. ALWAYS use the +code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore +the codebase.** The graph is faster, cheaper (fewer tokens), and gives +you structural context (callers, dependents, test coverage) that file +scanning cannot. + +### When to use graph tools FIRST + +- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of Grep +- **Understanding impact**: `get_impact_radius` instead of manually tracing imports +- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files +- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for +- **Architecture questions**: `get_architecture_overview` + `list_communities` + +Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need. + +### Key Tools + +| Tool | Use when | +| ------ | ---------- | +| `detect_changes` | Reviewing code changes — gives risk-scored analysis | +| `get_review_context` | Need source snippets for review — token-efficient | +| `get_impact_radius` | Understanding blast radius of a change | +| `get_affected_flows` | Finding which execution paths are impacted | +| `query_graph` | Tracing callers, callees, imports, tests, dependencies | +| `semantic_search_nodes` | Finding functions/classes by name or keyword | +| `get_architecture_overview` | Understanding high-level codebase structure | +| `refactor_tool` | Planning renames, finding dead code | + +### Workflow + +1. The graph auto-updates on file changes (via hooks). +2. Use `detect_changes` for code review. +3. Use `get_affected_flows` to understand impact. +4. Use `query_graph` pattern="tests_for" to check coverage. diff --git a/README.md b/README.md index 07249031..78934be1 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ code-review-graph build # parse your codebase One command sets up everything. `install` detects which AI coding tools you have, writes the correct MCP configuration for each one, and injects graph-aware instructions into your platform rules. It auto-detects whether you installed via `uvx` or `pip`/`pipx` and generates the right config. Restart your editor/tool after installing.

- One Install, Every Platform: auto-detects Codex, Claude Code, Cursor, Windsurf, Zed, Continue, OpenCode, Antigravity, Qwen, Qoder, and Kiro + One Install, Every Platform: auto-detects Codex, Claude Code, Cursor, Windsurf, Zed, Continue, OpenCode, Antigravity, Qwen, Qoder, Kiro, and GitHub Copilot

To target a specific platform: @@ -55,6 +55,7 @@ code-review-graph install --platform codex # configure only Codex code-review-graph install --platform cursor # configure only Cursor code-review-graph install --platform claude-code # configure only Claude Code code-review-graph install --platform kiro # configure only Kiro +code-review-graph install --platform copilot # configure only GitHub Copilot ``` Requires Python 3.10+. For the best experience, install [uv](https://docs.astral.sh/uv/) (the MCP config will use `uvx` if available, otherwise falls back to the `code-review-graph` command directly). @@ -511,5 +512,5 @@ MIT. See [LICENSE](LICENSE).
code-review-graph.com

pip install code-review-graph && code-review-graph install
-Works with Codex, Claude Code, Cursor, Windsurf, Zed, Continue, OpenCode, Antigravity, Qwen, Qoder, and Kiro +Works with Codex, Claude Code, Cursor, Windsurf, Zed, Continue, OpenCode, Antigravity, Qwen, Qoder, Kiro, and GitHub Copilot

diff --git a/code_review_graph/cli.py b/code_review_graph/cli.py index 18e9d527..794c49c2 100644 --- a/code_review_graph/cli.py +++ b/code_review_graph/cli.py @@ -52,7 +52,7 @@ # Shared platform choices for install and init commands _PLATFORM_CHOICES = [ "codex", "claude", "claude-code", "cursor", "windsurf", "zed", - "continue", "opencode", "antigravity", "qwen", "kiro", "qoder", "all", + "continue", "opencode", "antigravity", "qwen", "kiro", "qoder", "copilot", "all", ] diff --git a/code_review_graph/skills.py b/code_review_graph/skills.py index 66265534..95cfd230 100644 --- a/code_review_graph/skills.py +++ b/code_review_graph/skills.py @@ -120,6 +120,14 @@ def _zed_settings_path() -> Path: "format": "object", "needs_type": True, }, + "copilot": { + "name": "GitHub Copilot", + "config_path": lambda root: root / ".vscode" / "mcp.json", + "key": "servers", + "detect": lambda: (Path.home() / ".vscode").exists(), + "format": "object", + "needs_type": True, + }, } @@ -725,6 +733,7 @@ def inject_claude_md(repo_root: Path) -> None: ".windsurfrules": ("windsurf",), "QODER.md": ("qoder",), ".kiro/steering/code-review-graph.md": ("kiro",), + ".github/copilot-instructions.md": ("copilot",), } diff --git a/tests/test_skills.py b/tests/test_skills.py index 873ed2f8..1e9c1bb2 100644 --- a/tests/test_skills.py +++ b/tests/test_skills.py @@ -329,11 +329,11 @@ def test_idempotent_with_existing_content(self, tmp_path): class TestInjectPlatformInstructionsFiltering: def test_all_writes_every_file(self, tmp_path): updated = inject_platform_instructions(tmp_path, target="all") - assert set(updated) == {"AGENTS.md", "GEMINI.md", ".cursorrules", ".windsurfrules", "QODER.md", ".kiro/steering/code-review-graph.md"} + assert set(updated) == {"AGENTS.md", "GEMINI.md", ".cursorrules", ".windsurfrules", "QODER.md", ".kiro/steering/code-review-graph.md", ".github/copilot-instructions.md"} def test_default_is_all(self, tmp_path): updated = inject_platform_instructions(tmp_path) - assert set(updated) == {"AGENTS.md", "GEMINI.md", ".cursorrules", ".windsurfrules", "QODER.md", ".kiro/steering/code-review-graph.md"} + assert set(updated) == {"AGENTS.md", "GEMINI.md", ".cursorrules", ".windsurfrules", "QODER.md", ".kiro/steering/code-review-graph.md", ".github/copilot-instructions.md"} def test_claude_writes_nothing(self, tmp_path): updated = inject_platform_instructions(tmp_path, target="claude") @@ -925,7 +925,100 @@ def test_kiro_dry_run(self, tmp_path): assert not config_path.exists() -class TestDetectServeCommand: +class TestCopilotPlatform: + """Tests for GitHub Copilot platform support.""" + + def test_copilot_platform_entry_exists(self): + """PLATFORMS dict has a 'copilot' key with correct metadata.""" + assert "copilot" in PLATFORMS + copilot = PLATFORMS["copilot"] + assert copilot["name"] == "GitHub Copilot" + assert copilot["key"] == "servers" + assert copilot["format"] == "object" + assert copilot["needs_type"] is True + + def test_install_copilot_config(self, tmp_path): + """install_platform_configs creates .vscode/mcp.json with 'servers' key.""" + configured = install_platform_configs(tmp_path, target="copilot") + assert "GitHub Copilot" in configured + config_path = tmp_path / ".vscode" / "mcp.json" + assert config_path.exists() + data = json.loads(config_path.read_text()) + assert "code-review-graph" in data["servers"] + entry = data["servers"]["code-review-graph"] + assert entry["type"] == "stdio" + assert "serve" in entry["args"] + + def test_install_copilot_preserves_existing_servers(self, tmp_path): + """Existing server entries are preserved when adding code-review-graph.""" + config_path = tmp_path / ".vscode" / "mcp.json" + config_path.parent.mkdir(parents=True) + config_path.write_text( + json.dumps({"servers": {"other-server": {"command": "other"}}}), + encoding="utf-8", + ) + install_platform_configs(tmp_path, target="copilot") + data = json.loads(config_path.read_text()) + assert "other-server" in data["servers"] + assert "code-review-graph" in data["servers"] + + def test_install_copilot_no_duplicate(self, tmp_path): + """Second install skips when code-review-graph already exists.""" + install_platform_configs(tmp_path, target="copilot") + config_path = tmp_path / ".vscode" / "mcp.json" + first_content = config_path.read_text() + install_platform_configs(tmp_path, target="copilot") + second_content = config_path.read_text() + assert first_content == second_content + data = json.loads(second_content) + assert list(data["servers"].keys()).count("code-review-graph") == 1 + + def test_copilot_instructions_file_written(self, tmp_path): + """inject_platform_instructions creates .github/copilot-instructions.md.""" + updated = inject_platform_instructions(tmp_path, target="copilot") + assert ".github/copilot-instructions.md" in updated + instructions = tmp_path / ".github" / "copilot-instructions.md" + assert instructions.exists() + content = instructions.read_text() + assert _CLAUDE_MD_SECTION_MARKER in content + + def test_copilot_instructions_idempotent(self, tmp_path): + """Running inject twice produces identical content.""" + inject_platform_instructions(tmp_path, target="copilot") + first = (tmp_path / ".github" / "copilot-instructions.md").read_text() + inject_platform_instructions(tmp_path, target="copilot") + second = (tmp_path / ".github" / "copilot-instructions.md").read_text() + assert first == second + + def test_copilot_dry_run(self, tmp_path): + """dry_run=True does not create any files.""" + configured = install_platform_configs(tmp_path, target="copilot", dry_run=True) + assert "GitHub Copilot" in configured + config_path = tmp_path / ".vscode" / "mcp.json" + assert not config_path.exists() + + def test_copilot_writes_only_copilot_instructions(self, tmp_path): + """inject_platform_instructions with target='copilot' writes only copilot file.""" + updated = inject_platform_instructions(tmp_path, target="copilot") + assert updated == [".github/copilot-instructions.md"] + assert not (tmp_path / "AGENTS.md").exists() + assert not (tmp_path / "GEMINI.md").exists() + assert not (tmp_path / ".cursorrules").exists() + assert not (tmp_path / ".windsurfrules").exists() + assert not (tmp_path / "QODER.md").exists() + + def test_copilot_included_in_all_when_detected(self, tmp_path): + """install_platform_configs with target='all' includes Copilot when ~/.vscode exists.""" + fake_home = tmp_path / "fakehome" + (fake_home / ".vscode").mkdir(parents=True) + with patch("code_review_graph.skills.Path.home", return_value=fake_home): + configured = install_platform_configs(tmp_path, target="all") + assert "GitHub Copilot" in configured + config_path = tmp_path / ".vscode" / "mcp.json" + assert config_path.exists() + + + """Tests for _detect_serve_command() and its helpers.""" # ------------------------------------------------------------------ diff --git a/uv.lock b/uv.lock index 62a32add..c40535b3 100644 --- a/uv.lock +++ b/uv.lock @@ -411,6 +411,7 @@ requires-dist = [ { name = "pyyaml", marker = "extra == 'eval'", specifier = ">=6.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.3.0,<1" }, { name = "sentence-transformers", marker = "extra == 'embeddings'", specifier = ">=3.0.0,<4" }, + { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0.0,<3" }, { name = "tomli", marker = "python_full_version < '3.11' and extra == 'dev'", specifier = ">=2.0" }, { name = "tree-sitter", specifier = ">=0.23.0,<1" }, { name = "tree-sitter-language-pack", specifier = ">=0.3.0,<1" }, From 59cc52df80cef793c0667af81c372b96d1c8884d Mon Sep 17 00:00:00 2001 From: Vignesh Date: Thu, 23 Apr 2026 23:10:03 +0530 Subject: [PATCH 2/3] feat: add GitHub Copilot CLI platform support - Add 'copilot-cli' entry to PLATFORMS dict in skills.py - Config: ~/.copilot/mcp-config.json (user-level, global) - Key: 'servers' (VS Code MCP format, same as copilot platform) - Detection: checks ~/.copilot exists - needs_type: True (type: stdio required) - Add 'copilot-cli' as co-owner of .github/copilot-instructions.md in _PLATFORM_INSTRUCTION_FILES so install --platform copilot-cli also injects graph-aware instructions into the project - Add 'copilot-cli' to _PLATFORM_CHOICES in cli.py - Add TestCopilotCLIPlatform test class (8 tests) covering: platform entry metadata, config install, idempotency, existing server preservation, instruction injection, dry-run, all-detection, and user-level config path assertion - Update README platform lists and install examples Unlike the 'copilot' platform (.vscode/mcp.json, project-level), copilot-cli writes to ~/.copilot/mcp-config.json making the MCP server available globally across all projects in the terminal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 5 +- code_review_graph/cli.py | 3 +- code_review_graph/skills.py | 10 ++- tests/test_skills.py | 138 ++++++++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 78934be1..69ec730f 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ code-review-graph install --platform codex # configure only Codex code-review-graph install --platform cursor # configure only Cursor code-review-graph install --platform claude-code # configure only Claude Code code-review-graph install --platform kiro # configure only Kiro -code-review-graph install --platform copilot # configure only GitHub Copilot +code-review-graph install --platform copilot # configure only GitHub Copilot (VS Code) +code-review-graph install --platform copilot-cli # configure only GitHub Copilot CLI ``` Requires Python 3.10+. For the best experience, install [uv](https://docs.astral.sh/uv/) (the MCP config will use `uvx` if available, otherwise falls back to the `code-review-graph` command directly). @@ -512,5 +513,5 @@ MIT. See [LICENSE](LICENSE).
code-review-graph.com

pip install code-review-graph && code-review-graph install
-Works with Codex, Claude Code, Cursor, Windsurf, Zed, Continue, OpenCode, Antigravity, Qwen, Qoder, Kiro, and GitHub Copilot +Works with Codex, Claude Code, Cursor, Windsurf, Zed, Continue, OpenCode, Antigravity, Qwen, Qoder, Kiro, GitHub Copilot, and GitHub Copilot CLI

diff --git a/code_review_graph/cli.py b/code_review_graph/cli.py index 794c49c2..86411aeb 100644 --- a/code_review_graph/cli.py +++ b/code_review_graph/cli.py @@ -52,7 +52,8 @@ # Shared platform choices for install and init commands _PLATFORM_CHOICES = [ "codex", "claude", "claude-code", "cursor", "windsurf", "zed", - "continue", "opencode", "antigravity", "qwen", "kiro", "qoder", "copilot", "all", + "continue", "opencode", "antigravity", "qwen", "kiro", "qoder", + "copilot", "copilot-cli", "all", ] diff --git a/code_review_graph/skills.py b/code_review_graph/skills.py index 95cfd230..26ce11e1 100644 --- a/code_review_graph/skills.py +++ b/code_review_graph/skills.py @@ -128,6 +128,14 @@ def _zed_settings_path() -> Path: "format": "object", "needs_type": True, }, + "copilot-cli": { + "name": "GitHub Copilot CLI", + "config_path": lambda root: Path.home() / ".copilot" / "mcp-config.json", + "key": "servers", + "detect": lambda: (Path.home() / ".copilot").exists(), + "format": "object", + "needs_type": True, + }, } @@ -733,7 +741,7 @@ def inject_claude_md(repo_root: Path) -> None: ".windsurfrules": ("windsurf",), "QODER.md": ("qoder",), ".kiro/steering/code-review-graph.md": ("kiro",), - ".github/copilot-instructions.md": ("copilot",), + ".github/copilot-instructions.md": ("copilot", "copilot-cli"), } diff --git a/tests/test_skills.py b/tests/test_skills.py index 1e9c1bb2..5ab71aad 100644 --- a/tests/test_skills.py +++ b/tests/test_skills.py @@ -343,6 +343,7 @@ def test_claude_writes_nothing(self, tmp_path): assert not (tmp_path / ".cursorrules").exists() assert not (tmp_path / ".windsurfrules").exists() assert not (tmp_path / "QODER.md").exists() + assert not (tmp_path / ".github" / "copilot-instructions.md").exists() def test_cursor_writes_only_cursor_files(self, tmp_path): updated = inject_platform_instructions(tmp_path, target="cursor") @@ -1018,7 +1019,144 @@ def test_copilot_included_in_all_when_detected(self, tmp_path): assert config_path.exists() +class TestCopilotCLIPlatform: + """Tests for GitHub Copilot CLI platform support.""" + def test_copilot_cli_platform_entry_exists(self): + """PLATFORMS dict has a 'copilot-cli' key with correct metadata.""" + assert "copilot-cli" in PLATFORMS + copilot_cli = PLATFORMS["copilot-cli"] + assert copilot_cli["name"] == "GitHub Copilot CLI" + assert copilot_cli["key"] == "servers" + assert copilot_cli["format"] == "object" + assert copilot_cli["needs_type"] is True + + def test_install_copilot_cli_config(self, tmp_path): + """install_platform_configs creates ~/.copilot/mcp-config.json with 'servers' key.""" + fake_home = tmp_path / "fakehome" + (fake_home / ".copilot").mkdir(parents=True) + config_path = fake_home / ".copilot" / "mcp-config.json" + with patch.dict( + PLATFORMS, + { + "copilot-cli": { + **PLATFORMS["copilot-cli"], + "config_path": lambda root: config_path, + "detect": lambda: True, + }, + }, + ): + configured = install_platform_configs(tmp_path, target="copilot-cli") + assert "GitHub Copilot CLI" in configured + assert config_path.exists() + data = json.loads(config_path.read_text()) + assert "code-review-graph" in data["servers"] + entry = data["servers"]["code-review-graph"] + assert entry["type"] == "stdio" + assert "serve" in entry["args"] + + def test_install_copilot_cli_preserves_existing_servers(self, tmp_path): + """Existing server entries are preserved when adding code-review-graph.""" + fake_home = tmp_path / "fakehome" + config_path = fake_home / ".copilot" / "mcp-config.json" + config_path.parent.mkdir(parents=True) + config_path.write_text( + json.dumps({"servers": {"other-server": {"command": "other"}}}), + encoding="utf-8", + ) + with patch.dict( + PLATFORMS, + { + "copilot-cli": { + **PLATFORMS["copilot-cli"], + "config_path": lambda root: config_path, + "detect": lambda: True, + }, + }, + ): + install_platform_configs(tmp_path, target="copilot-cli") + data = json.loads(config_path.read_text()) + assert "other-server" in data["servers"] + assert "code-review-graph" in data["servers"] + + def test_install_copilot_cli_no_duplicate(self, tmp_path): + """Second install skips when code-review-graph already exists.""" + fake_home = tmp_path / "fakehome" + config_path = fake_home / ".copilot" / "mcp-config.json" + with patch.dict( + PLATFORMS, + { + "copilot-cli": { + **PLATFORMS["copilot-cli"], + "config_path": lambda root: config_path, + "detect": lambda: True, + }, + }, + ): + install_platform_configs(tmp_path, target="copilot-cli") + first_content = config_path.read_text() + install_platform_configs(tmp_path, target="copilot-cli") + second_content = config_path.read_text() + assert first_content == second_content + data = json.loads(second_content) + assert list(data["servers"].keys()).count("code-review-graph") == 1 + + def test_copilot_cli_injects_copilot_instructions(self, tmp_path): + """inject_platform_instructions with target='copilot-cli' writes .github/copilot-instructions.md.""" + updated = inject_platform_instructions(tmp_path, target="copilot-cli") + assert ".github/copilot-instructions.md" in updated + instructions = tmp_path / ".github" / "copilot-instructions.md" + assert instructions.exists() + content = instructions.read_text() + assert _CLAUDE_MD_SECTION_MARKER in content + + def test_copilot_cli_dry_run(self, tmp_path): + """dry_run=True does not create any files.""" + fake_home = tmp_path / "fakehome" + config_path = fake_home / ".copilot" / "mcp-config.json" + with patch.dict( + PLATFORMS, + { + "copilot-cli": { + **PLATFORMS["copilot-cli"], + "config_path": lambda root: config_path, + "detect": lambda: True, + }, + }, + ): + configured = install_platform_configs(tmp_path, target="copilot-cli", dry_run=True) + assert "GitHub Copilot CLI" in configured + assert not config_path.exists() + + def test_copilot_cli_included_in_all_when_detected(self, tmp_path): + """install_platform_configs with target='all' includes Copilot CLI when ~/.copilot exists.""" + fake_home = tmp_path / "fakehome" + (fake_home / ".copilot").mkdir(parents=True) + config_path = fake_home / ".copilot" / "mcp-config.json" + with patch.dict( + PLATFORMS, + { + "copilot-cli": { + **PLATFORMS["copilot-cli"], + "config_path": lambda root: config_path, + "detect": lambda: True, + }, + "copilot": {**PLATFORMS["copilot"], "detect": lambda: False}, + }, + ): + configured = install_platform_configs(tmp_path, target="all") + assert "GitHub Copilot CLI" in configured + assert config_path.exists() + + def test_copilot_cli_user_level_config_not_in_project(self, tmp_path): + """Copilot CLI config is user-level (not inside the repo root).""" + config_path = PLATFORMS["copilot-cli"]["config_path"](tmp_path) + assert not str(config_path).startswith(str(tmp_path)), ( + "copilot-cli config should be user-level (~/.copilot/), not project-level" + ) + + +class TestDetectServeCommand: """Tests for _detect_serve_command() and its helpers.""" # ------------------------------------------------------------------ From f04ee2c0c131f995960a8429e93bef519e0aef7a Mon Sep 17 00:00:00 2001 From: Vignesh Date: Thu, 30 Apr 2026 12:55:53 +0530 Subject: [PATCH 3/3] fix: rename copilot instructions file and add frontmatter + VS Code tool refs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per PR #382 review feedback: - Rename .github/copilot-instructions.md → .github/code-review-graph.instruction.md to avoid conflicts with users' existing copilot-instructions.md files - Add YAML front matter (applyTo, description) so Copilot Chat applies the instructions workspace-wide as a skill - Replace generic 'Grep/Glob/Read' with Copilot-specific tool references (#tool:read/readFile, #tool:search/fileSearch, #tool:search/textSearch) so the model text-matches on explicit tool names - Add _COPILOT_SECTION constant and _PLATFORM_INSTRUCTION_CUSTOM_SECTIONS dict in skills.py to use the Copilot-tailored section for the instruction file - Update all test references to the new filename Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ns.md => code-review-graph.instruction.md} | 7 ++- code_review_graph/skills.py | 60 ++++++++++++++++++- tests/test_skills.py | 24 ++++---- 3 files changed, 76 insertions(+), 15 deletions(-) rename .github/{copilot-instructions.md => code-review-graph.instruction.md} (85%) diff --git a/.github/copilot-instructions.md b/.github/code-review-graph.instruction.md similarity index 85% rename from .github/copilot-instructions.md rename to .github/code-review-graph.instruction.md index d3dc9b48..1d51118b 100644 --- a/.github/copilot-instructions.md +++ b/.github/code-review-graph.instruction.md @@ -1,8 +1,13 @@ +--- +applyTo: '**' +description: Use code-review-graph MCP tools for token-efficient codebase exploration and code review instead of built-in file/search tools. +--- + ## MCP Tools: code-review-graph **IMPORTANT: This project has a knowledge graph. ALWAYS use the -code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore +code-review-graph MCP tools BEFORE using #tool:read/readFile #tool:search/fileSearch #tool:search/textSearch to explore the codebase.** The graph is faster, cheaper (fewer tokens), and gives you structural context (callers, dependents, test coverage) that file scanning cannot. diff --git a/code_review_graph/skills.py b/code_review_graph/skills.py index 26ce11e1..efddc136 100644 --- a/code_review_graph/skills.py +++ b/code_review_graph/skills.py @@ -697,6 +697,59 @@ def install_hooks(repo_root: Path, platform: str = "claude") -> None: 4. Use `query_graph` pattern=\"tests_for\" to check coverage. """ +# Copilot-specific instruction file content: uses VS Code tool references and +# includes YAML front matter so Copilot Chat applies it across the workspace. +_COPILOT_SECTION = f"""--- +applyTo: '**' +description: Use code-review-graph MCP tools for token-efficient codebase exploration and code review instead of built-in file/search tools. +--- + +{_CLAUDE_MD_SECTION_MARKER} +## MCP Tools: code-review-graph + +**IMPORTANT: This project has a knowledge graph. ALWAYS use the +code-review-graph MCP tools BEFORE using #tool:read/readFile #tool:search/fileSearch #tool:search/textSearch to explore +the codebase.** The graph is faster, cheaper (fewer tokens), and gives +you structural context (callers, dependents, test coverage) that file +scanning cannot. + +### When to use graph tools FIRST + +- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of #tool:search/fileSearch +- **Understanding impact**: `get_impact_radius` instead of manually tracing imports +- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files +- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for +- **Architecture questions**: `get_architecture_overview` + `list_communities` + +Fall back to #tool:read/readFile, #tool:search/fileSearch, or #tool:search/textSearch **only** when the graph doesn't cover what you need. + +### Key Tools + +| Tool | Use when | +| ------ | ---------- | +| `detect_changes` | Reviewing code changes — gives risk-scored analysis | +| `get_review_context` | Need source snippets for review — token-efficient | +| `get_impact_radius` | Understanding blast radius of a change | +| `get_affected_flows` | Finding which execution paths are impacted | +| `query_graph` | Tracing callers, callees, imports, tests, dependencies | +| `semantic_search_nodes` | Finding functions/classes by name or keyword | +| `get_architecture_overview` | Understanding high-level codebase structure | +| `refactor_tool` | Planning renames, finding dead code | + +### Workflow + +1. The graph auto-updates on file changes (via hooks). +2. Use `detect_changes` for code review. +3. Use `get_affected_flows` to understand impact. +4. Use `query_graph` pattern=\"tests_for\" to check coverage. +""" + +# Maps instruction file path → (marker, section) for files that need content +# different from the default _CLAUDE_MD_SECTION. +_PLATFORM_INSTRUCTION_CUSTOM_SECTIONS: dict[str, tuple[str, str]] = { + ".github/code-review-graph.instruction.md": (_CLAUDE_MD_SECTION_MARKER, _COPILOT_SECTION), +} + def _inject_instructions(file_path: Path, marker: str, section: str) -> bool: """Append an instruction section to a file if not already present. @@ -741,7 +794,7 @@ def inject_claude_md(repo_root: Path) -> None: ".windsurfrules": ("windsurf",), "QODER.md": ("qoder",), ".kiro/steering/code-review-graph.md": ("kiro",), - ".github/copilot-instructions.md": ("copilot", "copilot-cli"), + ".github/code-review-graph.instruction.md": ("copilot", "copilot-cli"), } @@ -763,7 +816,10 @@ def inject_platform_instructions(repo_root: Path, target: str = "all") -> list[s if target != "all" and target not in owners: continue path = repo_root / filename - if _inject_instructions(path, _CLAUDE_MD_SECTION_MARKER, _CLAUDE_MD_SECTION): + marker, section = _PLATFORM_INSTRUCTION_CUSTOM_SECTIONS.get( + filename, (_CLAUDE_MD_SECTION_MARKER, _CLAUDE_MD_SECTION) + ) + if _inject_instructions(path, marker, section): updated.append(filename) return updated diff --git a/tests/test_skills.py b/tests/test_skills.py index 5ab71aad..b0dddae7 100644 --- a/tests/test_skills.py +++ b/tests/test_skills.py @@ -329,11 +329,11 @@ def test_idempotent_with_existing_content(self, tmp_path): class TestInjectPlatformInstructionsFiltering: def test_all_writes_every_file(self, tmp_path): updated = inject_platform_instructions(tmp_path, target="all") - assert set(updated) == {"AGENTS.md", "GEMINI.md", ".cursorrules", ".windsurfrules", "QODER.md", ".kiro/steering/code-review-graph.md", ".github/copilot-instructions.md"} + assert set(updated) == {"AGENTS.md", "GEMINI.md", ".cursorrules", ".windsurfrules", "QODER.md", ".kiro/steering/code-review-graph.md", ".github/code-review-graph.instruction.md"} def test_default_is_all(self, tmp_path): updated = inject_platform_instructions(tmp_path) - assert set(updated) == {"AGENTS.md", "GEMINI.md", ".cursorrules", ".windsurfrules", "QODER.md", ".kiro/steering/code-review-graph.md", ".github/copilot-instructions.md"} + assert set(updated) == {"AGENTS.md", "GEMINI.md", ".cursorrules", ".windsurfrules", "QODER.md", ".kiro/steering/code-review-graph.md", ".github/code-review-graph.instruction.md"} def test_claude_writes_nothing(self, tmp_path): updated = inject_platform_instructions(tmp_path, target="claude") @@ -343,7 +343,7 @@ def test_claude_writes_nothing(self, tmp_path): assert not (tmp_path / ".cursorrules").exists() assert not (tmp_path / ".windsurfrules").exists() assert not (tmp_path / "QODER.md").exists() - assert not (tmp_path / ".github" / "copilot-instructions.md").exists() + assert not (tmp_path / ".github" / "code-review-graph.instruction.md").exists() def test_cursor_writes_only_cursor_files(self, tmp_path): updated = inject_platform_instructions(tmp_path, target="cursor") @@ -975,10 +975,10 @@ def test_install_copilot_no_duplicate(self, tmp_path): assert list(data["servers"].keys()).count("code-review-graph") == 1 def test_copilot_instructions_file_written(self, tmp_path): - """inject_platform_instructions creates .github/copilot-instructions.md.""" + """inject_platform_instructions creates .github/code-review-graph.instruction.md.""" updated = inject_platform_instructions(tmp_path, target="copilot") - assert ".github/copilot-instructions.md" in updated - instructions = tmp_path / ".github" / "copilot-instructions.md" + assert ".github/code-review-graph.instruction.md" in updated + instructions = tmp_path / ".github" / "code-review-graph.instruction.md" assert instructions.exists() content = instructions.read_text() assert _CLAUDE_MD_SECTION_MARKER in content @@ -986,9 +986,9 @@ def test_copilot_instructions_file_written(self, tmp_path): def test_copilot_instructions_idempotent(self, tmp_path): """Running inject twice produces identical content.""" inject_platform_instructions(tmp_path, target="copilot") - first = (tmp_path / ".github" / "copilot-instructions.md").read_text() + first = (tmp_path / ".github" / "code-review-graph.instruction.md").read_text() inject_platform_instructions(tmp_path, target="copilot") - second = (tmp_path / ".github" / "copilot-instructions.md").read_text() + second = (tmp_path / ".github" / "code-review-graph.instruction.md").read_text() assert first == second def test_copilot_dry_run(self, tmp_path): @@ -1001,7 +1001,7 @@ def test_copilot_dry_run(self, tmp_path): def test_copilot_writes_only_copilot_instructions(self, tmp_path): """inject_platform_instructions with target='copilot' writes only copilot file.""" updated = inject_platform_instructions(tmp_path, target="copilot") - assert updated == [".github/copilot-instructions.md"] + assert updated == [".github/code-review-graph.instruction.md"] assert not (tmp_path / "AGENTS.md").exists() assert not (tmp_path / "GEMINI.md").exists() assert not (tmp_path / ".cursorrules").exists() @@ -1102,10 +1102,10 @@ def test_install_copilot_cli_no_duplicate(self, tmp_path): assert list(data["servers"].keys()).count("code-review-graph") == 1 def test_copilot_cli_injects_copilot_instructions(self, tmp_path): - """inject_platform_instructions with target='copilot-cli' writes .github/copilot-instructions.md.""" + """inject_platform_instructions with target='copilot-cli' writes .github/code-review-graph.instruction.md.""" updated = inject_platform_instructions(tmp_path, target="copilot-cli") - assert ".github/copilot-instructions.md" in updated - instructions = tmp_path / ".github" / "copilot-instructions.md" + assert ".github/code-review-graph.instruction.md" in updated + instructions = tmp_path / ".github" / "code-review-graph.instruction.md" assert instructions.exists() content = instructions.read_text() assert _CLAUDE_MD_SECTION_MARKER in content