From 88cfdcf8a1f78dd67e8207ea2c929905e2f397a3 Mon Sep 17 00:00:00 2001 From: CaptainReply Date: Fri, 14 Aug 2026 21:59:17 +0200 Subject: [PATCH 1/2] feat(skills): verify GitHub Copilot as a host MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The last host carrying paths from documentation alone. Verified against Copilot CLI 1.0.80 at ~/.copilot/skills, and at .agents/skills with --project, in both directions: the installer's own report, and `copilot skill list --json` naming every skill it loaded and the directory it came from. A session invoked the reply-mcp skill and answered from the pack. Per-pack install and remove were exercised the same way at both scopes — 9 skills, 14, 18 and back — each count read from the host rather than from the files on disk. The registry keeps .agents/skills for project scope, which the host reads alongside .github/skills and .claude/skills, so one install still serves the other flat hosts. Two orchestrate tests moved off `verified` and onto needs_new_session. With every host verified, a report asserting `verified` alone can no longer fail — a stamp hardcoded to true would pass it — while needs_new_session is mixed and still catches a dropped or invented flag. --- README.md | 32 +++++++++++++------- src/__tests__/skills/hosts.test.ts | 2 +- src/__tests__/skills/orchestrate.test.ts | 37 ++++++++++-------------- src/skills/hosts.ts | 4 ++- 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 6183352..bf734b7 100644 --- a/README.md +++ b/README.md @@ -273,20 +273,30 @@ receive the skills as files. Add `--json` for a machine-readable report, and | Cursor | copied files | `~/.cursor/skills`, or `.agents/skills` with `--project` | Cursor 3.14.27, cursor-agent 2026.08.04-aaa8809 | a new session (a new chat) | | Windsurf (ships as Devin) | copied files | `~/.codeium/windsurf/skills`, or `.windsurf/skills` with `--project` | Devin 3.6.27, devin CLI 3000.3.27 | a new session (the next `devin` run) | | Antigravity | copied files | `~/.gemini/config/skills`, or `.agents/skills` with `--project` | Antigravity 2.0.1 | nothing — it re-reads its skills every turn, including in a conversation already open | -| GitHub Copilot | copied files | `~/.copilot/skills`, or `.agents/skills` with `--project` | not yet | — | +| GitHub Copilot | copied files | `~/.copilot/skills`, or `.agents/skills` with `--project` | Copilot CLI 1.0.80 | a new session, or `/skills reload` in the open one | The project directories overlap in a way the rows above do not show: Windsurf also reads `.agents/skills`, which is where `--project` puts things for Cursor, -Codex and Antigravity. A project-scope install aimed at one of them therefore -makes those skills visible in the others, and removing them for one changes what -the others see. Use user scope when you want a host's skills to itself. - -"Not yet" means the skills directory for that assistant comes from its -documentation and has not been confirmed by a verification run of our own: the -install works, but we cannot promise the assistant reads from where we put the -files. GitHub Copilot is marked `(paths not yet verified)` in the report and -carries `"verified": false` in `--json`. Cursor and Windsurf were verified with -reply-cli 0.5.1, Antigravity with the release that adds it. +Codex, Antigravity and GitHub Copilot. A project-scope install aimed at one of +them therefore makes those skills visible in the others, and removing them for +one changes what the others see. Use user scope when you want a host's skills to +itself. GitHub Copilot reaches furthest: it also reads `.claude/skills` and +`.github/skills` in a repository, and `~/.claude/skills` for user scope, so a +Claude Code install shows up in it as well. + +Every assistant in the table has been verified against the version named. A host +added later may arrive with its skills directory taken from the vendor's +documentation alone; until someone confirms it by installing and using a skill, +the report marks that host `(paths not yet verified)` and `--json` carries +`"verified": false`. Cursor and Windsurf were verified with reply-cli 0.5.1, +Antigravity and GitHub Copilot with the release that adds them. + +GitHub Copilot is two products over one directory: the `copilot` CLI and the +VS Code agent host, which ships `~/.copilot/skills` among the defaults of its +`chat.agentSkillsLocations` setting. The CLI is what the row above was verified +against. VS Code scans those directories once per window and does not watch them, +so an install made while it is open needs `Developer: Reload Window` rather than +just a new chat. Antigravity is the one host that needs nothing to pick up new skills — it re-reads them every turn, so an install lands in a conversation you already have diff --git a/src/__tests__/skills/hosts.test.ts b/src/__tests__/skills/hosts.test.ts index 9c1fb76..d0843c5 100644 --- a/src/__tests__/skills/hosts.test.ts +++ b/src/__tests__/skills/hosts.test.ts @@ -16,7 +16,7 @@ describe('host registry', ()=>{ it('marks only the hosts we actually verified', ()=>{ expect(HOSTS.filter(h=>h.verified).map(h=>h.id)) - .toEqual(['claude-code', 'codex', 'antigravity', 'cursor', 'windsurf']); + .toEqual(['claude-code', 'codex', 'antigravity', 'cursor', 'github-copilot', 'windsurf']); }); it('marks only the hosts that pick up skills without a new session', ()=>{ diff --git a/src/__tests__/skills/orchestrate.test.ts b/src/__tests__/skills/orchestrate.test.ts index 0fa4afe..be6749a 100644 --- a/src/__tests__/skills/orchestrate.test.ts +++ b/src/__tests__/skills/orchestrate.test.ts @@ -267,17 +267,10 @@ describe('run_skills', ()=>{ expect(human_lines(report).join('\n')).toMatch(/new session/i); }); - // I5 (final review): some hosts still ship with paths taken from - // documentation rather than a verification run, and one of them is detected - // here alongside the verified hosts so this asserts both halves at once — - // the flag travels from the registry into a real report, and the warning - // still reaches a host that needs it. - // - // Which host that is comes from the registry rather than a name, because two - // PRs in a row had to edit this test after verifying one. When the list - // finally empties, the guard below fails loudly and someone decides what - // this should assert, instead of the assertion quietly passing on nothing. - const unverified = HOSTS.find(h=>!h.verified); + // Asserted with `verified` because it is the only mixed flag now that every + // host is verified, so a hardcoded stamp still fails. + const in_session = HOSTS.find(h=>!h.needs_new_session); + const flags_guard = 'every host needs a new session — decide what this should assert now'; // Derived from which directories exist rather than from a list of ids, in // registry order, because creating one host's config directory can reveal // another whose own directory is an ancestor of it — a fixed list silently @@ -288,21 +281,21 @@ describe('run_skills', ()=>{ HOSTS.filter(h=>h.config_dirs.some(dir=> made.some(m=>m === dir || m.startsWith(`${dir}${path.sep}`)))); - it('carries each host\'s verified flag into the report', async()=>{ - expect(unverified, 'every host is verified — decide what this should assert now').toBeDefined(); - const made = ['.claude', '.cursor', unverified!.config_dirs[0]]; - fs.mkdirSync(path.join(home, unverified!.config_dirs[0]), {recursive: true}); + it('carries each host\'s registry flags into the report', async()=>{ + expect(in_session, flags_guard).toBeDefined(); + const made = ['.claude', '.cursor', in_session!.config_dirs[0]]; + fs.mkdirSync(path.join(home, in_session!.config_dirs[0]), {recursive: true}); const report = await run_skills(opts()); - expect(report.hosts.map(h=>[h.host, h.verified])).toEqual( - detected_defs(made).map(h=>[h.id, h.verified]), + expect(report.hosts.map(h=>[h.host, h.verified, h.needs_new_session])).toEqual( + detected_defs(made).map(h=>[h.id, h.verified, h.needs_new_session]), ); - expect(human_lines(report).join('\n')).toContain('paths not yet verified'); }); - it('carries the verified flag on a host that was requested but not installed', async()=>{ - expect(unverified, 'every host is verified — decide what this should assert now').toBeDefined(); - const report = await run_skills(opts({agents: [unverified!.id]})); - expect(report.hosts[0].verified).toBe(false); + it('carries the registry flags on a host that was requested but not installed', async()=>{ + expect(in_session, flags_guard).toBeDefined(); + const report = await run_skills(opts({agents: [in_session!.id]})); + expect([report.hosts[0].verified, report.hosts[0].needs_new_session]) + .toEqual([in_session!.verified, in_session!.needs_new_session]); }); // Retiring a host leaves its journal branch behind, and nothing else in this diff --git a/src/skills/hosts.ts b/src/skills/hosts.ts index de23859..ee42d95 100644 --- a/src/skills/hosts.ts +++ b/src/skills/hosts.ts @@ -100,9 +100,11 @@ const HOSTS: Host_def[] = [ binaries: [], binary_paths: [], user_skills_dir: path.join('.copilot', 'skills'), + // It also reads .github/skills and .claude/skills for project scope. project_skills_dir: path.join('.agents', 'skills'), + // `/skills reload` picks them up too, without a restart. needs_new_session: true, - verified: false, + verified: true, }, { id: 'windsurf', From 00113a29bb60e42f9b941761b43279a25b3cf0c8 Mon Sep 17 00:00:00 2001 From: CaptainReply Date: Sat, 15 Aug 2026 00:54:47 +0200 Subject: [PATCH 2/2] docs: correct which directories a Copilot install actually shares MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overlap note said a Claude Code install shows up in GitHub Copilot. Not one made by this command: a native host falls back to the flat adapter only when its plugin mechanism cannot express project scope, and Claude Code's can, so both scopes go through its plugin CLI and nothing is written to .claude/skills. Copilot does read that directory, so the crossover is real when something else fills it — a skill copied by hand, or the npx skills channel. Named as such now. Also separates the two products' user directories: ~/.claude/skills is a VS Code default, not one the CLI reads. --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bf734b7..7579496 100644 --- a/README.md +++ b/README.md @@ -280,9 +280,11 @@ also reads `.agents/skills`, which is where `--project` puts things for Cursor, Codex, Antigravity and GitHub Copilot. A project-scope install aimed at one of them therefore makes those skills visible in the others, and removing them for one changes what the others see. Use user scope when you want a host's skills to -itself. GitHub Copilot reaches furthest: it also reads `.claude/skills` and -`.github/skills` in a repository, and `~/.claude/skills` for user scope, so a -Claude Code install shows up in it as well. +itself. GitHub Copilot reads two further repository directories, `.claude/skills` +and `.github/skills`. This command never writes to either — Claude Code installs +through its own plugin CLI in both scopes — so that crossover appears only when +something else fills them: a skill copied there by hand, or the `npx skills` +channel, which writes `.claude/skills` for a project-scoped Claude Code install. Every assistant in the table has been verified against the version named. A host added later may arrive with its skills directory taken from the vendor's @@ -293,8 +295,9 @@ Antigravity and GitHub Copilot with the release that adds them. GitHub Copilot is two products over one directory: the `copilot` CLI and the VS Code agent host, which ships `~/.copilot/skills` among the defaults of its -`chat.agentSkillsLocations` setting. The CLI is what the row above was verified -against. VS Code scans those directories once per window and does not watch them, +`chat.agentSkillsLocations` setting — alongside `~/.claude/skills` and +`~/.agents/skills`, user directories the CLI itself does not read. The CLI is +what the row above was verified against. VS Code scans those directories once per window and does not watch them, so an install made while it is open needs `Developer: Reload Window` rather than just a new chat.