GitHub Copilot (copilot)
Output
.github/copilot-instructions.md # canonical entry-point pointer body (written by sync)
.github/instructions/<name>.instructions.md # scoped rule per file
.github/agents/<name>.agent.md # one custom-agent profile per agent
.github/skills/<name>/SKILL.md # one folder per skill, bundled assets included
.vscode/mcp.json # when MCP entries exist; VS Code's file, servers key
.github/mcp.json # when MCP entries exist; Copilot CLI's file, mcpServers key
.mcp.json # only with outputs.copilot.root-mcp-file; mcpServers key
.github/hooks/agnostic-ai.json # when hook entries exist
.github/copilot/settings.json # when a Settings model exists
Rules: Copilot supports path-scoped instructions via
applyTo:frontmatter. Rules withglobs(or a source-layout scope likerules/backend/auth.md) emit as a separate.instructions.mdwithapplyToderived from globs (explicitglobswins, else<scope>/**). Always-on rules (no globs, no scope, oralwaysApply: true) skip per-file emission and are reachable via the pointer body plus the source spec dir. The adapter manages no rule frontmatter keys: everyx-copilotkey lands alongsideapplyTo.Agents: native custom agent profiles at
.github/agents/<name>.agent.md: frontmattername+descriptionplustoolsandmodelwhen the spec declares them; arbitraryx-copilotkeys (target,user-invocable,mcp-servers, ...) pass through. The body is the agent prompt. The old flattenedagent-<name>.instructions.mdcopies are gone; the ledger sweeps them.Skills: native Copilot skills folders at
.github/skills/<name>/SKILL.md(Copilot also scans.claude/skills/and.agents/skills/), with bundled sibling files propagated byte-for-byte. The old flattenedskill-<name>.instructions.mdcopies are gone; the ledger sweeps them.Chat modes: when
outputs.copilot.chatmodes-diris set, each agent also emits as a Copilot Custom Chat Mode at<dir>/<name>.chatmode.mdwithdescription/model/toolsfrontmatter. The native agent profile still emits alongside.Settings: the last portable
modelvalue merges into.github/copilot/settings.json, where Copilot CLI documents it as the repository default model. Other repository settings survive.import copilotrestores the model tosettings/imported.yamland leaves target-only keys in the native file.MCP: two files, because Copilot has two readers that disagree on which one to open.
.vscode/mcp.jsonuses the VS Code schema: top-levelserverskey, each entry carrying atype(stdio,http, orsse). VS Code's Agent Host does not read that file itself: the vendor doc says it "doesn't read.vscode/mcp.jsondirectly" and that VS Code forwards the config instead, except servers needing interactive input..github/mcp.jsoncarries the same servers undermcpServersfor Copilot CLI, which its project-level discovery table lists as "Shared configuration that is committed to the repository". That second file exists because the CLI rejects the VS Code wrapper outright: "The.vscode/mcp.jsonfile for VS Code is not read by Copilot CLI. It uses the unsupported top-level keyservers." Before #646 only the VS Code file emitted, so a Copilot CLI user with no VS Code in the loop got no MCP server at all. Override either path withoutputs.copilot.mcp-file/outputs.copilot.cli-mcp-file.VS Code MCP output owns the complete
serversmap and preserves unrelated top-level keys, includinginputsandsandbox, at the default or configured path. JSONC is accepted; sync removes comments and normalizes formatting. Invalid JSONC aborts the write..github/mcp.jsonand the opt-in root mirror remain managed as whole documents.VS Code alone accepts stdio
cwd,envFile(for example${workspaceFolder}/.env), andsandboxEnabled(macOS and Linux only), remoteoauth: {clientId, enterpriseManaged}, anddev.watch(a glob or glob array that restarts the server on change) on stdio, HTTP, and SSE servers.dev.debug, shapeddebug: {type: "node"|"debugpy", debugpyPath}, is stdio-only; a remote server that sets it gets a coverage note while its watch patterns still emit. These fields stay out of the Copilot CLI file and root mirror. Every entry also accepts MCProots, a list of{uri, name}objects. See VS Code MCP configuration.The same table's third entry, a
.mcp.jsonanywhere from the working directory up to the repository root, stays opt-in behindoutputs.copilot.root-mcp-file: .mcp.json. The project root is shared ground with Claude Code rather than Copilot's own directory, and a repository-root file is a surprise for a project that does not need it. Claude Code writes its own root.mcp.jsonundermcpServerstoo, so enabling both targets produces identical bytes at one path; the collision check compares content, not owners, so it stays quiet and sync writes the file once.Hooks: land in
.github/hooks/agnostic-ai.json(override viaoutputs.copilot.hooks-file), one of possibly several*.jsonfiles Copilot loads and merges from that directory: "Repository-level hook files:.github/hooks/*.jsonin the repository root", read by both Copilot CLI and Copilot cloud agent (docs.github.com/en/copilot/reference/hooks-reference, "Hooks locations"). The wrapper is{"version": 1, "hooks": {...}}with an integer version. Each hook entry is a flat object carryingmatcherdirectly ({"type": "command", "matcher": ..., "command": ..., "timeoutSec": ...}), not Claude Code's nested{matcher, hooks: [...]}group. 14 events exist today, one more than the 13 recorded when #629 was filed.event:passes through verbatim, same as every other hook emitter in this repo. The vendor documents both the PascalCase vocabulary Claude Code, Codex, OpenHands, Windsurf, and Qoder share (PreToolUse) and Copilot's own camelCase form (preToolUse) as independently valid keys in this same file, selecting between its "VS Code compatible" and "camelCase" hook payload formats respectively. The PascalCase form also carries Claude's own matcher semantics and tool names, somatcher: Bashreaches it unchanged. The camelCase form answers only to Copilot's own lowercase tool names (bash,edit,view, ...), and pairing it with a Claude-style matcher earns a coverage note rather than a guessed rename.Two of the 14 rows are camelCase-only:
userPromptTransformedandsubagentStart("A subagent is spawned (before it runs)."). Neither has a PascalCase pairing anywhere on the page, so a spec spelling either one in PascalCase emits a key Copilot parses and never fires.SubagentStartis the one that bites, since Claude Code and Codex both accept it (target-audit 2026-09-11, #737).Command, HTTP (
url,headers,allowedEnvVars), andsessionStartprompt handlers all surviveimport copilotand re-emission.A hook spec that sets
argsemits Copilot's own shell-free form instead:{"type": "command", "exec": <command>, "args": [...]}. The field table is explicit that the executable moves out ofcommand(execis accepted "Instead ofbash,powershell, andcommand", and "Do not combineexecwithbash,powershell, orcommand"), which is where this differs from Claude Code, whose exec form keeps the executable incommand. It buys whatargsis for: a path or argument carrying a space, running as written.It costs one surface:
execandargsare both marked "Only supported in Copilot CLI", and a cloud agent job reads the same.github/hooks/*.jsonfiles where "A subset of events fires, and onlybash(orcommand) entries are honored." An exec-form entry is skipped whole there.syncraises a coverage note naming that trade. Leaveargsunset for a hook that must run under cloud agent (#755).Every hook runs twice when you sync
claudeandcopilottogether. The same "Hooks locations" section names both files:.github/hooks/*.jsonand, as a cross-tool read,.claude/settings.jsonand.claude/settings.local.json. Sources are "loaded ... and combined", and "When the same event appears in multiple sources, all hook entries from all sources are run."Both targets are in the default target list, so this is the out-of-the-box path: a formatter runs twice, an audit hook double-writes, and a blocking
preToolUsereturns two decisions per tool call. Copilot names no toggle for that read, unlike Cursor and Trae, which gate theirs behind an off-by-default switch. Until there is one, give the hook spec a singletarget:rather than both (#755).
Config keys
| Key | Default | Notes |
|---|---|---|
outputs.copilot.instructions-dir | .github/instructions | |
outputs.copilot.agents-dir | .github/agents | |
outputs.copilot.skills-dir | .github/skills | |
outputs.copilot.mcp-file | .vscode/mcp.json | |
outputs.copilot.cli-mcp-file | .github/mcp.json | Copilot CLI's own file, mcpServers key |
outputs.copilot.root-mcp-file | unset, opt-in | writes the same servers to a workspace-root .mcp.json under the mcpServers key |
outputs.copilot.chatmodes-dir | empty, opt-in | emits one Custom Chat Mode per agent |
outputs.copilot.rules-file | unset | writes always-on rules concatenated at that path and skips the pointer-body write |
outputs.copilot.hooks-file | .github/hooks/agnostic-ai.json |
Import
agnostic-ai import copilot reads:
| Source | Becomes |
|---|---|
.github/copilot-instructions.md | <rules>/<slug>.md per ## section, plus a copy at .agnostic-ai/AGNOSTIC_AI.md |
.github/instructions/<name>.instructions.md | <rules>/<name>.md; the agent- and skill- filename prefixes become agents and skills |
.github/agents/<name>.agent.md and .github/chatmodes/<name>.chatmode.md | <agents>/<name>.md |
.github/skills/<name>/ | <skills>/<name>/ |
.github/hooks/*.json | one target-scoped hook spec per handler |
.vscode/mcp.json | <mcps>/<name>.yaml |
.github/copilot/settings.json model | the portable Settings source |
Verify
- Install the GitHub Copilot extension (and optional Copilot Chat) in VS Code.
- Check the tree:
ls .github/copilot-instructions.md .github/instructions/ .vscode/mcp.json .github/mcp.json,grep "Generated by agnostic-ai" .github/instructions/*.mdfor the provenance header (it sits after theapplyTofrontmatter),python -m json.tool .vscode/mcp.json > /dev/null && python -m json.tool .github/mcp.json > /dev/null. - Open the project. Copilot loads
.github/copilot-instructions.mdplus every matching.instructions.md. TheOutput → GitHub Copilotchannel shows no "failed to parse instructions" warnings. - Open a file matching a rule glob (e.g. a
.gofile forapplyTo: "**/*.go") and trigger chat; the rule body shows in context. - If MCPs are configured, run
MCP: Show Installed Servers; eachservers.<name>from.vscode/mcp.jsonis ready. In Copilot CLI, run/mcpfrom the project root after accepting the folder-trust prompt; eachmcpServers.<name>from.github/mcp.jsonis listed. - If hooks are configured,
python -m json.tool .github/hooks/agnostic-ai.json > /dev/nullconfirms the file parses. In Copilot CLI, a hook set onPreToolUseorpreToolUsefires on the next matching tool call; the CLI's own hook log names which file and entry ran.