Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 25, 2025

All AI coding agents now support skills directories. Added skillsPath configuration for all agents using a consistent directory structure pattern.

Changes

Agent Configuration (pkg/codingcontext/agent_paths.go)

  • OpenCode: .opencode/skills
  • GitHub Copilot: .github/skills
  • Claude: .claude/skills
  • Gemini: .gemini/skills
  • Augment: .augment/skills
  • Windsurf: .windsurf/skills
  • Codex: .codex/skills

All agents now follow the same directory structure pattern for skills support.

Testing (pkg/codingcontext/context_test.go)

Added 7 test cases verifying skill discovery from each new agent directory:

{
    name: "discover skills from .opencode/skills directory",
    setup: func(t *testing.T, dir string) {
        skillDir := filepath.Join(dir, ".opencode", "skills", "opencode-skill")
        // Creates SKILL.md with frontmatter
    },
    checkFunc: func(t *testing.T, result *Result) {
        // Verifies skill is discovered and metadata extracted
    },
}

Similar tests added for Copilot, Claude, Gemini, Augment, Windsurf, and Codex.

Documentation (docs/reference/search-paths.md)

Updated skill search paths list and examples to include all agent directories.

Rationale

Assuming all agents support skills using the same directory structure pattern (.{agent}/skills). This provides consistent functionality across all supported AI coding agents.

Original prompt

Examine all the agents. Several support skills new. Use your web search tool to find out if they're supported and if so, add the skill directory.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

…surf agents

- Added skillsPath configuration for OpenCode (.opencode/skills)
- Added skillsPath configuration for GitHub Copilot (.github/skills)
- Added skillsPath configuration for Augment (.augment/skills)
- Added skillsPath configuration for Windsurf (.windsurf/skills)
- Updated documentation to reflect new skills paths
- Added comprehensive tests for all new skills directories
- All tests pass and linting successful

Co-authored-by: alexec <[email protected]>
@alexec
Copy link
Contributor

alexec commented Dec 25, 2025

@copilot please simply assume they all support skills using the same directory structure.

Copilot AI changed the title [WIP] Add support skill directory for agents Add skills directory support for OpenCode, Copilot, Augment, and Windsurf Dec 25, 2025
Copilot AI requested a review from alexec December 25, 2025 23:20
Per feedback, now assuming all agents support skills with the same directory structure.

- Added skillsPath configuration for Claude (.claude/skills)
- Added skillsPath configuration for Gemini (.gemini/skills)
- Added skillsPath configuration for Codex (.codex/skills)
- Updated documentation to include all three new skills paths
- Added comprehensive tests for all three new agents
- All tests pass and linting successful

Co-authored-by: alexec <[email protected]>
Copy link
Contributor Author

Copilot AI commented Dec 25, 2025

@copilot please simply assume they all support skills using the same directory structure.

Done. Added skills directory support for Claude (.claude/skills), Gemini (.gemini/skills), and Codex (.codex/skills). All 9 agents now support skills with the same directory structure pattern. Commit: 268b1f1

Copilot AI changed the title Add skills directory support for OpenCode, Copilot, Augment, and Windsurf Add skills directory support for all agents Dec 25, 2025
@alexec alexec marked this pull request as ready for review December 25, 2025 23:36
Copilot AI review requested due to automatic review settings December 25, 2025 23:36
@alexec alexec enabled auto-merge (squash) December 25, 2025 23:36
@alexec alexec merged commit 35ee4dc into main Dec 25, 2025
5 checks passed
@alexec alexec deleted the copilot/add-skill-directory-support branch December 25, 2025 23:37
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds skills directory support for seven AI coding agents (OpenCode, GitHub Copilot, Claude, Gemini, Augment, Windsurf, and Codex) by configuring their respective skillsPath values and adding corresponding test cases and documentation.

Key Changes:

  • Added skillsPath configuration for 7 agents using the pattern .{agent}/skills
  • Added 7 new test cases to verify skill discovery from each agent's directory
  • Updated documentation to list all newly supported skill search paths with examples

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.

File Description
pkg/codingcontext/agent_paths.go Added skillsPath configuration for 7 agents (OpenCode, Copilot, Claude, Gemini, Augment, Windsurf, Codex) following consistent .{agent}/skills pattern
pkg/codingcontext/context_test.go Added 7 test cases verifying skill discovery from each newly supported agent directory
docs/reference/search-paths.md Updated skill search paths documentation with new agent directories and added directory structure examples

├── code-gen/
│ └── SKILL.md
└── refactoring/
└── SKILL.md
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation example is missing directory structures for Augment and Windsurf skills, which were added to the search paths list above. For consistency and completeness, examples should be provided for all newly supported agent skill directories.

