diff --git a/server/internal/daemon/execenv/context.go b/server/internal/daemon/execenv/context.go index 68d131ca5..442c00788 100644 --- a/server/internal/daemon/execenv/context.go +++ b/server/internal/daemon/execenv/context.go @@ -13,7 +13,7 @@ import ( // // Claude: skills → {workDir}/.claude/skills/{name}/SKILL.md (native discovery) // Codex: skills → handled separately in Prepare via codex-home -// Copilot: skills → {workDir}/.agent_context/skills/{name}/SKILL.md (via AGENTS.md references) +// Copilot: skills → {workDir}/.github/skills/{name}/SKILL.md (native project-level discovery) // OpenCode: skills → {workDir}/.config/opencode/skills/{name}/SKILL.md (native discovery) // Pi: skills → {workDir}/.pi/agent/skills/{name}/SKILL.md (native discovery) // Cursor: skills → {workDir}/.cursor/skills/{name}/SKILL.md (native discovery) @@ -54,6 +54,12 @@ func resolveSkillsDir(workDir, provider string) (string, error) { case "claude": // Claude Code natively discovers skills from .claude/skills/ in the workdir. skillsDir = filepath.Join(workDir, ".claude", "skills") + case "copilot": + // GitHub Copilot CLI natively discovers project-level skills from + // .github/skills//SKILL.md (takes precedence over user-level + // skills in ~/.copilot/skills/). + // See: https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference + skillsDir = filepath.Join(workDir, ".github", "skills") case "opencode": // OpenCode natively discovers skills from .config/opencode/skills/ in the workdir. skillsDir = filepath.Join(workDir, ".config", "opencode", "skills") diff --git a/server/internal/daemon/execenv/execenv_test.go b/server/internal/daemon/execenv/execenv_test.go index e7c4f1b08..e72ef16a7 100644 --- a/server/internal/daemon/execenv/execenv_test.go +++ b/server/internal/daemon/execenv/execenv_test.go @@ -479,6 +479,56 @@ func TestInjectRuntimeConfigNoSkills(t *testing.T) { } } +func TestWriteContextFilesCopilotNativeSkills(t *testing.T) { + t.Parallel() + dir := t.TempDir() + + ctx := TaskContextForEnv{ + IssueID: "copilot-skill-test", + AgentSkills: []SkillContextForEnv{ + { + Name: "Go Conventions", + Content: "Follow Go conventions.", + Files: []SkillFileContextForEnv{ + {Path: "templates/example.go", Content: "package main"}, + }, + }, + }, + } + + if err := writeContextFiles(dir, "copilot", ctx); err != nil { + t.Fatalf("writeContextFiles failed: %v", err) + } + + // Copilot CLI natively discovers project-level skills from .github/skills/. + skillMd, err := os.ReadFile(filepath.Join(dir, ".github", "skills", "go-conventions", "SKILL.md")) + if err != nil { + t.Fatalf("failed to read .github/skills/go-conventions/SKILL.md: %v", err) + } + if !strings.Contains(string(skillMd), "Follow Go conventions.") { + t.Error("SKILL.md missing content") + } + + // Supporting files should also be under .github/skills/. + supportFile, err := os.ReadFile(filepath.Join(dir, ".github", "skills", "go-conventions", "templates", "example.go")) + if err != nil { + t.Fatalf("failed to read supporting file: %v", err) + } + if string(supportFile) != "package main" { + t.Errorf("supporting file content = %q, want %q", string(supportFile), "package main") + } + + // .agent_context/skills/ should NOT exist for Copilot. + if _, err := os.Stat(filepath.Join(dir, ".agent_context", "skills")); !os.IsNotExist(err) { + t.Error("expected .agent_context/skills/ to NOT exist for Copilot provider") + } + + // issue_context.md should still be in .agent_context/. + if _, err := os.Stat(filepath.Join(dir, ".agent_context", "issue_context.md")); os.IsNotExist(err) { + t.Error("expected .agent_context/issue_context.md to exist") + } +} + func TestWriteContextFilesOpencodeNativeSkills(t *testing.T) { t.Parallel() dir := t.TempDir() diff --git a/server/internal/daemon/execenv/runtime_config.go b/server/internal/daemon/execenv/runtime_config.go index 5c000b97a..de404e6f3 100644 --- a/server/internal/daemon/execenv/runtime_config.go +++ b/server/internal/daemon/execenv/runtime_config.go @@ -12,7 +12,7 @@ import ( // // For Claude: writes {workDir}/CLAUDE.md (skills discovered natively from .claude/skills/) // For Codex: writes {workDir}/AGENTS.md (skills discovered natively via CODEX_HOME) -// For Copilot: writes {workDir}/AGENTS.md (Copilot CLI natively reads AGENTS.md) +// For Copilot: writes {workDir}/AGENTS.md (skills discovered natively from .github/skills/) // For OpenCode: writes {workDir}/AGENTS.md (skills discovered natively from .config/opencode/skills/) // For OpenClaw: writes {workDir}/AGENTS.md (skills discovered natively from .openclaw/skills/) // For Gemini: writes {workDir}/GEMINI.md (discovered natively by the Gemini CLI)