Skip to content

Commit 343d5e9

Browse files
mrubensroomote
andauthored
Add support for skills (#10335)
* Add support for skills * fix: use type-only import for ClineProvider and relative paths in skills section --------- Co-authored-by: Roo Code <roomote@roocode.com>
1 parent a510223 commit 343d5e9

File tree

11 files changed

+1162
-2
lines changed

11 files changed

+1162
-2
lines changed

src/core/prompts/sections/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export { getToolUseGuidelinesSection } from "./tool-use-guidelines"
88
export { getCapabilitiesSection } from "./capabilities"
99
export { getModesSection } from "./modes"
1010
export { markdownFormattingSection } from "./markdown-formatting"
11+
export { getSkillsSection } from "./skills"
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { SkillsManager, SkillMetadata } from "../../../services/skills/SkillsManager"
2+
3+
/**
4+
* Get a display-friendly relative path for a skill.
5+
* Converts absolute paths to relative paths to avoid leaking sensitive filesystem info.
6+
*
7+
* @param skill - The skill metadata
8+
* @returns A relative path like ".roo/skills/name/SKILL.md" or "~/.roo/skills/name/SKILL.md"
9+
*/
10+
function getDisplayPath(skill: SkillMetadata): string {
11+
const basePath = skill.source === "project" ? ".roo" : "~/.roo"
12+
const skillsDir = skill.mode ? `skills-${skill.mode}` : "skills"
13+
return `${basePath}/${skillsDir}/${skill.name}/SKILL.md`
14+
}
15+
16+
/**
17+
* Generate the skills section for the system prompt.
18+
* Only includes skills relevant to the current mode.
19+
* Format matches the modes section style.
20+
*
21+
* @param skillsManager - The SkillsManager instance
22+
* @param currentMode - The current mode slug (e.g., 'code', 'architect')
23+
*/
24+
export async function getSkillsSection(
25+
skillsManager: SkillsManager | undefined,
26+
currentMode: string | undefined,
27+
): Promise<string> {
28+
if (!skillsManager || !currentMode) return ""
29+
30+
// Get skills filtered by current mode (with override resolution)
31+
const skills = skillsManager.getSkillsForMode(currentMode)
32+
if (skills.length === 0) return ""
33+
34+
// Separate generic and mode-specific skills for display
35+
const genericSkills = skills.filter((s) => !s.mode)
36+
const modeSpecificSkills = skills.filter((s) => s.mode === currentMode)
37+
38+
let skillsList = ""
39+
40+
if (modeSpecificSkills.length > 0) {
41+
skillsList += modeSpecificSkills
42+
.map(
43+
(skill) =>
44+
` * "${skill.name}" skill (${currentMode} mode) - ${skill.description} [${getDisplayPath(skill)}]`,
45+
)
46+
.join("\n")
47+
}
48+
49+
if (genericSkills.length > 0) {
50+
if (skillsList) skillsList += "\n"
51+
skillsList += genericSkills
52+
.map((skill) => ` * "${skill.name}" skill - ${skill.description} [${getDisplayPath(skill)}]`)
53+
.join("\n")
54+
}
55+
56+
return `====
57+
58+
AVAILABLE SKILLS
59+
60+
Skills are pre-packaged instructions for specific tasks. When a user request matches a skill description, read the full SKILL.md file to get detailed instructions.
61+
62+
- These are the currently available skills for "${currentMode}" mode:
63+
${skillsList}
64+
65+
To use a skill:
66+
1. Identify which skill matches the user's request based on the description
67+
2. Use read_file to load the full SKILL.md file from the path shown in brackets
68+
3. Follow the instructions in the skill file
69+
4. Access any bundled files (scripts, references, assets) as needed
70+
`
71+
}

src/core/prompts/system.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { isEmpty } from "../../utils/object"
1818

1919
import { McpHub } from "../../services/mcp/McpHub"
2020
import { CodeIndexManager } from "../../services/code-index/manager"
21+
import { SkillsManager } from "../../services/skills/SkillsManager"
2122

2223
import { PromptVariables, loadSystemPromptFile } from "./sections/custom-system-prompt"
2324

@@ -34,6 +35,7 @@ import {
3435
getModesSection,
3536
addCustomInstructions,
3637
markdownFormattingSection,
38+
getSkillsSection,
3739
} from "./sections"
3840

3941
// Helper function to get prompt component, filtering out empty objects
@@ -69,6 +71,7 @@ async function generatePrompt(
6971
settings?: SystemPromptSettings,
7072
todoList?: TodoItem[],
7173
modelId?: string,
74+
skillsManager?: SkillsManager,
7275
): Promise<string> {
7376
if (!context) {
7477
throw new Error("Extension context is required for generating system prompt")
@@ -91,7 +94,7 @@ async function generatePrompt(
9194
// Determine the effective protocol (defaults to 'xml')
9295
const effectiveProtocol = getEffectiveProtocol(settings?.toolProtocol)
9396

94-
const [modesSection, mcpServersSection] = await Promise.all([
97+
const [modesSection, mcpServersSection, skillsSection] = await Promise.all([
9598
getModesSection(context),
9699
shouldIncludeMcp
97100
? getMcpServersSection(
@@ -101,6 +104,7 @@ async function generatePrompt(
101104
!isNativeProtocol(effectiveProtocol),
102105
)
103106
: Promise.resolve(""),
107+
getSkillsSection(skillsManager, mode as string),
104108
])
105109

106110
// Build tools catalog section only for XML protocol
@@ -147,7 +151,7 @@ ${mcpServersSection}
147151
${getCapabilitiesSection(cwd, shouldIncludeMcp ? mcpHub : undefined)}
148152
149153
${modesSection}
150-
154+
${skillsSection ? `\n${skillsSection}` : ""}
151155
${getRulesSection(cwd, settings)}
152156
153157
${getSystemInfoSection(cwd)}
@@ -183,6 +187,7 @@ export const SYSTEM_PROMPT = async (
183187
settings?: SystemPromptSettings,
184188
todoList?: TodoItem[],
185189
modelId?: string,
190+
skillsManager?: SkillsManager,
186191
): Promise<string> => {
187192
if (!context) {
188193
throw new Error("Extension context is required for generating system prompt")
@@ -255,5 +260,6 @@ ${customInstructions}`
255260
settings,
256261
todoList,
257262
modelId,
263+
skillsManager,
258264
)
259265
}

src/core/task/Task.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3538,6 +3538,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
35383538
},
35393539
undefined, // todoList
35403540
this.api.getModel().id,
3541+
provider.getSkillsManager(),
35413542
)
35423543
})()
35433544
}

src/core/task/__tests__/Task.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,7 @@ describe("Cline", () => {
976976
apiConfiguration: mockApiConfig,
977977
}),
978978
getMcpHub: vi.fn().mockReturnValue(undefined),
979+
getSkillsManager: vi.fn().mockReturnValue(undefined),
979980
say: vi.fn(),
980981
postStateToWebview: vi.fn().mockResolvedValue(undefined),
981982
postMessageToWebview: vi.fn().mockResolvedValue(undefined),

src/core/webview/ClineProvider.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import { ShadowCheckpointService } from "../../services/checkpoints/ShadowCheckp
7171
import { CodeIndexManager } from "../../services/code-index/manager"
7272
import type { IndexProgressUpdate } from "../../services/code-index/interfaces/manager"
7373
import { MdmService } from "../../services/mdm/MdmService"
74+
import { SkillsManager } from "../../services/skills/SkillsManager"
7475

7576
import { fileExistsAtPath } from "../../utils/fs"
7677
import { setTtsEnabled, setTtsSpeed } from "../../utils/tts"
@@ -137,6 +138,7 @@ export class ClineProvider
137138
private codeIndexManager?: CodeIndexManager
138139
private _workspaceTracker?: WorkspaceTracker // workSpaceTracker read-only for access outside this class
139140
protected mcpHub?: McpHub // Change from private to protected
141+
protected skillsManager?: SkillsManager
140142
private marketplaceManager: MarketplaceManager
141143
private mdmService?: MdmService
142144
private taskCreationCallback: (task: Task) => void
@@ -197,6 +199,12 @@ export class ClineProvider
197199
this.log(`Failed to initialize MCP Hub: ${error}`)
198200
})
199201

202+
// Initialize Skills Manager for skill discovery
203+
this.skillsManager = new SkillsManager(this)
204+
this.skillsManager.initialize().catch((error) => {
205+
this.log(`Failed to initialize Skills Manager: ${error}`)
206+
})
207+
200208
this.marketplaceManager = new MarketplaceManager(this.context, this.customModesManager)
201209

202210
// Forward <most> task events to the provider.
@@ -603,6 +611,8 @@ export class ClineProvider
603611
this._workspaceTracker = undefined
604612
await this.mcpHub?.unregisterClient()
605613
this.mcpHub = undefined
614+
await this.skillsManager?.dispose()
615+
this.skillsManager = undefined
606616
this.marketplaceManager?.cleanup()
607617
this.customModesManager?.dispose()
608618
this.log("Disposed all disposables")
@@ -2440,6 +2450,10 @@ export class ClineProvider
24402450
return this.mcpHub
24412451
}
24422452

2453+
public getSkillsManager(): SkillsManager | undefined {
2454+
return this.skillsManager
2455+
}
2456+
24432457
/**
24442458
* Check if the current state is compliant with MDM policy
24452459
* @returns true if compliant or no MDM policy exists, false if MDM policy exists and user is non-compliant

src/core/webview/__tests__/generateSystemPrompt.browser-capability.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ function makeProviderStub() {
4949
rooIgnoreController: { getInstructions: () => undefined },
5050
}),
5151
getMcpHub: () => undefined,
52+
getSkillsManager: () => undefined,
5253
// State must enable browser tool and provide apiConfiguration
5354
getState: async () => ({
5455
apiConfiguration: {

src/core/webview/generateSystemPrompt.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
9999
toolProtocol,
100100
isStealthModel: modelInfo?.isStealthModel,
101101
},
102+
undefined, // todoList
103+
undefined, // modelId
104+
provider.getSkillsManager(),
102105
)
103106

104107
return systemPrompt

0 commit comments

Comments
 (0)