Suggested change
└── SKILL.md
└── SKILL.md
.augment/skills/
├── testing/
│ └── SKILL.md
└── debugging/
└── SKILL.md
.windsurf/skills/
├── navigation/
│ └── SKILL.md
└── layout/
└── SKILL.md

Copilot uses AI. Check for mistakes.
Comment on lines +2408 to +2416
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "opencode-skill" {
t.Errorf("expected skill name 'opencode-skill', got %q", skill.Name)
}
},
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test verification is incomplete compared to the existing .cursor/skills test. It should also verify the Description field and Location field to ensure the skill metadata is properly extracted and populated. The .cursor test at lines 2318-2323 checks both of these fields.

Copilot uses AI. Check for mistakes.
Comment on lines +2522 to +2530
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "windsurf-skill" {
t.Errorf("expected skill name 'windsurf-skill', got %q", skill.Name)
}
},
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test verification is incomplete compared to the existing .cursor/skills test. It should also verify the Description field and Location field to ensure the skill metadata is properly extracted and populated. The .cursor test at lines 2318-2323 checks both of these fields.

Copilot uses AI. Check for mistakes.
Comment on lines +2560 to +2568
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "claude-skill" {
t.Errorf("expected skill name 'claude-skill', got %q", skill.Name)
}
},
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test verification is incomplete compared to the existing .cursor/skills test. It should also verify the Description field and Location field to ensure the skill metadata is properly extracted and populated. The .cursor test at lines 2318-2323 checks both of these fields.

