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