Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions copilot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ Verified against GitHub's documentation for what Copilot actually reads.

| Category | Paths |
|---|---|
| Instructions | `.github/copilot-instructions.md`, `.github/instructions/**/*.instructions.md`, **`AGENTS.md` anywhere in the tree**, root `CLAUDE.md` and `GEMINI.md` |
| Instructions and custom agents | `.github/copilot-instructions.md`, `.github/instructions/**/*.instructions.md`, `.github/agents/**/*.agent.md`, **`AGENTS.md` anywhere in the tree**, root `CLAUDE.md` and `GEMINI.md` |
| Skills | `.github/skills/<name>/`, `.claude/skills/<name>/`, `.agents/skills/<name>/` |
| MCP | `copilot/mcp-config.json`, `.vscode/mcp.json` |
| MCP | `.vscode/mcp.json` (VS Code workspace configuration) |

Two of those deserve a note.

Expand All @@ -85,6 +85,15 @@ skipped, so a dependency shipping its own `AGENTS.md` is not counted as yours.
`scripts/` decide what it does. Digesting the manifest alone was a live bypass in
two other engines in this repo, so the shared core covers the tree.

**MCP configuration depends on the Copilot surface.** VS Code reads the
repository's `.vscode/mcp.json`, so it is measured here. Copilot cloud-agent MCP
servers are configured in the repository's GitHub settings and are not stored in
a repository file, so this file-based check cannot measure them. A custom cloud
agent may embed `mcp-servers` in `.github/agents/*.agent.md`; those profiles are
measured in full with the instruction surface. Copilot CLI instead reads the
user-level `~/.copilot/mcp-config.json`, outside this repository check. There is
no documented repository-level `copilot/mcp-config.json` path.

## What it does not do

- **It does not read your model or your tool roster.** Those are session facts, not
Expand Down
5 changes: 3 additions & 2 deletions copilot/engine/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
.github/instructions/**/*.instructions.md path-scoped, applyTo
AGENTS.md anywhere in the tree nearest wins
CLAUDE.md, GEMINI.md at the root alternatives to AGENTS.md
.github/agents/**/*.agent.md custom cloud agents
skills .github/skills/<name>/SKILL.md plus supporting files
.claude/skills/<name>/SKILL.md
.agents/skills/<name>/SKILL.md
mcp copilot/mcp-config.json, .vscode/mcp.json
mcp .vscode/mcp.json VS Code workspace

Standard library only, so the action needs no install step.

Expand Down Expand Up @@ -78,6 +79,7 @@
#: there without touching anything at the root.
INSTRUCTION_GLOBS = (
".github/instructions/**/*.instructions.md",
".github/agents/**/*.agent.md",
"**/AGENTS.md",
)

Expand All @@ -90,7 +92,6 @@

#: MCP server configuration Copilot may read from the repository.
MCP_FILES = (
"copilot/mcp-config.json",
".vscode/mcp.json",
)

Expand Down
26 changes: 22 additions & 4 deletions copilot/tests/test_copilot_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ def test_agents_md_anywhere_is_measured(self, tmp_path):
(nested / "AGENTS.md").write_text("Never touch prod.\n", encoding="utf-8")
assert "services/billing/AGENTS.md" in capture.snapshot(root)["instructions"]

def test_custom_agent_profile_is_measured(self, tmp_path):
root = _repo(tmp_path)
profile = root / ".github" / "agents" / "security.agent.md"
profile.parent.mkdir(parents=True)
profile.write_text(
"---\ndescription: Security review\nmcp-servers:\n audit:\n"
" type: local\n command: audit\n---\nReview this change.\n",
encoding="utf-8",
)
assert ".github/agents/security.agent.md" in capture.snapshot(root)["instructions"]

@pytest.mark.parametrize("name", ["CLAUDE.md", "GEMINI.md"])
def test_root_alternatives_are_measured(self, tmp_path, name):
root = _repo(tmp_path)
Expand Down Expand Up @@ -150,24 +161,31 @@ def test_skill_state_churn_does_not_alarm(self, tmp_path):


class TestMcpSurface:
@pytest.mark.parametrize("name", ["copilot/mcp-config.json", ".vscode/mcp.json"])
def test_mcp_config_is_measured(self, tmp_path, name):
def test_vscode_mcp_config_is_measured(self, tmp_path):
root = _repo(tmp_path)
name = ".vscode/mcp.json"
target = root / name
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text('{"servers": {}}', encoding="utf-8")
assert name in capture.snapshot(root)["mcp"]

def test_a_new_mcp_server_is_reported(self, tmp_path):
def test_undocumented_repository_mcp_path_is_not_measured(self, tmp_path):
root = _repo(tmp_path)
target = root / "copilot" / "mcp-config.json"
target.parent.mkdir(parents=True)
target.write_text('{"mcpServers": {}}', encoding="utf-8")
assert capture.snapshot(root)["mcp"] == {}

def test_a_new_mcp_server_is_reported(self, tmp_path):
root = _repo(tmp_path)
target = root / ".vscode" / "mcp.json"
target.parent.mkdir(parents=True)
target.write_text('{"servers": {}}', encoding="utf-8")
before = capture.snapshot(root)
target.write_text('{"servers": {"shadow": {"command": "x"}}}', encoding="utf-8")
changes = capture.diff(before, capture.snapshot(root))
assert {"change": "changed", "what": "MCP config",
"detail": "copilot/mcp-config.json"} in changes
"detail": ".vscode/mcp.json"} in changes


class TestVerifyAsAStatusCheck:
Expand Down
Loading