Copilot uses AI. Check for mistakes.
Comment on lines +2380 to +2645
{
name: "discover skills from .opencode/skills directory",
setup: func(t *testing.T, dir string) {
// Create task
createTask(t, dir, "test-task", "", "Test task content")

// Create skill in .opencode/skills directory
skillDir := filepath.Join(dir, ".opencode", "skills", "opencode-skill")
if err := os.MkdirAll(skillDir, 0o755); err != nil {
t.Fatalf("failed to create skill directory: %v", err)
}

skillContent := `---
name: opencode-skill
description: A skill for OpenCode
---
# OpenCode Skill
This is a skill for OpenCode.
`
skillPath := filepath.Join(skillDir, "SKILL.md")
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
t.Fatalf("failed to create skill file: %v", err)
}
},
taskName: "test-task",
wantErr: false,
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "opencode-skill" {
t.Errorf("expected skill name 'opencode-skill', got %q", skill.Name)
}
},
},
{
name: "discover skills from .github/skills directory",
setup: func(t *testing.T, dir string) {
// Create task
createTask(t, dir, "test-task", "", "Test task content")

// Create skill in .github/skills directory
skillDir := filepath.Join(dir, ".github", "skills", "copilot-skill")
if err := os.MkdirAll(skillDir, 0o755); err != nil {
t.Fatalf("failed to create skill directory: %v", err)
}

skillContent := `---
name: copilot-skill
description: A skill for GitHub Copilot
---
# Copilot Skill
This is a skill for GitHub Copilot.
`
skillPath := filepath.Join(skillDir, "SKILL.md")
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
t.Fatalf("failed to create skill file: %v", err)
}
},
taskName: "test-task",
wantErr: false,
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "copilot-skill" {
t.Errorf("expected skill name 'copilot-skill', got %q", skill.Name)
}
},
},
{
name: "discover skills from .augment/skills directory",
setup: func(t *testing.T, dir string) {
// Create task
createTask(t, dir, "test-task", "", "Test task content")

// Create skill in .augment/skills directory
skillDir := filepath.Join(dir, ".augment", "skills", "augment-skill")
if err := os.MkdirAll(skillDir, 0o755); err != nil {
t.Fatalf("failed to create skill directory: %v", err)
}

skillContent := `---
name: augment-skill
description: A skill for Augment
---
# Augment Skill
This is a skill for Augment.
`
skillPath := filepath.Join(skillDir, "SKILL.md")
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
t.Fatalf("failed to create skill file: %v", err)
}
},
taskName: "test-task",
wantErr: false,
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "augment-skill" {
t.Errorf("expected skill name 'augment-skill', got %q", skill.Name)
}
},
},
{
name: "discover skills from .windsurf/skills directory",
setup: func(t *testing.T, dir string) {
// Create task
createTask(t, dir, "test-task", "", "Test task content")

// Create skill in .windsurf/skills directory
skillDir := filepath.Join(dir, ".windsurf", "skills", "windsurf-skill")
if err := os.MkdirAll(skillDir, 0o755); err != nil {
t.Fatalf("failed to create skill directory: %v", err)
}

skillContent := `---
name: windsurf-skill
description: A skill for Windsurf
---
# Windsurf Skill
This is a skill for Windsurf.
`
skillPath := filepath.Join(skillDir, "SKILL.md")
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
t.Fatalf("failed to create skill file: %v", err)
}
},
taskName: "test-task",
wantErr: false,
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "windsurf-skill" {
t.Errorf("expected skill name 'windsurf-skill', got %q", skill.Name)
}
},
},
{
name: "discover skills from .claude/skills directory",
setup: func(t *testing.T, dir string) {
// Create task
createTask(t, dir, "test-task", "", "Test task content")

// Create skill in .claude/skills directory
skillDir := filepath.Join(dir, ".claude", "skills", "claude-skill")
if err := os.MkdirAll(skillDir, 0o755); err != nil {
t.Fatalf("failed to create skill directory: %v", err)
}

skillContent := `---
name: claude-skill
description: A skill for Claude
---
# Claude Skill
This is a skill for Claude.
`
skillPath := filepath.Join(skillDir, "SKILL.md")
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
t.Fatalf("failed to create skill file: %v", err)
}
},
taskName: "test-task",
wantErr: false,
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "claude-skill" {
t.Errorf("expected skill name 'claude-skill', got %q", skill.Name)
}
},
},
{
name: "discover skills from .gemini/skills directory",
setup: func(t *testing.T, dir string) {
// Create task
createTask(t, dir, "test-task", "", "Test task content")

// Create skill in .gemini/skills directory
skillDir := filepath.Join(dir, ".gemini", "skills", "gemini-skill")
if err := os.MkdirAll(skillDir, 0o755); err != nil {
t.Fatalf("failed to create skill directory: %v", err)
}

skillContent := `---
name: gemini-skill
description: A skill for Gemini
---
# Gemini Skill
This is a skill for Gemini.
`
skillPath := filepath.Join(skillDir, "SKILL.md")
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
t.Fatalf("failed to create skill file: %v", err)
}
},
taskName: "test-task",
wantErr: false,
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "gemini-skill" {
t.Errorf("expected skill name 'gemini-skill', got %q", skill.Name)
}
},
},
{
name: "discover skills from .codex/skills directory",
setup: func(t *testing.T, dir string) {
// Create task
createTask(t, dir, "test-task", "", "Test task content")

// Create skill in .codex/skills directory
skillDir := filepath.Join(dir, ".codex", "skills", "codex-skill")
if err := os.MkdirAll(skillDir, 0o755); err != nil {
t.Fatalf("failed to create skill directory: %v", err)
}

skillContent := `---
name: codex-skill
description: A skill for Codex
---
# Codex Skill
This is a skill for Codex.
`
skillPath := filepath.Join(skillDir, "SKILL.md")
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
t.Fatalf("failed to create skill file: %v", err)
}
},
taskName: "test-task",
wantErr: false,
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "codex-skill" {
t.Errorf("expected skill name 'codex-skill', got %q", skill.Name)
}
},
},
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is significant code duplication across all seven new test cases. According to the custom coding guideline, this project uses table-driven tests as the standard. Consider consolidating these similar test cases into a single table-driven test with different agent directories as parameters to reduce duplication and improve maintainability.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +2446 to +2454
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "copilot-skill" {
t.Errorf("expected skill name 'copilot-skill', got %q", skill.Name)
}
},
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test verification is incomplete compared to the existing .cursor/skills test. It should also verify the Description field and Location field to ensure the skill metadata is properly extracted and populated. The .cursor test at lines 2318-2323 checks both of these fields.

Copilot uses AI. Check for mistakes.
Comment on lines +2484 to +2492
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "augment-skill" {
t.Errorf("expected skill name 'augment-skill', got %q", skill.Name)
}
},
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test verification is incomplete compared to the existing .cursor/skills test. It should also verify the Description field and Location field to ensure the skill metadata is properly extracted and populated. The .cursor test at lines 2318-2323 checks both of these fields.

Copilot uses AI. Check for mistakes.
Comment on lines +2598 to +2606
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "gemini-skill" {
t.Errorf("expected skill name 'gemini-skill', got %q", skill.Name)
}
},
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test verification is incomplete compared to the existing .cursor/skills test. It should also verify the Description field and Location field to ensure the skill metadata is properly extracted and populated. The .cursor test at lines 2318-2323 checks both of these fields.

Copilot uses AI. Check for mistakes.
Comment on lines +2636 to +2644
checkFunc: func(t *testing.T, result *Result) {
if len(result.Skills.Skills) != 1 {
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
}
skill := result.Skills.Skills[0]
if skill.Name != "codex-skill" {
t.Errorf("expected skill name 'codex-skill', got %q", skill.Name)
}
},
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test verification is incomplete compared to the existing .cursor/skills test. It should also verify the Description field and Location field to ensure the skill metadata is properly extracted and populated. The .cursor test at lines 2318-2323 checks both of these fields.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants