Skip to content

Commit a266834

Browse files
roomote[bot]roomote
andauthored
feat: add support for .agents/skills directory (RooCodeInc#11181)
* feat: add support for .agents/skills directory This change adds support for discovering skills from the .agents/skills directory, following the Agent Skills convention for sharing skills across different AI coding tools. Priority order (later entries override earlier ones): 1. Global ~/.agents/skills (shared across AI coding tools, lowest priority) 2. Project .agents/skills 3. Global ~/.roo/skills (Roo-specific) 4. Project .roo/skills (highest priority) Changes: - Add getGlobalAgentsDirectory() and getProjectAgentsDirectoryForCwd() functions to roo-config - Update SkillsManager.getSkillsDirectories() to include .agents/skills - Update SkillsManager.setupFileWatchers() to watch .agents/skills - Add tests for new functionality * fix: clarify skill priority comment to match actual behavior * fix: clarify skill priority comment to explain Map.set replacement mechanism --------- Co-authored-by: Roo Code <roomote@roocode.com>
1 parent 590fef7 commit a266834

4 files changed

Lines changed: 333 additions & 11 deletions

File tree

src/services/roo-config/__tests__/index.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ vi.mock("../../search/file-search", () => ({
2828

2929
import {
3030
getGlobalRooDirectory,
31+
getGlobalAgentsDirectory,
3132
getProjectRooDirectoryForCwd,
33+
getProjectAgentsDirectoryForCwd,
3234
directoryExists,
3335
fileExists,
3436
readFileIfExists,
@@ -70,6 +72,27 @@ describe("RooConfigService", () => {
7072
})
7173
})
7274

75+
describe("getGlobalAgentsDirectory", () => {
76+
it("should return correct path for global .agents directory", () => {
77+
const result = getGlobalAgentsDirectory()
78+
expect(result).toBe(path.join("/mock/home", ".agents"))
79+
})
80+
81+
it("should handle different home directories", () => {
82+
mockHomedir.mockReturnValue("/different/home")
83+
const result = getGlobalAgentsDirectory()
84+
expect(result).toBe(path.join("/different/home", ".agents"))
85+
})
86+
})
87+
88+
describe("getProjectAgentsDirectoryForCwd", () => {
89+
it("should return correct path for given cwd", () => {
90+
const cwd = "/custom/project/path"
91+
const result = getProjectAgentsDirectoryForCwd(cwd)
92+
expect(result).toBe(path.join(cwd, ".agents"))
93+
})
94+
})
95+
7396
describe("directoryExists", () => {
7497
it("should return true for existing directory", async () => {
7598
mockStat.mockResolvedValue({ isDirectory: () => true } as any)

src/services/roo-config/index.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,50 @@ export function getGlobalRooDirectory(): string {
2828
return path.join(homeDir, ".roo")
2929
}
3030

31+
/**
32+
* Gets the global .agents directory path based on the current platform.
33+
* This is a shared directory for agent skills across different AI coding tools.
34+
*
35+
* @returns The absolute path to the global .agents directory
36+
*
37+
* @example Platform-specific paths:
38+
* ```
39+
* // macOS/Linux: ~/.agents/
40+
* // Example: /Users/john/.agents
41+
*
42+
* // Windows: %USERPROFILE%\.agents\
43+
* // Example: C:\Users\john\.agents
44+
* ```
45+
*
46+
* @example Usage:
47+
* ```typescript
48+
* const globalAgentsDir = getGlobalAgentsDirectory()
49+
* // Returns: "/Users/john/.agents" (on macOS/Linux)
50+
* // Returns: "C:\\Users\\john\\.agents" (on Windows)
51+
* ```
52+
*/
53+
export function getGlobalAgentsDirectory(): string {
54+
const homeDir = os.homedir()
55+
return path.join(homeDir, ".agents")
56+
}
57+
58+
/**
59+
* Gets the project-local .agents directory path for a given cwd.
60+
* This is a shared directory for agent skills across different AI coding tools.
61+
*
62+
* @param cwd - Current working directory (project path)
63+
* @returns The absolute path to the project-local .agents directory
64+
*
65+
* @example
66+
* ```typescript
67+
* const projectAgentsDir = getProjectAgentsDirectoryForCwd('/Users/john/my-project')
68+
* // Returns: "/Users/john/my-project/.agents"
69+
* ```
70+
*/
71+
export function getProjectAgentsDirectoryForCwd(cwd: string): string {
72+
return path.join(cwd, ".agents")
73+
}
74+
3175
/**
3276
* Gets the project-local .roo directory path for a given cwd
3377
*

src/services/skills/SkillsManager.ts

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as vscode from "vscode"
55
import matter from "gray-matter"
66

77
import type { ClineProvider } from "../../core/webview/ClineProvider"
8-
import { getGlobalRooDirectory } from "../roo-config"
8+
import { getGlobalRooDirectory, getGlobalAgentsDirectory, getProjectAgentsDirectoryForCwd } from "../roo-config"
99
import { directoryExists, fileExists } from "../roo-config"
1010
import { SkillMetadata, SkillContent } from "../../shared/skills"
1111
import { modes, getAllModes } from "../../shared/modes"
@@ -590,19 +590,44 @@ Add your skill instructions here.
590590
> {
591591
const dirs: Array<{ dir: string; source: "global" | "project"; mode?: string }> = []
592592
const globalRooDir = getGlobalRooDirectory()
593+
const globalAgentsDir = getGlobalAgentsDirectory()
593594
const provider = this.providerRef.deref()
594595
const projectRooDir = provider?.cwd ? path.join(provider.cwd, ".roo") : null
596+
const projectAgentsDir = provider?.cwd ? getProjectAgentsDirectoryForCwd(provider.cwd) : null
595597

596598
// Get list of modes to check for mode-specific skills
597599
const modesList = await this.getAvailableModes()
598600

599-
// Global directories
601+
// Priority rules for skills with the same name:
602+
// 1. Source level: project > global > built-in (handled by shouldOverrideSkill in getSkillsForMode)
603+
// 2. Within the same source level: later-processed directories override earlier ones
604+
// (via Map.set replacement during discovery - same source+mode+name key gets replaced)
605+
//
606+
// Processing order (later directories override earlier ones at the same source level):
607+
// - Global: .agents/skills first, then .roo/skills (so .roo wins)
608+
// - Project: .agents/skills first, then .roo/skills (so .roo wins)
609+
610+
// Global .agents directories (lowest priority - shared across agents)
611+
dirs.push({ dir: path.join(globalAgentsDir, "skills"), source: "global" })
612+
for (const mode of modesList) {
613+
dirs.push({ dir: path.join(globalAgentsDir, `skills-${mode}`), source: "global", mode })
614+
}
615+
616+
// Project .agents directories
617+
if (projectAgentsDir) {
618+
dirs.push({ dir: path.join(projectAgentsDir, "skills"), source: "project" })
619+
for (const mode of modesList) {
620+
dirs.push({ dir: path.join(projectAgentsDir, `skills-${mode}`), source: "project", mode })
621+
}
622+
}
623+
624+
// Global .roo directories (Roo-specific, higher priority than .agents)
600625
dirs.push({ dir: path.join(globalRooDir, "skills"), source: "global" })
601626
for (const mode of modesList) {
602627
dirs.push({ dir: path.join(globalRooDir, `skills-${mode}`), source: "global", mode })
603628
}
604629

605-
// Project directories
630+
// Project .roo directories (highest priority)
606631
if (projectRooDir) {
607632
dirs.push({ dir: path.join(projectRooDir, "skills"), source: "project" })
608633
for (const mode of modesList) {
@@ -647,20 +672,32 @@ Add your skill instructions here.
647672
if (!provider?.cwd) return
648673

649674
// Watch for changes in skills directories
650-
const globalSkillsDir = path.join(getGlobalRooDirectory(), "skills")
651-
const projectSkillsDir = path.join(provider.cwd, ".roo", "skills")
675+
const globalRooDir = getGlobalRooDirectory()
676+
const globalAgentsDir = getGlobalAgentsDirectory()
677+
const projectRooDir = path.join(provider.cwd, ".roo")
678+
const projectAgentsDir = getProjectAgentsDirectoryForCwd(provider.cwd)
679+
680+
// Watch global .roo skills directory
681+
this.watchDirectory(path.join(globalRooDir, "skills"))
682+
683+
// Watch global .agents skills directory
684+
this.watchDirectory(path.join(globalAgentsDir, "skills"))
652685

653-
// Watch global skills directory
654-
this.watchDirectory(globalSkillsDir)
686+
// Watch project .roo skills directory
687+
this.watchDirectory(path.join(projectRooDir, "skills"))
655688

656-
// Watch project skills directory
657-
this.watchDirectory(projectSkillsDir)
689+
// Watch project .agents skills directory
690+
this.watchDirectory(path.join(projectAgentsDir, "skills"))
658691

659692
// Watch mode-specific directories for all available modes
660693
const modesList = await this.getAvailableModes()
661694
for (const mode of modesList) {
662-
this.watchDirectory(path.join(getGlobalRooDirectory(), `skills-${mode}`))
663-
this.watchDirectory(path.join(provider.cwd, ".roo", `skills-${mode}`))
695+
// .roo mode-specific
696+
this.watchDirectory(path.join(globalRooDir, `skills-${mode}`))
697+
this.watchDirectory(path.join(projectRooDir, `skills-${mode}`))
698+
// .agents mode-specific
699+
this.watchDirectory(path.join(globalAgentsDir, `skills-${mode}`))
700+
this.watchDirectory(path.join(projectAgentsDir, `skills-${mode}`))
664701
}
665702
}
666703

0 commit comments

Comments
 (0)