Quellcode durchsuchen

refactor: replace fetch_instructions with skill tool and built-in skills (#11084)

Co-authored-by: Roo Code <[email protected]>
Hannes Rudolph vor 2 Wochen
Ursprung
Commit
f848795775
80 geänderte Dateien mit 1938 neuen und 636 gelöschten Zeilen
  1. 1 9
      apps/cli/src/ui/components/tools/types.ts
  2. 2 4
      apps/cli/src/ui/components/tools/utils.ts
  3. 0 1
      packages/types/src/global-settings.ts
  4. 2 2
      packages/types/src/skills.ts
  5. 1 1
      packages/types/src/tool.ts
  6. 4 4
      packages/types/src/vscode-extension-host.ts
  7. 18 16
      src/core/assistant-message/NativeToolCallParser.ts
  8. 10 11
      src/core/assistant-message/presentAssistantMessage.ts
  9. 5 8
      src/core/auto-approval/index.ts
  10. 0 4
      src/core/prompts/__tests__/add-custom-instructions.spec.ts
  11. 0 3
      src/core/prompts/__tests__/custom-system-prompt.spec.ts
  12. 2 13
      src/core/prompts/__tests__/system-prompt.spec.ts
  13. 0 62
      src/core/prompts/instructions/create-mode.ts
  14. 0 25
      src/core/prompts/instructions/instructions.ts
  15. 2 18
      src/core/prompts/sections/modes.ts
  16. 12 11
      src/core/prompts/sections/skills.ts
  17. 0 3
      src/core/prompts/system.ts
  18. 0 26
      src/core/prompts/tools/native-tools/fetch_instructions.ts
  19. 2 2
      src/core/prompts/tools/native-tools/index.ts
  20. 33 0
      src/core/prompts/tools/native-tools/skill.ts
  21. 0 2
      src/core/task/Task.ts
  22. 0 75
      src/core/tools/FetchInstructionsTool.ts
  23. 112 0
      src/core/tools/SkillTool.ts
  24. 345 0
      src/core/tools/__tests__/skillTool.spec.ts
  25. 0 3
      src/core/webview/ClineProvider.ts
  26. 1 36
      src/core/webview/__tests__/ClineProvider.spec.ts
  27. 0 1
      src/core/webview/__tests__/generateSystemPrompt.browser-capability.spec.ts
  28. 0 2
      src/core/webview/generateSystemPrompt.ts
  29. 15 0
      src/core/webview/skillsMessageHandler.ts
  30. 3 8
      src/core/webview/webviewMessageHandler.ts
  31. 3 1
      src/i18n/locales/ca/skills.json
  32. 3 1
      src/i18n/locales/de/skills.json
  33. 3 1
      src/i18n/locales/en/skills.json
  34. 3 1
      src/i18n/locales/es/skills.json
  35. 3 1
      src/i18n/locales/fr/skills.json
  36. 3 1
      src/i18n/locales/hi/skills.json
  37. 3 1
      src/i18n/locales/id/skills.json
  38. 3 1
      src/i18n/locales/it/skills.json
  39. 3 1
      src/i18n/locales/ja/skills.json
  40. 3 1
      src/i18n/locales/ko/skills.json
  41. 3 1
      src/i18n/locales/nl/skills.json
  42. 3 1
      src/i18n/locales/pl/skills.json
  43. 3 1
      src/i18n/locales/pt-BR/skills.json
  44. 3 1
      src/i18n/locales/ru/skills.json
  45. 3 1
      src/i18n/locales/tr/skills.json
  46. 3 1
      src/i18n/locales/vi/skills.json
  47. 3 1
      src/i18n/locales/zh-CN/skills.json
  48. 3 1
      src/i18n/locales/zh-TW/skills.json
  49. 2 0
      src/package.json
  50. 32 6
      src/services/skills/SkillsManager.ts
  51. 8 0
      src/services/skills/__tests__/SkillsManager.spec.ts
  52. 175 0
      src/services/skills/__tests__/generate-built-in-skills.spec.ts
  53. 421 0
      src/services/skills/built-in-skills.ts
  54. 172 189
      src/services/skills/built-in/create-mcp-server/SKILL.md
  55. 57 0
      src/services/skills/built-in/create-mode/SKILL.md
  56. 300 0
      src/services/skills/generate-built-in-skills.ts
  57. 2 2
      src/shared/skills.ts
  58. 10 8
      src/shared/tools.ts
  59. 62 11
      webview-ui/src/components/chat/ChatRow.tsx
  60. 2 44
      webview-ui/src/components/mcp/McpView.tsx
  61. 0 5
      webview-ui/src/context/ExtensionStateContext.tsx
  62. 0 1
      webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx
  63. 4 0
      webview-ui/src/i18n/locales/ca/chat.json
  64. 4 0
      webview-ui/src/i18n/locales/de/chat.json
  65. 3 2
      webview-ui/src/i18n/locales/en/chat.json
  66. 4 0
      webview-ui/src/i18n/locales/es/chat.json
  67. 4 0
      webview-ui/src/i18n/locales/fr/chat.json
  68. 4 0
      webview-ui/src/i18n/locales/hi/chat.json
  69. 4 0
      webview-ui/src/i18n/locales/id/chat.json
  70. 4 0
      webview-ui/src/i18n/locales/it/chat.json
  71. 4 0
      webview-ui/src/i18n/locales/ja/chat.json
  72. 4 0
      webview-ui/src/i18n/locales/ko/chat.json
  73. 4 0
      webview-ui/src/i18n/locales/nl/chat.json
  74. 4 0
      webview-ui/src/i18n/locales/pl/chat.json
  75. 4 0
      webview-ui/src/i18n/locales/pt-BR/chat.json
  76. 4 0
      webview-ui/src/i18n/locales/ru/chat.json
  77. 4 0
      webview-ui/src/i18n/locales/tr/chat.json
  78. 4 0
      webview-ui/src/i18n/locales/vi/chat.json
  79. 4 0
      webview-ui/src/i18n/locales/zh-CN/chat.json
  80. 4 0
      webview-ui/src/i18n/locales/zh-TW/chat.json

+ 1 - 9
apps/cli/src/ui/components/tools/types.ts

@@ -16,15 +16,7 @@ export type ToolCategory =
 	| "other"
 
 export function getToolCategory(toolName: string): ToolCategory {
-	const fileReadTools = [
-		"readFile",
-		"read_file",
-		"fetchInstructions",
-		"fetch_instructions",
-		"listFilesTopLevel",
-		"listFilesRecursive",
-		"list_files",
-	]
+	const fileReadTools = ["readFile", "read_file", "skill", "listFilesTopLevel", "listFilesRecursive", "list_files"]
 
 	const fileWriteTools = [
 		"editedExistingFile",

+ 2 - 4
apps/cli/src/ui/components/tools/utils.ts

@@ -50,8 +50,7 @@ export function getToolDisplayName(toolName: string): string {
 		// File read operations
 		readFile: "Read",
 		read_file: "Read",
-		fetchInstructions: "Fetch Instructions",
-		fetch_instructions: "Fetch Instructions",
+		skill: "Load Skill",
 		listFilesTopLevel: "List Files",
 		listFilesRecursive: "List Files (Recursive)",
 		list_files: "List Files",
@@ -107,8 +106,7 @@ export function getToolIconName(toolName: string): IconName {
 		// File read operations
 		readFile: "file",
 		read_file: "file",
-		fetchInstructions: "file",
-		fetch_instructions: "file",
+		skill: "file",
 		listFilesTopLevel: "folder",
 		listFilesRecursive: "folder",
 		list_files: "folder",

+ 0 - 1
packages/types/src/global-settings.ts

@@ -199,7 +199,6 @@ export const globalSettingsSchema = z.object({
 	telemetrySetting: telemetrySettingsSchema.optional(),
 
 	mcpEnabled: z.boolean().optional(),
-	enableMcpServerCreation: z.boolean().optional(),
 
 	mode: z.string().optional(),
 	modeApiConfigs: z.record(z.string(), z.string()).optional(),

+ 2 - 2
packages/types/src/skills.ts

@@ -5,8 +5,8 @@
 export interface SkillMetadata {
 	name: string // Required: skill identifier
 	description: string // Required: when to use this skill
-	path: string // Absolute path to SKILL.md
-	source: "global" | "project" // Where the skill was discovered
+	path: string // Absolute path to SKILL.md (or "<built-in:name>" for built-in skills)
+	source: "global" | "project" | "built-in" // Where the skill was discovered
 	mode?: string // If set, skill is only available in this mode
 }
 

+ 1 - 1
packages/types/src/tool.ts

@@ -33,10 +33,10 @@ export const toolNames = [
 	"attempt_completion",
 	"switch_mode",
 	"new_task",
-	"fetch_instructions",
 	"codebase_search",
 	"update_todo_list",
 	"run_slash_command",
+	"skill",
 	"generate_image",
 	"custom_tool",
 ] as const

+ 4 - 4
packages/types/src/vscode-extension-host.ts

@@ -362,7 +362,6 @@ export type ExtensionState = Pick<
 	experiments: Experiments // Map of experiment IDs to their enabled state
 
 	mcpEnabled: boolean
-	enableMcpServerCreation: boolean
 
 	mode: string
 	customModes: ModeConfig[]
@@ -502,7 +501,6 @@ export interface WebviewMessage {
 		| "deleteMessageConfirm"
 		| "submitEditedMessage"
 		| "editMessageConfirm"
-		| "enableMcpServerCreation"
 		| "remoteControlEnabled"
 		| "taskSyncEnabled"
 		| "searchCommits"
@@ -643,7 +641,7 @@ export interface WebviewMessage {
 	modeConfig?: ModeConfig
 	timeout?: number
 	payload?: WebViewMessagePayload
-	source?: "global" | "project"
+	source?: "global" | "project" | "built-in"
 	skillName?: string // For skill operations (createSkill, deleteSkill, openSkillFile)
 	skillMode?: string // For skill operations (mode restriction)
 	skillDescription?: string // For createSkill (skill description)
@@ -790,7 +788,6 @@ export interface ClineSayTool {
 		| "codebaseSearch"
 		| "readFile"
 		| "readCommandOutput"
-		| "fetchInstructions"
 		| "listFilesTopLevel"
 		| "listFilesRecursive"
 		| "searchFiles"
@@ -801,6 +798,7 @@ export interface ClineSayTool {
 		| "imageGenerated"
 		| "runSlashCommand"
 		| "updateTodoList"
+		| "skill"
 	path?: string
 	// For readCommandOutput
 	readStart?: number
@@ -847,6 +845,8 @@ export interface ClineSayTool {
 	args?: string
 	source?: string
 	description?: string
+	// Properties for skill tool
+	skill?: string
 }
 
 // Must keep in sync with system prompt.

+ 18 - 16
src/core/assistant-message/NativeToolCallParser.ts

@@ -449,14 +449,6 @@ export class NativeToolCallParser {
 				}
 				break
 
-			case "fetch_instructions":
-				if (partialArgs.task !== undefined) {
-					nativeArgs = {
-						task: partialArgs.task,
-					}
-				}
-				break
-
 			case "generate_image":
 				if (partialArgs.prompt !== undefined || partialArgs.path !== undefined) {
 					nativeArgs = {
@@ -476,6 +468,15 @@ export class NativeToolCallParser {
 				}
 				break
 
+			case "skill":
+				if (partialArgs.skill !== undefined) {
+					nativeArgs = {
+						skill: partialArgs.skill,
+						args: partialArgs.args,
+					}
+				}
+				break
+
 			case "search_files":
 				if (partialArgs.path !== undefined || partialArgs.regex !== undefined) {
 					nativeArgs = {
@@ -736,14 +737,6 @@ export class NativeToolCallParser {
 					}
 					break
 
-				case "fetch_instructions":
-					if (args.task !== undefined) {
-						nativeArgs = {
-							task: args.task,
-						} as NativeArgsFor<TName>
-					}
-					break
-
 				case "generate_image":
 					if (args.prompt !== undefined && args.path !== undefined) {
 						nativeArgs = {
@@ -763,6 +756,15 @@ export class NativeToolCallParser {
 					}
 					break
 
+				case "skill":
+					if (args.skill !== undefined) {
+						nativeArgs = {
+							skill: args.skill,
+							args: args.args,
+						} as NativeArgsFor<TName>
+					}
+					break
+
 				case "search_files":
 					if (args.path !== undefined && args.regex !== undefined) {
 						nativeArgs = {

+ 10 - 11
src/core/assistant-message/presentAssistantMessage.ts

@@ -14,7 +14,6 @@ import type { ToolParamName, ToolResponse, ToolUse, McpToolUse } from "../../sha
 import { AskIgnoredError } from "../task/AskIgnoredError"
 import { Task } from "../task/Task"
 
-import { fetchInstructionsTool } from "../tools/FetchInstructionsTool"
 import { listFilesTool } from "../tools/ListFilesTool"
 import { readFileTool } from "../tools/ReadFileTool"
 import { readCommandOutputTool } from "../tools/ReadCommandOutputTool"
@@ -34,6 +33,7 @@ import { attemptCompletionTool, AttemptCompletionCallbacks } from "../tools/Atte
 import { newTaskTool } from "../tools/NewTaskTool"
 import { updateTodoListTool } from "../tools/UpdateTodoListTool"
 import { runSlashCommandTool } from "../tools/RunSlashCommandTool"
+import { skillTool } from "../tools/SkillTool"
 import { generateImageTool } from "../tools/GenerateImageTool"
 import { applyDiffTool as applyDiffToolClass } from "../tools/ApplyDiffTool"
 import { isValidToolName, validateToolUse } from "../tools/validateToolUse"
@@ -347,8 +347,6 @@ export async function presentAssistantMessage(cline: Task) {
 							return readFileTool.getReadFileToolDescription(block.name, block.nativeArgs)
 						}
 						return readFileTool.getReadFileToolDescription(block.name, block.params)
-					case "fetch_instructions":
-						return `[${block.name} for '${block.params.task}']`
 					case "write_to_file":
 						return `[${block.name} for '${block.params.path}']`
 					case "apply_diff":
@@ -394,6 +392,8 @@ export async function presentAssistantMessage(cline: Task) {
 					}
 					case "run_slash_command":
 						return `[${block.name} for '${block.params.command}'${block.params.args ? ` with args: ${block.params.args}` : ""}]`
+					case "skill":
+						return `[${block.name} for '${block.params.skill}'${block.params.args ? ` with args: ${block.params.args}` : ""}]`
 					case "generate_image":
 						return `[${block.name} for '${block.params.path}']`
 					default:
@@ -760,13 +760,6 @@ export async function presentAssistantMessage(cline: Task) {
 						pushToolResult,
 					})
 					break
-				case "fetch_instructions":
-					await fetchInstructionsTool.handle(cline, block as ToolUse<"fetch_instructions">, {
-						askApproval,
-						handleError,
-						pushToolResult,
-					})
-					break
 				case "list_files":
 					await listFilesTool.handle(cline, block as ToolUse<"list_files">, {
 						askApproval,
@@ -870,6 +863,13 @@ export async function presentAssistantMessage(cline: Task) {
 						pushToolResult,
 					})
 					break
+				case "skill":
+					await skillTool.handle(cline, block as ToolUse<"skill">, {
+						askApproval,
+						handleError,
+						pushToolResult,
+					})
+					break
 				case "generate_image":
 					await checkpointSaveAndMark(cline)
 					await generateImageTool.handle(cline, block as ToolUse<"generate_image">, {
@@ -1049,7 +1049,6 @@ function containsXmlToolMarkup(text: string): boolean {
 		"codebase_search",
 		"edit_file",
 		"execute_command",
-		"fetch_instructions",
 		"generate_image",
 		"list_files",
 		"new_task",

+ 5 - 8
src/core/auto-approval/index.ts

@@ -151,14 +151,11 @@ export async function checkAutoApproval({
 			return { decision: "approve" }
 		}
 
-		if (tool?.tool === "fetchInstructions") {
-			if (tool.content === "create_mode") {
-				return state.alwaysAllowModeSwitch === true ? { decision: "approve" } : { decision: "ask" }
-			}
-
-			if (tool.content === "create_mcp_server") {
-				return state.alwaysAllowMcp === true ? { decision: "approve" } : { decision: "ask" }
-			}
+		// The skill tool only loads pre-defined instructions from built-in, global, or project skills.
+		// It does not read arbitrary files - skills must be explicitly installed/defined by the user.
+		// Auto-approval is intentional to provide a seamless experience when loading task instructions.
+		if (tool.tool === "skill") {
+			return { decision: "approve" }
 		}
 
 		if (tool?.tool === "switchMode") {

+ 0 - 4
src/core/prompts/__tests__/add-custom-instructions.spec.ts

@@ -211,7 +211,6 @@ describe("addCustomInstructions", () => {
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			undefined, // experiments
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -233,7 +232,6 @@ describe("addCustomInstructions", () => {
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			undefined, // experiments
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -257,7 +255,6 @@ describe("addCustomInstructions", () => {
 			undefined, // customModes,
 			undefined, // globalCustomInstructions
 			undefined, // experiments
-			false, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -280,7 +277,6 @@ describe("addCustomInstructions", () => {
 			undefined, // customModes,
 			undefined, // globalCustomInstructions
 			undefined, // experiments
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			true, // partialReadsEnabled

+ 0 - 3
src/core/prompts/__tests__/custom-system-prompt.spec.ts

@@ -105,7 +105,6 @@ describe("File-Based Custom System Prompt", () => {
 				undefined, // customModes
 				undefined, // globalCustomInstructions
 				undefined, // experiments
-				true, // enableMcpServerCreation
 				undefined, // language
 				undefined, // rooIgnoreInstructions
 				undefined, // partialReadsEnabled
@@ -142,7 +141,6 @@ describe("File-Based Custom System Prompt", () => {
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			undefined, // experiments
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -187,7 +185,6 @@ describe("File-Based Custom System Prompt", () => {
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			undefined, // experiments
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled

+ 2 - 13
src/core/prompts/__tests__/system-prompt.spec.ts

@@ -226,7 +226,6 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			experiments,
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -248,7 +247,6 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // customModes,
 			undefined, // globalCustomInstructions
 			experiments,
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -272,7 +270,6 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // customModes,
 			undefined, // globalCustomInstructions
 			experiments,
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -294,7 +291,6 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // customModes,
 			undefined, // globalCustomInstructions
 			experiments,
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -316,7 +312,6 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // customModes,
 			undefined, // globalCustomInstructions
 			experiments,
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -324,6 +319,7 @@ describe("SYSTEM_PROMPT", () => {
 
 		expect(prompt).toMatchFileSnapshot("./__snapshots__/system-prompt/with-different-viewport-size.snap")
 	})
+
 	it("should include vscode language in custom instructions", async () => {
 		// Mock vscode.env.language
 		const vscode = vi.mocked(await import("vscode")) as any
@@ -364,7 +360,6 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			undefined, // experiments
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -424,7 +419,6 @@ describe("SYSTEM_PROMPT", () => {
 			customModes, // customModes
 			"Global instructions", // globalCustomInstructions
 			experiments,
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -461,7 +455,6 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			undefined, // experiments
-			false, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -493,7 +486,6 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			undefined, // experiments
-			false, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -523,7 +515,6 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			experiments,
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -555,7 +546,6 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			experiments,
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -587,7 +577,6 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			experiments,
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -619,7 +608,6 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			experiments,
-			true, // enableMcpServerCreation
 			undefined, // language
 			undefined, // rooIgnoreInstructions
 			undefined, // partialReadsEnabled
@@ -654,6 +642,7 @@ describe("SYSTEM_PROMPT", () => {
 		expect(prompt).toContain("SYSTEM INFORMATION")
 		expect(prompt).toContain("OBJECTIVE")
 	})
+
 	afterAll(() => {
 		vi.restoreAllMocks()
 	})

+ 0 - 62
src/core/prompts/instructions/create-mode.ts

@@ -1,62 +0,0 @@
-import * as path from "path"
-import * as vscode from "vscode"
-
-import { GlobalFileNames } from "../../../shared/globalFileNames"
-import { getSettingsDirectoryPath } from "../../../utils/storage"
-
-export async function createModeInstructions(context: vscode.ExtensionContext | undefined): Promise<string> {
-	if (!context) throw new Error("Missing VSCode Extension Context")
-
-	const settingsDir = await getSettingsDirectoryPath(context.globalStorageUri.fsPath)
-	const customModesPath = path.join(settingsDir, GlobalFileNames.customModes)
-
-	return `
-Custom modes can be configured in two ways:
-  1. Globally via '${customModesPath}' (created automatically on startup)
-  2. Per-workspace via '.roomodes' in the workspace root directory
-
-When modes with the same slug exist in both files, the workspace-specific .roomodes version takes precedence. This allows projects to override global modes or define project-specific modes.
-
-If asked to create a project mode, create it in .roomodes in the workspace root. If asked to create a global mode, use the global custom modes file.
-
-- The following fields are required and must not be empty:
-  * slug: A valid slug (lowercase letters, numbers, and hyphens). Must be unique, and shorter is better.
-  * name: The display name for the mode
-  * roleDefinition: A detailed description of the mode's role and capabilities
-  * groups: Array of allowed tool groups (can be empty). Each group can be specified either as a string (e.g., "edit" to allow editing any file) or with file restrictions (e.g., ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }] to only allow editing markdown files)
-
-- The following fields are optional but highly recommended:
-  * description: A short, human-readable description of what this mode does (5 words)
-  * whenToUse: A clear description of when this mode should be selected and what types of tasks it's best suited for. This helps the Orchestrator mode make better decisions.
-  * customInstructions: Additional instructions for how the mode should operate
-
-- For multi-line text, include newline characters in the string like "This is the first line.\\nThis is the next line.\\n\\nThis is a double line break."
-
-Both files should follow this structure (in YAML format):
-
-customModes:
-  - slug: designer  # Required: unique slug with lowercase letters, numbers, and hyphens
-    name: Designer  # Required: mode display name
-    description: UI/UX design systems expert  # Optional but recommended: short description (5 words)
-    roleDefinition: >-
-      You are Roo, a UI/UX expert specializing in design systems and frontend development. Your expertise includes:
-      - Creating and maintaining design systems
-      - Implementing responsive and accessible web interfaces
-      - Working with CSS, HTML, and modern frontend frameworks
-      - Ensuring consistent user experiences across platforms  # Required: non-empty
-    whenToUse: >-
-      Use this mode when creating or modifying UI components, implementing design systems,
-      or ensuring responsive web interfaces. This mode is especially effective with CSS,
-      HTML, and modern frontend frameworks.  # Optional but recommended
-    groups:  # Required: array of tool groups (can be empty)
-      - read     # Read files group (read_file, fetch_instructions, search_files, list_files)
-      - edit     # Edit files group (apply_diff, write_to_file) - allows editing any file
-      # Or with file restrictions:
-      # - - edit
-      #   - fileRegex: \\.md$
-      #     description: Markdown files only  # Edit group that only allows editing markdown files
-      - browser  # Browser group (browser_action)
-      - command  # Command group (execute_command)
-      - mcp      # MCP group (use_mcp_tool, access_mcp_resource)
-    customInstructions: Additional instructions for the Designer mode  # Optional`
-}

+ 0 - 25
src/core/prompts/instructions/instructions.ts

@@ -1,25 +0,0 @@
-import { createMCPServerInstructions } from "./create-mcp-server"
-import { createModeInstructions } from "./create-mode"
-import { McpHub } from "../../../services/mcp/McpHub"
-import { DiffStrategy } from "../../../shared/tools"
-import * as vscode from "vscode"
-
-interface InstructionsDetail {
-	mcpHub?: McpHub
-	diffStrategy?: DiffStrategy
-	context?: vscode.ExtensionContext
-}
-
-export async function fetchInstructions(text: string, detail: InstructionsDetail): Promise<string> {
-	switch (text) {
-		case "create_mcp_server": {
-			return await createMCPServerInstructions(detail.mcpHub, detail.diffStrategy)
-		}
-		case "create_mode": {
-			return await createModeInstructions(detail.context)
-		}
-		default: {
-			return ""
-		}
-	}
-}

+ 2 - 18
src/core/prompts/sections/modes.ts

@@ -5,17 +5,14 @@ import type { ModeConfig } from "@roo-code/types"
 import { getAllModesWithPrompts } from "../../../shared/modes"
 import { ensureSettingsDirectoryExists } from "../../../utils/globalContext"
 
-export async function getModesSection(
-	context: vscode.ExtensionContext,
-	skipXmlExamples: boolean = false,
-): Promise<string> {
+export async function getModesSection(context: vscode.ExtensionContext): Promise<string> {
 	// Make sure path gets created
 	await ensureSettingsDirectoryExists(context)
 
 	// Get all modes with their overrides from extension state
 	const allModes = await getAllModesWithPrompts(context)
 
-	let modesContent = `====
+	const modesContent = `====
 
 MODES
 
@@ -34,18 +31,5 @@ ${allModes
 	})
 	.join("\n")}`
 
-	if (!skipXmlExamples) {
-		modesContent += `
-If the user asks you to create or edit a new mode for this project, you should read the instructions by using the fetch_instructions tool, like this:
-<fetch_instructions>
-<task>create_mode</task>
-</fetch_instructions>
-`
-	} else {
-		modesContent += `
-If the user asks you to create or edit a new mode for this project, you should read the instructions by using the fetch_instructions tool.
-`
-	}
-
 	return modesContent
 }

+ 12 - 11
src/core/prompts/sections/skills.ts

@@ -33,10 +33,11 @@ export async function getSkillsSection(
 		.map((skill) => {
 			const name = escapeXml(skill.name)
 			const description = escapeXml(skill.description)
-			// Per the Agent Skills integration guidance for filesystem-based agents,
-			// location should be an absolute path to the SKILL.md file.
-			const location = escapeXml(skill.path)
-			return `  <skill>\n    <name>${name}</name>\n    <description>${description}</description>\n    <location>${location}</location>\n  </skill>`
+			// Only include location for file-based skills (not built-in)
+			// Built-in skills are loaded via the skill tool by name, not by path
+			const isFileBasedSkill = skill.source !== "built-in" && skill.path !== "built-in"
+			const locationLine = isFileBasedSkill ? `\n    <location>${escapeXml(skill.path)}</location>` : ""
+			return `  <skill>\n    <name>${name}</name>\n    <description>${description}</description>${locationLine}\n  </skill>`
 		})
 		.join("\n")
 
@@ -62,9 +63,9 @@ Step 2: Branching Decision
 <if_skill_applies>
 - Select EXACTLY ONE skill.
 - Prefer the most specific skill when multiple skills match.
-- Read the full SKILL.md file at the skill's <location>.
-- Load the SKILL.md contents fully into context BEFORE continuing.
-- Follow the SKILL.md instructions precisely.
+- Use the skill tool to load the skill by name.
+- Load the skill's instructions fully into context BEFORE continuing.
+- Follow the skill instructions precisely.
 - Do NOT respond outside the skill-defined flow.
 </if_skill_applies>
 
@@ -74,15 +75,15 @@ Step 2: Branching Decision
 </if_no_skill_applies>
 
 CONSTRAINTS:
-- Do NOT load every SKILL.md up front.
-- Load SKILL.md ONLY after a skill is selected.
+- Do NOT load every skill up front.
+- Load skills ONLY after a skill is selected.
 - Do NOT skip this check.
 - FAILURE to perform this check is an error.
 </mandatory_skill_check>
 
 <linked_file_handling>
-- When a SKILL.md is loaded, ONLY the contents of SKILL.md are present.
-- Files linked from SKILL.md are NOT loaded automatically.
+- When a skill is loaded, ONLY the skill instructions are present.
+- Files linked from the skill are NOT loaded automatically.
 - The model MUST explicitly decide to read a linked file based on task relevance.
 - Do NOT assume the contents of linked files unless they have been explicitly read.
 - Prefer reading the minimum necessary linked file.

+ 0 - 3
src/core/prompts/system.ts

@@ -53,7 +53,6 @@ async function generatePrompt(
 	customModeConfigs?: ModeConfig[],
 	globalCustomInstructions?: string,
 	experiments?: Record<string, boolean>,
-	enableMcpServerCreation?: boolean,
 	language?: string,
 	rooIgnoreInstructions?: string,
 	partialReadsEnabled?: boolean,
@@ -127,7 +126,6 @@ export const SYSTEM_PROMPT = async (
 	customModes?: ModeConfig[],
 	globalCustomInstructions?: string,
 	experiments?: Record<string, boolean>,
-	enableMcpServerCreation?: boolean,
 	language?: string,
 	rooIgnoreInstructions?: string,
 	partialReadsEnabled?: boolean,
@@ -196,7 +194,6 @@ ${customInstructions}`
 		customModes,
 		globalCustomInstructions,
 		experiments,
-		enableMcpServerCreation,
 		language,
 		rooIgnoreInstructions,
 		partialReadsEnabled,

+ 0 - 26
src/core/prompts/tools/native-tools/fetch_instructions.ts

@@ -1,26 +0,0 @@
-import type OpenAI from "openai"
-
-const FETCH_INSTRUCTIONS_DESCRIPTION = `Retrieve detailed instructions for performing a predefined task, such as creating an MCP server or creating a mode.`
-
-const TASK_PARAMETER_DESCRIPTION = `Task identifier to fetch instructions for`
-
-export default {
-	type: "function",
-	function: {
-		name: "fetch_instructions",
-		description: FETCH_INSTRUCTIONS_DESCRIPTION,
-		strict: true,
-		parameters: {
-			type: "object",
-			properties: {
-				task: {
-					type: "string",
-					description: TASK_PARAMETER_DESCRIPTION,
-					enum: ["create_mcp_server", "create_mode"],
-				},
-			},
-			required: ["task"],
-			additionalProperties: false,
-		},
-	},
-} satisfies OpenAI.Chat.ChatCompletionTool

+ 2 - 2
src/core/prompts/tools/native-tools/index.ts

@@ -7,13 +7,13 @@ import attemptCompletion from "./attempt_completion"
 import browserAction from "./browser_action"
 import codebaseSearch from "./codebase_search"
 import executeCommand from "./execute_command"
-import fetchInstructions from "./fetch_instructions"
 import generateImage from "./generate_image"
 import listFiles from "./list_files"
 import newTask from "./new_task"
 import readCommandOutput from "./read_command_output"
 import { createReadFileTool, type ReadFileToolOptions } from "./read_file"
 import runSlashCommand from "./run_slash_command"
+import skill from "./skill"
 import searchAndReplace from "./search_and_replace"
 import searchReplace from "./search_replace"
 import edit_file from "./edit_file"
@@ -62,13 +62,13 @@ export function getNativeTools(options: NativeToolsOptions = {}): OpenAI.Chat.Ch
 		browserAction,
 		codebaseSearch,
 		executeCommand,
-		fetchInstructions,
 		generateImage,
 		listFiles,
 		newTask,
 		readCommandOutput,
 		createReadFileTool(readFileOptions),
 		runSlashCommand,
+		skill,
 		searchAndReplace,
 		searchReplace,
 		edit_file,

+ 33 - 0
src/core/prompts/tools/native-tools/skill.ts

@@ -0,0 +1,33 @@
+import type OpenAI from "openai"
+
+const SKILL_DESCRIPTION = `Load and execute a skill by name. Skills provide specialized instructions for common tasks like creating MCP servers or custom modes.
+
+Use this tool when you need to follow specific procedures documented in a skill. Available skills are listed in the AVAILABLE SKILLS section of the system prompt.`
+
+const SKILL_PARAMETER_DESCRIPTION = `Name of the skill to load (e.g., create-mcp-server, create-mode). Must match a skill name from the available skills list.`
+
+const ARGS_PARAMETER_DESCRIPTION = `Optional context or arguments to pass to the skill`
+
+export default {
+	type: "function",
+	function: {
+		name: "skill",
+		description: SKILL_DESCRIPTION,
+		strict: true,
+		parameters: {
+			type: "object",
+			properties: {
+				skill: {
+					type: "string",
+					description: SKILL_PARAMETER_DESCRIPTION,
+				},
+				args: {
+					type: ["string", "null"],
+					description: ARGS_PARAMETER_DESCRIPTION,
+				},
+			},
+			required: ["skill", "args"],
+			additionalProperties: false,
+		},
+	},
+} satisfies OpenAI.Chat.ChatCompletionTool

+ 0 - 2
src/core/task/Task.ts

@@ -3758,7 +3758,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
 			customModePrompts,
 			customInstructions,
 			experiments,
-			enableMcpServerCreation,
 			browserToolEnabled,
 			language,
 			maxConcurrentFileReads,
@@ -3797,7 +3796,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
 				customModes,
 				customInstructions,
 				experiments,
-				enableMcpServerCreation,
 				language,
 				rooIgnoreInstructions,
 				maxReadFileLine !== -1,

+ 0 - 75
src/core/tools/FetchInstructionsTool.ts

@@ -1,75 +0,0 @@
-import { type ClineSayTool } from "@roo-code/types"
-
-import { Task } from "../task/Task"
-import { fetchInstructions } from "../prompts/instructions/instructions"
-import { formatResponse } from "../prompts/responses"
-import type { ToolUse } from "../../shared/tools"
-
-import { BaseTool, ToolCallbacks } from "./BaseTool"
-
-interface FetchInstructionsParams {
-	task: string
-}
-
-export class FetchInstructionsTool extends BaseTool<"fetch_instructions"> {
-	readonly name = "fetch_instructions" as const
-
-	async execute(params: FetchInstructionsParams, task: Task, callbacks: ToolCallbacks): Promise<void> {
-		const { handleError, pushToolResult, askApproval } = callbacks
-		const { task: taskParam } = params
-
-		try {
-			if (!taskParam) {
-				task.consecutiveMistakeCount++
-				task.recordToolError("fetch_instructions")
-				task.didToolFailInCurrentTurn = true
-				pushToolResult(await task.sayAndCreateMissingParamError("fetch_instructions", "task"))
-				return
-			}
-
-			task.consecutiveMistakeCount = 0
-
-			const completeMessage = JSON.stringify({
-				tool: "fetchInstructions",
-				content: taskParam,
-			} satisfies ClineSayTool)
-
-			const didApprove = await askApproval("tool", completeMessage)
-
-			if (!didApprove) {
-				return
-			}
-
-			// Now fetch the content and provide it to the agent.
-			const provider = task.providerRef.deref()
-			const mcpHub = provider?.getMcpHub()
-
-			if (!mcpHub) {
-				throw new Error("MCP hub not available")
-			}
-
-			const diffStrategy = task.diffStrategy
-			const context = provider?.context
-			const content = await fetchInstructions(taskParam, { mcpHub, diffStrategy, context })
-
-			if (!content) {
-				pushToolResult(formatResponse.toolError(`Invalid instructions request: ${taskParam}`))
-				return
-			}
-
-			pushToolResult(content)
-		} catch (error) {
-			await handleError("fetch instructions", error as Error)
-		}
-	}
-
-	override async handlePartial(task: Task, block: ToolUse<"fetch_instructions">): Promise<void> {
-		const taskParam: string | undefined = block.params.task
-		const sharedMessageProps: ClineSayTool = { tool: "fetchInstructions", content: taskParam }
-
-		const partialMessage = JSON.stringify({ ...sharedMessageProps, content: undefined } satisfies ClineSayTool)
-		await task.ask("tool", partialMessage, block.partial).catch(() => {})
-	}
-}
-
-export const fetchInstructionsTool = new FetchInstructionsTool()

+ 112 - 0
src/core/tools/SkillTool.ts

@@ -0,0 +1,112 @@
+import { Task } from "../task/Task"
+import { formatResponse } from "../prompts/responses"
+import { BaseTool, ToolCallbacks } from "./BaseTool"
+import type { ToolUse } from "../../shared/tools"
+
+interface SkillParams {
+	skill: string
+	args?: string | null
+}
+
+export class SkillTool extends BaseTool<"skill"> {
+	readonly name = "skill" as const
+
+	async execute(params: SkillParams, task: Task, callbacks: ToolCallbacks): Promise<void> {
+		const { skill: skillName, args } = params
+		const { askApproval, handleError, pushToolResult } = callbacks
+
+		try {
+			// Validate skill name parameter
+			if (!skillName) {
+				task.consecutiveMistakeCount++
+				task.recordToolError("skill")
+				task.didToolFailInCurrentTurn = true
+				pushToolResult(await task.sayAndCreateMissingParamError("skill", "skill"))
+				return
+			}
+
+			task.consecutiveMistakeCount = 0
+
+			// Get SkillsManager from provider
+			const provider = task.providerRef.deref()
+			const skillsManager = provider?.getSkillsManager()
+
+			if (!skillsManager) {
+				task.recordToolError("skill")
+				task.didToolFailInCurrentTurn = true
+				pushToolResult(formatResponse.toolError("Skills Manager not available"))
+				return
+			}
+
+			// Get current mode for skill resolution
+			const state = await provider?.getState()
+			const currentMode = state?.mode ?? "code"
+
+			// Fetch skill content
+			const skillContent = await skillsManager.getSkillContent(skillName, currentMode)
+
+			if (!skillContent) {
+				// Get available skills for error message
+				const availableSkills = skillsManager.getSkillsForMode(currentMode)
+				const skillNames = availableSkills.map((s) => s.name)
+
+				task.recordToolError("skill")
+				task.didToolFailInCurrentTurn = true
+				pushToolResult(
+					formatResponse.toolError(
+						`Skill '${skillName}' not found. Available skills: ${skillNames.join(", ") || "(none)"}`,
+					),
+				)
+				return
+			}
+
+			// Build approval message
+			const toolMessage = JSON.stringify({
+				tool: "skill",
+				skill: skillName,
+				args: args,
+				source: skillContent.source,
+				description: skillContent.description,
+			})
+
+			const didApprove = await askApproval("tool", toolMessage)
+
+			if (!didApprove) {
+				return
+			}
+
+			// Build the result message
+			let result = `Skill: ${skillName}`
+
+			if (skillContent.description) {
+				result += `\nDescription: ${skillContent.description}`
+			}
+
+			if (args) {
+				result += `\nProvided arguments: ${args}`
+			}
+
+			result += `\nSource: ${skillContent.source}`
+			result += `\n\n--- Skill Instructions ---\n\n${skillContent.instructions}`
+
+			pushToolResult(result)
+		} catch (error) {
+			await handleError("executing skill", error as Error)
+		}
+	}
+
+	override async handlePartial(task: Task, block: ToolUse<"skill">): Promise<void> {
+		const skillName: string | undefined = block.params.skill
+		const args: string | undefined = block.params.args
+
+		const partialMessage = JSON.stringify({
+			tool: "skill",
+			skill: skillName,
+			args: args,
+		})
+
+		await task.ask("tool", partialMessage, block.partial).catch(() => {})
+	}
+}
+
+export const skillTool = new SkillTool()

+ 345 - 0
src/core/tools/__tests__/skillTool.spec.ts

@@ -0,0 +1,345 @@
+import { describe, it, expect, vi, beforeEach } from "vitest"
+import { skillTool } from "../SkillTool"
+import { Task } from "../../task/Task"
+import { formatResponse } from "../../prompts/responses"
+import type { ToolUse } from "../../../shared/tools"
+
+describe("skillTool", () => {
+	let mockTask: any
+	let mockCallbacks: any
+	let mockSkillsManager: any
+
+	beforeEach(() => {
+		vi.clearAllMocks()
+
+		mockSkillsManager = {
+			getSkillContent: vi.fn(),
+			getSkillsForMode: vi.fn().mockReturnValue([]),
+		}
+
+		mockTask = {
+			consecutiveMistakeCount: 0,
+			recordToolError: vi.fn(),
+			didToolFailInCurrentTurn: false,
+			sayAndCreateMissingParamError: vi.fn().mockResolvedValue("Missing parameter error"),
+			ask: vi.fn().mockResolvedValue({}),
+			providerRef: {
+				deref: vi.fn().mockReturnValue({
+					getState: vi.fn().mockResolvedValue({ mode: "code" }),
+					getSkillsManager: vi.fn().mockReturnValue(mockSkillsManager),
+				}),
+			},
+		}
+
+		mockCallbacks = {
+			askApproval: vi.fn().mockResolvedValue(true),
+			handleError: vi.fn(),
+			pushToolResult: vi.fn(),
+		}
+	})
+
+	it("should handle missing skill parameter", async () => {
+		const block: ToolUse<"skill"> = {
+			type: "tool_use" as const,
+			name: "skill" as const,
+			params: {},
+			partial: false,
+			nativeArgs: {
+				skill: "",
+			},
+		}
+
+		await skillTool.handle(mockTask as Task, block, mockCallbacks)
+
+		expect(mockTask.consecutiveMistakeCount).toBe(1)
+		expect(mockTask.recordToolError).toHaveBeenCalledWith("skill")
+		expect(mockTask.sayAndCreateMissingParamError).toHaveBeenCalledWith("skill", "skill")
+		expect(mockCallbacks.pushToolResult).toHaveBeenCalledWith("Missing parameter error")
+	})
+
+	it("should handle skill not found", async () => {
+		const block: ToolUse<"skill"> = {
+			type: "tool_use" as const,
+			name: "skill" as const,
+			params: {},
+			partial: false,
+			nativeArgs: {
+				skill: "non-existent",
+			},
+		}
+
+		mockSkillsManager.getSkillContent.mockResolvedValue(null)
+		mockSkillsManager.getSkillsForMode.mockReturnValue([{ name: "create-mcp-server" }])
+
+		await skillTool.handle(mockTask as Task, block, mockCallbacks)
+
+		expect(mockCallbacks.pushToolResult).toHaveBeenCalledWith(
+			formatResponse.toolError("Skill 'non-existent' not found. Available skills: create-mcp-server"),
+		)
+	})
+
+	it("should handle empty available skills list", async () => {
+		const block: ToolUse<"skill"> = {
+			type: "tool_use" as const,
+			name: "skill" as const,
+			params: {},
+			partial: false,
+			nativeArgs: {
+				skill: "non-existent",
+			},
+		}
+
+		mockSkillsManager.getSkillContent.mockResolvedValue(null)
+		mockSkillsManager.getSkillsForMode.mockReturnValue([])
+
+		await skillTool.handle(mockTask as Task, block, mockCallbacks)
+
+		expect(mockCallbacks.pushToolResult).toHaveBeenCalledWith(
+			formatResponse.toolError("Skill 'non-existent' not found. Available skills: (none)"),
+		)
+	})
+
+	it("should successfully load built-in skill", async () => {
+		const block: ToolUse<"skill"> = {
+			type: "tool_use" as const,
+			name: "skill" as const,
+			params: {},
+			partial: false,
+			nativeArgs: {
+				skill: "create-mcp-server",
+			},
+		}
+
+		const mockSkillContent = {
+			name: "create-mcp-server",
+			description: "Instructions for creating MCP servers",
+			source: "built-in",
+			instructions: "Step 1: Create the server...",
+		}
+
+		mockSkillsManager.getSkillContent.mockResolvedValue(mockSkillContent)
+
+		await skillTool.handle(mockTask as Task, block, mockCallbacks)
+
+		expect(mockCallbacks.askApproval).toHaveBeenCalledWith(
+			"tool",
+			JSON.stringify({
+				tool: "skill",
+				skill: "create-mcp-server",
+				args: undefined,
+				source: "built-in",
+				description: "Instructions for creating MCP servers",
+			}),
+		)
+
+		expect(mockCallbacks.pushToolResult).toHaveBeenCalledWith(
+			`Skill: create-mcp-server
+Description: Instructions for creating MCP servers
+Source: built-in
+
+--- Skill Instructions ---
+
+Step 1: Create the server...`,
+		)
+	})
+
+	it("should successfully load skill with arguments", async () => {
+		const block: ToolUse<"skill"> = {
+			type: "tool_use" as const,
+			name: "skill" as const,
+			params: {},
+			partial: false,
+			nativeArgs: {
+				skill: "create-mcp-server",
+				args: "weather API server",
+			},
+		}
+
+		const mockSkillContent = {
+			name: "create-mcp-server",
+			description: "Instructions for creating MCP servers",
+			source: "built-in",
+			instructions: "Step 1: Create the server...",
+		}
+
+		mockSkillsManager.getSkillContent.mockResolvedValue(mockSkillContent)
+
+		await skillTool.handle(mockTask as Task, block, mockCallbacks)
+
+		expect(mockCallbacks.pushToolResult).toHaveBeenCalledWith(
+			`Skill: create-mcp-server
+Description: Instructions for creating MCP servers
+Provided arguments: weather API server
+Source: built-in
+
+--- Skill Instructions ---
+
+Step 1: Create the server...`,
+		)
+	})
+
+	it("should handle user rejection", async () => {
+		const block: ToolUse<"skill"> = {
+			type: "tool_use" as const,
+			name: "skill" as const,
+			params: {},
+			partial: false,
+			nativeArgs: {
+				skill: "create-mcp-server",
+			},
+		}
+
+		mockSkillsManager.getSkillContent.mockResolvedValue({
+			name: "create-mcp-server",
+			description: "Test",
+			source: "built-in",
+			instructions: "Test instructions",
+		})
+
+		mockCallbacks.askApproval.mockResolvedValue(false)
+
+		await skillTool.handle(mockTask as Task, block, mockCallbacks)
+
+		expect(mockCallbacks.pushToolResult).not.toHaveBeenCalled()
+	})
+
+	it("should handle partial block", async () => {
+		const block: ToolUse<"skill"> = {
+			type: "tool_use" as const,
+			name: "skill" as const,
+			params: {
+				skill: "create-mcp-server",
+				args: "",
+			},
+			partial: true,
+		}
+
+		await skillTool.handle(mockTask as Task, block, mockCallbacks)
+
+		expect(mockTask.ask).toHaveBeenCalledWith(
+			"tool",
+			JSON.stringify({
+				tool: "skill",
+				skill: "create-mcp-server",
+				args: "",
+			}),
+			true,
+		)
+
+		expect(mockCallbacks.pushToolResult).not.toHaveBeenCalled()
+	})
+
+	it("should handle errors during execution", async () => {
+		const block: ToolUse<"skill"> = {
+			type: "tool_use" as const,
+			name: "skill" as const,
+			params: {},
+			partial: false,
+			nativeArgs: {
+				skill: "create-mcp-server",
+			},
+		}
+
+		const error = new Error("Test error")
+		mockSkillsManager.getSkillContent.mockRejectedValue(error)
+
+		await skillTool.handle(mockTask as Task, block, mockCallbacks)
+
+		expect(mockCallbacks.handleError).toHaveBeenCalledWith("executing skill", error)
+	})
+
+	it("should reset consecutive mistake count on valid skill", async () => {
+		const block: ToolUse<"skill"> = {
+			type: "tool_use" as const,
+			name: "skill" as const,
+			params: {},
+			partial: false,
+			nativeArgs: {
+				skill: "create-mcp-server",
+			},
+		}
+
+		mockTask.consecutiveMistakeCount = 5
+
+		const mockSkillContent = {
+			name: "create-mcp-server",
+			description: "Test",
+			source: "built-in",
+			instructions: "Test instructions",
+		}
+
+		mockSkillsManager.getSkillContent.mockResolvedValue(mockSkillContent)
+
+		await skillTool.handle(mockTask as Task, block, mockCallbacks)
+
+		expect(mockTask.consecutiveMistakeCount).toBe(0)
+	})
+
+	it("should handle Skills Manager not available", async () => {
+		const block: ToolUse<"skill"> = {
+			type: "tool_use" as const,
+			name: "skill" as const,
+			params: {},
+			partial: false,
+			nativeArgs: {
+				skill: "create-mcp-server",
+			},
+		}
+
+		mockTask.providerRef.deref = vi.fn().mockReturnValue({
+			getState: vi.fn().mockResolvedValue({ mode: "code" }),
+			getSkillsManager: vi.fn().mockReturnValue(undefined),
+		})
+
+		await skillTool.handle(mockTask as Task, block, mockCallbacks)
+
+		expect(mockTask.recordToolError).toHaveBeenCalledWith("skill")
+		expect(mockCallbacks.pushToolResult).toHaveBeenCalledWith(
+			formatResponse.toolError("Skills Manager not available"),
+		)
+	})
+
+	it("should load project skill", async () => {
+		const block: ToolUse<"skill"> = {
+			type: "tool_use" as const,
+			name: "skill" as const,
+			params: {},
+			partial: false,
+			nativeArgs: {
+				skill: "my-project-skill",
+			},
+		}
+
+		const mockSkillContent = {
+			name: "my-project-skill",
+			description: "A custom project skill",
+			source: "project",
+			instructions: "Follow these project-specific instructions...",
+		}
+
+		mockSkillsManager.getSkillContent.mockResolvedValue(mockSkillContent)
+
+		await skillTool.handle(mockTask as Task, block, mockCallbacks)
+
+		expect(mockCallbacks.askApproval).toHaveBeenCalledWith(
+			"tool",
+			JSON.stringify({
+				tool: "skill",
+				skill: "my-project-skill",
+				args: undefined,
+				source: "project",
+				description: "A custom project skill",
+			}),
+		)
+
+		expect(mockCallbacks.pushToolResult).toHaveBeenCalledWith(
+			`Skill: my-project-skill
+Description: A custom project skill
+Source: project
+
+--- Skill Instructions ---
+
+Follow these project-specific instructions...`,
+		)
+	})
+})

+ 0 - 3
src/core/webview/ClineProvider.ts

@@ -2024,7 +2024,6 @@ export class ClineProvider
 			terminalZshP10k,
 			terminalZdotdir,
 			mcpEnabled,
-			enableMcpServerCreation,
 			currentApiConfigName,
 			listApiConfigMeta,
 			pinnedApiConfigs,
@@ -2162,7 +2161,6 @@ export class ClineProvider
 			terminalZshP10k: terminalZshP10k ?? false,
 			terminalZdotdir: terminalZdotdir ?? false,
 			mcpEnabled: mcpEnabled ?? true,
-			enableMcpServerCreation: enableMcpServerCreation ?? true,
 			currentApiConfigName: currentApiConfigName ?? "default",
 			listApiConfigMeta: listApiConfigMeta ?? [],
 			pinnedApiConfigs: pinnedApiConfigs ?? {},
@@ -2408,7 +2406,6 @@ export class ClineProvider
 			mode: stateValues.mode ?? defaultModeSlug,
 			language: stateValues.language ?? formatLanguage(vscode.env.language),
 			mcpEnabled: stateValues.mcpEnabled ?? true,
-			enableMcpServerCreation: stateValues.enableMcpServerCreation ?? true,
 			mcpServers: this.mcpHub?.getAllServers() ?? [],
 			currentApiConfigName: stateValues.currentApiConfigName ?? "default",
 			listApiConfigMeta: stateValues.listApiConfigMeta ?? [],

+ 1 - 36
src/core/webview/__tests__/ClineProvider.spec.ts

@@ -315,6 +315,7 @@ vi.mock("../../../api/providers/fetchers/modelCache", () => ({
 
 vi.mock("../diff/strategies/multi-search-replace", () => ({
 	MultiSearchReplaceDiffStrategy: vi.fn().mockImplementation(() => ({
+		getToolDescription: () => "test",
 		getName: () => "test-strategy",
 		applyDiff: vi.fn(),
 	})),
@@ -557,7 +558,6 @@ describe("ClineProvider", () => {
 			writeDelayMs: 1000,
 			browserViewportSize: "900x600",
 			mcpEnabled: true,
-			enableMcpServerCreation: false,
 			mode: defaultModeSlug,
 			customModes: [],
 			experiments: experimentDefault,
@@ -1349,7 +1349,6 @@ describe("ClineProvider", () => {
 					apiProvider: "openrouter" as const,
 				},
 				mcpEnabled: true,
-				enableMcpServerCreation: false,
 				mode: "code" as const,
 				experiments: experimentDefault,
 			} as any)
@@ -1374,7 +1373,6 @@ describe("ClineProvider", () => {
 					apiProvider: "openrouter" as const,
 				},
 				mcpEnabled: false,
-				enableMcpServerCreation: false,
 				mode: "code" as const,
 				experiments: experimentDefault,
 			} as any)
@@ -1431,38 +1429,6 @@ describe("ClineProvider", () => {
 			)
 		})
 
-		test("generates system prompt with various configurations", async () => {
-			await provider.resolveWebviewView(mockWebviewView)
-
-			// Mock getState with typical configuration
-			vi.spyOn(provider, "getState").mockResolvedValue({
-				apiConfiguration: {
-					apiProvider: "openrouter",
-					apiModelId: "test-model",
-				},
-				customModePrompts: {},
-				mode: "code",
-				enableMcpServerCreation: true,
-				mcpEnabled: false,
-				browserViewportSize: "900x600",
-				experiments: experimentDefault,
-				browserToolEnabled: true,
-			} as any)
-
-			// Trigger getSystemPrompt
-			const handler = getMessageHandler()
-			await handler({ type: "getSystemPrompt", mode: "code" })
-
-			// Verify system prompt was generated and sent
-			expect(mockPostMessage).toHaveBeenCalledWith(
-				expect.objectContaining({
-					type: "systemPrompt",
-					text: expect.any(String),
-					mode: "code",
-				}),
-			)
-		})
-
 		test("uses correct mode-specific instructions when mode is specified", async () => {
 			await provider.resolveWebviewView(mockWebviewView)
 
@@ -1475,7 +1441,6 @@ describe("ClineProvider", () => {
 					architect: { customInstructions: "Architect mode instructions" },
 				},
 				mode: "architect",
-				enableMcpServerCreation: false,
 				mcpEnabled: false,
 				browserViewportSize: "900x600",
 				experiments: experimentDefault,

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

@@ -60,7 +60,6 @@ function makeProviderStub() {
 			browserViewportSize: "900x600",
 			mcpEnabled: false,
 			experiments: {},
-			enableMcpServerCreation: false,
 			browserToolEnabled: true, // critical: enabled in settings
 			language: "en",
 			maxReadFileLine: -1,

+ 0 - 2
src/core/webview/generateSystemPrompt.ts

@@ -17,7 +17,6 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
 		browserViewportSize,
 		mcpEnabled,
 		experiments,
-		enableMcpServerCreation,
 		browserToolEnabled,
 		language,
 		maxReadFileLine,
@@ -69,7 +68,6 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
 		customModes,
 		customInstructions,
 		experiments,
-		enableMcpServerCreation,
 		language,
 		rooIgnoreInstructions,
 		maxReadFileLine !== -1,

+ 15 - 0
src/core/webview/skillsMessageHandler.ts

@@ -44,6 +44,11 @@ export async function handleCreateSkill(
 			throw new Error(t("skills:errors.missing_create_fields"))
 		}
 
+		// Built-in skills cannot be created
+		if (source === "built-in") {
+			throw new Error(t("skills:errors.cannot_modify_builtin"))
+		}
+
 		const skillsManager = provider.getSkillsManager()
 		if (!skillsManager) {
 			throw new Error(t("skills:errors.manager_unavailable"))
@@ -82,6 +87,11 @@ export async function handleDeleteSkill(
 			throw new Error(t("skills:errors.missing_delete_fields"))
 		}
 
+		// Built-in skills cannot be deleted
+		if (source === "built-in") {
+			throw new Error(t("skills:errors.cannot_modify_builtin"))
+		}
+
 		const skillsManager = provider.getSkillsManager()
 		if (!skillsManager) {
 			throw new Error(t("skills:errors.manager_unavailable"))
@@ -114,6 +124,11 @@ export async function handleOpenSkillFile(provider: ClineProvider, message: Webv
 			throw new Error(t("skills:errors.missing_delete_fields"))
 		}
 
+		// Built-in skills cannot be opened as files (they have no file path)
+		if (source === "built-in") {
+			throw new Error(t("skills:errors.cannot_open_builtin"))
+		}
+
 		const skillsManager = provider.getSkillsManager()
 		if (!skillsManager) {
 			throw new Error(t("skills:errors.manager_unavailable"))

+ 3 - 8
src/core/webview/webviewMessageHandler.ts

@@ -1444,10 +1444,6 @@ export const webviewMessageHandler = async (
 			}
 			break
 		}
-		case "enableMcpServerCreation":
-			await updateGlobalState("enableMcpServerCreation", message.bool ?? true)
-			await provider.postStateToWebview()
-			break
 		case "remoteControlEnabled":
 			try {
 				await CloudService.instance.updateUserSettings({ extensionBridgeEnabled: message.bool ?? false })
@@ -2249,10 +2245,9 @@ export const webviewMessageHandler = async (
 					const yamlContent = await fs.readFile(fileUri[0].fsPath, "utf-8")
 
 					// Import the mode with the specified source level
-					const result = await provider.customModesManager.importModeWithRules(
-						yamlContent,
-						message.source || "project", // Default to project if not specified
-					)
+					// Note: "built-in" is not a valid source for importing modes
+					const importSource = message.source === "global" ? "global" : "project"
+					const result = await provider.customModesManager.importModeWithRules(yamlContent, importSource)
 
 					if (result.success) {
 						// Update state after importing

+ 3 - 1
src/i18n/locales/ca/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Falten camps obligatoris: skillName, source o skillDescription",
 		"manager_unavailable": "El gestor d'habilitats no està disponible",
 		"missing_delete_fields": "Falten camps obligatoris: skillName o source",
-		"skill_not_found": "No s'ha trobat l'habilitat \"{{name}}\""
+		"skill_not_found": "No s'ha trobat l'habilitat \"{{name}}\"",
+		"cannot_modify_builtin": "Les habilitats integrades no es poden crear ni eliminar",
+		"cannot_open_builtin": "Les habilitats integrades no es poden obrir com a fitxers"
 	}
 }

+ 3 - 1
src/i18n/locales/de/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Erforderliche Felder fehlen: skillName, source oder skillDescription",
 		"manager_unavailable": "Skill-Manager nicht verfügbar",
 		"missing_delete_fields": "Erforderliche Felder fehlen: skillName oder source",
-		"skill_not_found": "Skill \"{{name}}\" nicht gefunden"
+		"skill_not_found": "Skill \"{{name}}\" nicht gefunden",
+		"cannot_modify_builtin": "Integrierte Skills können nicht erstellt oder gelöscht werden",
+		"cannot_open_builtin": "Integrierte Skills können nicht als Dateien geöffnet werden"
 	}
 }

+ 3 - 1
src/i18n/locales/en/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Missing required fields: skillName, source, or skillDescription",
 		"manager_unavailable": "Skills manager not available",
 		"missing_delete_fields": "Missing required fields: skillName or source",
-		"skill_not_found": "Skill \"{{name}}\" not found"
+		"skill_not_found": "Skill \"{{name}}\" not found",
+		"cannot_modify_builtin": "Built-in skills cannot be created or deleted",
+		"cannot_open_builtin": "Built-in skills cannot be opened as files"
 	}
 }

+ 3 - 1
src/i18n/locales/es/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Faltan campos obligatorios: skillName, source o skillDescription",
 		"manager_unavailable": "El gestor de habilidades no está disponible",
 		"missing_delete_fields": "Faltan campos obligatorios: skillName o source",
-		"skill_not_found": "No se encontró la habilidad \"{{name}}\""
+		"skill_not_found": "No se encontró la habilidad \"{{name}}\"",
+		"cannot_modify_builtin": "Las habilidades integradas no se pueden crear ni eliminar",
+		"cannot_open_builtin": "Las habilidades integradas no se pueden abrir como archivos"
 	}
 }

+ 3 - 1
src/i18n/locales/fr/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Champs obligatoires manquants : skillName, source ou skillDescription",
 		"manager_unavailable": "Le gestionnaire de compétences n'est pas disponible",
 		"missing_delete_fields": "Champs obligatoires manquants : skillName ou source",
-		"skill_not_found": "Compétence \"{{name}}\" introuvable"
+		"skill_not_found": "Compétence \"{{name}}\" introuvable",
+		"cannot_modify_builtin": "Les compétences intégrées ne peuvent pas être créées ou supprimées",
+		"cannot_open_builtin": "Les compétences intégrées ne peuvent pas être ouvertes en tant que fichiers"
 	}
 }

+ 3 - 1
src/i18n/locales/hi/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "आवश्यक फ़ील्ड गायब हैं: skillName, source, या skillDescription",
 		"manager_unavailable": "स्किल मैनेजर उपलब्ध नहीं है",
 		"missing_delete_fields": "आवश्यक फ़ील्ड गायब हैं: skillName या source",
-		"skill_not_found": "स्किल \"{{name}}\" नहीं मिला"
+		"skill_not_found": "स्किल \"{{name}}\" नहीं मिला",
+		"cannot_modify_builtin": "बिल्ट-इन स्किल्स को बनाया या हटाया नहीं जा सकता",
+		"cannot_open_builtin": "बिल्ट-इन स्किल्स को फाइलों के रूप में नहीं खोला जा सकता"
 	}
 }

+ 3 - 1
src/i18n/locales/id/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Bidang wajib tidak ada: skillName, source, atau skillDescription",
 		"manager_unavailable": "Manajer skill tidak tersedia",
 		"missing_delete_fields": "Bidang wajib tidak ada: skillName atau source",
-		"skill_not_found": "Skill \"{{name}}\" tidak ditemukan"
+		"skill_not_found": "Skill \"{{name}}\" tidak ditemukan",
+		"cannot_modify_builtin": "Skill bawaan tidak dapat dibuat atau dihapus",
+		"cannot_open_builtin": "Skill bawaan tidak dapat dibuka sebagai file"
 	}
 }

+ 3 - 1
src/i18n/locales/it/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Campi obbligatori mancanti: skillName, source o skillDescription",
 		"manager_unavailable": "Il gestore delle skill non è disponibile",
 		"missing_delete_fields": "Campi obbligatori mancanti: skillName o source",
-		"skill_not_found": "Skill \"{{name}}\" non trovata"
+		"skill_not_found": "Skill \"{{name}}\" non trovata",
+		"cannot_modify_builtin": "Le skill integrate non possono essere create o eliminate",
+		"cannot_open_builtin": "Le skill integrate non possono essere aperte come file"
 	}
 }

+ 3 - 1
src/i18n/locales/ja/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "必須フィールドが不足しています:skillName、source、またはskillDescription",
 		"manager_unavailable": "スキルマネージャーが利用できません",
 		"missing_delete_fields": "必須フィールドが不足しています:skillNameまたはsource",
-		"skill_not_found": "スキル「{{name}}」が見つかりません"
+		"skill_not_found": "スキル「{{name}}」が見つかりません",
+		"cannot_modify_builtin": "組み込みスキルは作成または削除できません",
+		"cannot_open_builtin": "組み込みスキルはファイルとして開けません"
 	}
 }

+ 3 - 1
src/i18n/locales/ko/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "필수 필드 누락: skillName, source 또는 skillDescription",
 		"manager_unavailable": "스킬 관리자를 사용할 수 없습니다",
 		"missing_delete_fields": "필수 필드 누락: skillName 또는 source",
-		"skill_not_found": "스킬 \"{{name}}\"을(를) 찾을 수 없습니다"
+		"skill_not_found": "스킬 \"{{name}}\"을(를) 찾을 수 없습니다",
+		"cannot_modify_builtin": "기본 제공 스킬은 생성하거나 삭제할 수 없습니다",
+		"cannot_open_builtin": "기본 제공 스킬은 파일로 열 수 없습니다"
 	}
 }

+ 3 - 1
src/i18n/locales/nl/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Vereiste velden ontbreken: skillName, source of skillDescription",
 		"manager_unavailable": "Vaardigheidenbeheerder niet beschikbaar",
 		"missing_delete_fields": "Vereiste velden ontbreken: skillName of source",
-		"skill_not_found": "Vaardigheid \"{{name}}\" niet gevonden"
+		"skill_not_found": "Vaardigheid \"{{name}}\" niet gevonden",
+		"cannot_modify_builtin": "Ingebouwde vaardigheden kunnen niet worden aangemaakt of verwijderd",
+		"cannot_open_builtin": "Ingebouwde vaardigheden kunnen niet als bestanden worden geopend"
 	}
 }

+ 3 - 1
src/i18n/locales/pl/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Brakuje wymaganych pól: skillName, source lub skillDescription",
 		"manager_unavailable": "Menedżer umiejętności niedostępny",
 		"missing_delete_fields": "Brakuje wymaganych pól: skillName lub source",
-		"skill_not_found": "Nie znaleziono umiejętności \"{{name}}\""
+		"skill_not_found": "Nie znaleziono umiejętności \"{{name}}\"",
+		"cannot_modify_builtin": "Wbudowane umiejętności nie mogą być tworzone ani usuwane",
+		"cannot_open_builtin": "Wbudowane umiejętności nie mogą być otwierane jako pliki"
 	}
 }

+ 3 - 1
src/i18n/locales/pt-BR/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Campos obrigatórios ausentes: skillName, source ou skillDescription",
 		"manager_unavailable": "Gerenciador de habilidades não disponível",
 		"missing_delete_fields": "Campos obrigatórios ausentes: skillName ou source",
-		"skill_not_found": "Habilidade \"{{name}}\" não encontrada"
+		"skill_not_found": "Habilidade \"{{name}}\" não encontrada",
+		"cannot_modify_builtin": "Habilidades integradas não podem ser criadas ou excluídas",
+		"cannot_open_builtin": "Habilidades integradas não podem ser abertas como arquivos"
 	}
 }

+ 3 - 1
src/i18n/locales/ru/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Отсутствуют обязательные поля: skillName, source или skillDescription",
 		"manager_unavailable": "Менеджер навыков недоступен",
 		"missing_delete_fields": "Отсутствуют обязательные поля: skillName или source",
-		"skill_not_found": "Навык \"{{name}}\" не найден"
+		"skill_not_found": "Навык \"{{name}}\" не найден",
+		"cannot_modify_builtin": "Встроенные навыки нельзя создавать или удалять",
+		"cannot_open_builtin": "Встроенные навыки нельзя открыть как файлы"
 	}
 }

+ 3 - 1
src/i18n/locales/tr/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Gerekli alanlar eksik: skillName, source veya skillDescription",
 		"manager_unavailable": "Beceri yöneticisi kullanılamıyor",
 		"missing_delete_fields": "Gerekli alanlar eksik: skillName veya source",
-		"skill_not_found": "\"{{name}}\" becerisi bulunamadı"
+		"skill_not_found": "\"{{name}}\" becerisi bulunamadı",
+		"cannot_modify_builtin": "Yerleşik beceriler oluşturulamaz veya silinemez",
+		"cannot_open_builtin": "Yerleşik beceriler dosya olarak açılamaz"
 	}
 }

+ 3 - 1
src/i18n/locales/vi/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "Thiếu các trường bắt buộc: skillName, source hoặc skillDescription",
 		"manager_unavailable": "Trình quản lý kỹ năng không khả dụng",
 		"missing_delete_fields": "Thiếu các trường bắt buộc: skillName hoặc source",
-		"skill_not_found": "Không tìm thấy kỹ năng \"{{name}}\""
+		"skill_not_found": "Không tìm thấy kỹ năng \"{{name}}\"",
+		"cannot_modify_builtin": "Không thể tạo hoặc xóa kỹ năng tích hợp sẵn",
+		"cannot_open_builtin": "Không thể mở kỹ năng tích hợp sẵn dưới dạng tệp"
 	}
 }

+ 3 - 1
src/i18n/locales/zh-CN/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "缺少必填字段:skillName、source 或 skillDescription",
 		"manager_unavailable": "技能管理器不可用",
 		"missing_delete_fields": "缺少必填字段:skillName 或 source",
-		"skill_not_found": "未找到技能 \"{{name}}\""
+		"skill_not_found": "未找到技能 \"{{name}}\"",
+		"cannot_modify_builtin": "内置技能无法创建或删除",
+		"cannot_open_builtin": "内置技能无法作为文件打开"
 	}
 }

+ 3 - 1
src/i18n/locales/zh-TW/skills.json

@@ -9,6 +9,8 @@
 		"missing_create_fields": "缺少必填欄位:skillName、source 或 skillDescription",
 		"manager_unavailable": "技能管理器無法使用",
 		"missing_delete_fields": "缺少必填欄位:skillName 或 source",
-		"skill_not_found": "找不到技能「{{name}}」"
+		"skill_not_found": "找不到技能「{{name}}」",
+		"cannot_modify_builtin": "內建技能無法建立或刪除",
+		"cannot_open_builtin": "內建技能無法作為檔案開啟"
 	}
 }

+ 2 - 0
src/package.json

@@ -439,6 +439,8 @@
 		"pretest": "turbo run bundle --cwd ..",
 		"test": "vitest run",
 		"format": "prettier --write .",
+		"generate:skills": "tsx services/skills/generate-built-in-skills.ts",
+		"prebundle": "pnpm generate:skills",
 		"bundle": "node esbuild.mjs",
 		"vscode:prepublish": "pnpm bundle --production",
 		"vsix": "mkdirp ../bin && vsce package --no-dependencies --out ../bin",

+ 32 - 6
src/services/skills/SkillsManager.ts

@@ -15,6 +15,7 @@ import {
 	SKILL_NAME_MAX_LENGTH,
 } from "@roo-code/types"
 import { t } from "../../i18n"
+import { getBuiltInSkills, getBuiltInSkillContent } from "./built-in-skills"
 
 // Re-export for convenience
 export type { SkillMetadata, SkillContent }
@@ -159,13 +160,19 @@ export class SkillsManager {
 
 	/**
 	 * Get skills available for the current mode.
-	 * Resolves overrides: project > global, mode-specific > generic.
+	 * Resolves overrides: project > global > built-in, mode-specific > generic.
 	 *
 	 * @param currentMode - The current mode slug (e.g., 'code', 'architect')
 	 */
 	getSkillsForMode(currentMode: string): SkillMetadata[] {
 		const resolvedSkills = new Map<string, SkillMetadata>()
 
+		// First, add built-in skills (lowest priority)
+		for (const skill of getBuiltInSkills()) {
+			resolvedSkills.set(skill.name, skill)
+		}
+
+		// Then, add discovered skills (will override built-in skills with same name)
 		for (const skill of this.skills.values()) {
 			// Skip mode-specific skills that don't match current mode
 			if (skill.mode && skill.mode !== currentMode) continue
@@ -189,12 +196,22 @@ export class SkillsManager {
 
 	/**
 	 * Determine if newSkill should override existingSkill based on priority rules.
-	 * Priority: project > global, mode-specific > generic
+	 * Priority: project > global > built-in, mode-specific > generic
 	 */
 	private shouldOverrideSkill(existing: SkillMetadata, newSkill: SkillMetadata): boolean {
-		// Project always overrides global
-		if (newSkill.source === "project" && existing.source === "global") return true
-		if (newSkill.source === "global" && existing.source === "project") return false
+		// Define source priority: project > global > built-in
+		const sourcePriority: Record<string, number> = {
+			project: 3,
+			global: 2,
+			"built-in": 1,
+		}
+
+		const existingPriority = sourcePriority[existing.source] ?? 0
+		const newPriority = sourcePriority[newSkill.source] ?? 0
+
+		// Higher priority source always wins
+		if (newPriority > existingPriority) return true
+		if (newPriority < existingPriority) return false
 
 		// Same source: mode-specific overrides generic
 		if (newSkill.mode && !existing.mode) return true
@@ -219,12 +236,21 @@ export class SkillsManager {
 			const modeSkills = this.getSkillsForMode(currentMode)
 			skill = modeSkills.find((s) => s.name === name)
 		} else {
-			// Fall back to any skill with this name
+			// Fall back to any skill with this name (check discovered skills first, then built-in)
 			skill = Array.from(this.skills.values()).find((s) => s.name === name)
+			if (!skill) {
+				skill = getBuiltInSkills().find((s) => s.name === name)
+			}
 		}
 
 		if (!skill) return null
 
+		// For built-in skills, use the built-in content
+		if (skill.source === "built-in") {
+			return getBuiltInSkillContent(name)
+		}
+
+		// For file-based skills, read from disk
 		const fileContent = await fs.readFile(skill.path, "utf-8")
 		const { content: body } = matter(fileContent)
 

+ 8 - 0
src/services/skills/__tests__/SkillsManager.spec.ts

@@ -98,6 +98,14 @@ vi.mock("../../../i18n", () => ({
 	},
 }))
 
+// Mock built-in skills to isolate tests from actual built-in skills
+vi.mock("../built-in-skills", () => ({
+	getBuiltInSkills: () => [],
+	getBuiltInSkillContent: () => null,
+	isBuiltInSkill: () => false,
+	getBuiltInSkillNames: () => [],
+}))
+
 import { SkillsManager } from "../SkillsManager"
 import { ClineProvider } from "../../../core/webview/ClineProvider"
 

+ 175 - 0
src/services/skills/__tests__/generate-built-in-skills.spec.ts

@@ -0,0 +1,175 @@
+/**
+ * Tests for the built-in skills generation script validation logic.
+ *
+ * Note: These tests focus on the validation functions since the main script
+ * is designed to be run as a CLI tool. The actual generation is tested
+ * via the integration with the build process.
+ */
+
+describe("generate-built-in-skills validation", () => {
+	describe("validateSkillName", () => {
+		// Validation function extracted from the generation script
+		function validateSkillName(name: string): string[] {
+			const errors: string[] = []
+
+			if (name.length < 1 || name.length > 64) {
+				errors.push(`Name must be 1-64 characters (got ${name.length})`)
+			}
+
+			const nameFormat = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
+			if (!nameFormat.test(name)) {
+				errors.push(
+					"Name must be lowercase letters/numbers/hyphens only (no leading/trailing hyphen, no consecutive hyphens)",
+				)
+			}
+
+			return errors
+		}
+
+		it("should accept valid skill names", () => {
+			expect(validateSkillName("mcp-builder")).toHaveLength(0)
+			expect(validateSkillName("create-mode")).toHaveLength(0)
+			expect(validateSkillName("pdf-processing")).toHaveLength(0)
+			expect(validateSkillName("a")).toHaveLength(0)
+			expect(validateSkillName("skill123")).toHaveLength(0)
+			expect(validateSkillName("my-skill-v2")).toHaveLength(0)
+		})
+
+		it("should reject names with uppercase letters", () => {
+			const errors = validateSkillName("Create-MCP-Server")
+			expect(errors).toHaveLength(1)
+			expect(errors[0]).toContain("lowercase")
+		})
+
+		it("should reject names with leading hyphen", () => {
+			const errors = validateSkillName("-my-skill")
+			expect(errors).toHaveLength(1)
+			expect(errors[0]).toContain("leading/trailing hyphen")
+		})
+
+		it("should reject names with trailing hyphen", () => {
+			const errors = validateSkillName("my-skill-")
+			expect(errors).toHaveLength(1)
+			expect(errors[0]).toContain("leading/trailing hyphen")
+		})
+
+		it("should reject names with consecutive hyphens", () => {
+			const errors = validateSkillName("my--skill")
+			expect(errors).toHaveLength(1)
+			expect(errors[0]).toContain("consecutive hyphens")
+		})
+
+		it("should reject empty names", () => {
+			const errors = validateSkillName("")
+			expect(errors.length).toBeGreaterThan(0)
+		})
+
+		it("should reject names longer than 64 characters", () => {
+			const longName = "a".repeat(65)
+			const errors = validateSkillName(longName)
+			expect(errors).toHaveLength(1)
+			expect(errors[0]).toContain("1-64 characters")
+		})
+
+		it("should reject names with special characters", () => {
+			expect(validateSkillName("my_skill").length).toBeGreaterThan(0)
+			expect(validateSkillName("my.skill").length).toBeGreaterThan(0)
+			expect(validateSkillName("my skill").length).toBeGreaterThan(0)
+		})
+	})
+
+	describe("validateDescription", () => {
+		// Validation function extracted from the generation script
+		function validateDescription(description: string): string[] {
+			const errors: string[] = []
+			const trimmed = description.trim()
+
+			if (trimmed.length < 1 || trimmed.length > 1024) {
+				errors.push(`Description must be 1-1024 characters (got ${trimmed.length})`)
+			}
+
+			return errors
+		}
+
+		it("should accept valid descriptions", () => {
+			expect(validateDescription("A short description")).toHaveLength(0)
+			expect(validateDescription("x")).toHaveLength(0)
+			expect(validateDescription("x".repeat(1024))).toHaveLength(0)
+		})
+
+		it("should reject empty descriptions", () => {
+			const errors = validateDescription("")
+			expect(errors).toHaveLength(1)
+			expect(errors[0]).toContain("1-1024 characters")
+		})
+
+		it("should reject whitespace-only descriptions", () => {
+			const errors = validateDescription("   ")
+			expect(errors).toHaveLength(1)
+			expect(errors[0]).toContain("got 0")
+		})
+
+		it("should reject descriptions longer than 1024 characters", () => {
+			const longDesc = "x".repeat(1025)
+			const errors = validateDescription(longDesc)
+			expect(errors).toHaveLength(1)
+			expect(errors[0]).toContain("got 1025")
+		})
+	})
+
+	describe("escapeForTemplateLiteral", () => {
+		// Escape function extracted from the generation script
+		function escapeForTemplateLiteral(str: string): string {
+			return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${")
+		}
+
+		it("should escape backticks", () => {
+			expect(escapeForTemplateLiteral("code `example`")).toBe("code \\`example\\`")
+		})
+
+		it("should escape template literal interpolation", () => {
+			expect(escapeForTemplateLiteral("value: ${foo}")).toBe("value: \\${foo}")
+		})
+
+		it("should escape backslashes", () => {
+			expect(escapeForTemplateLiteral("path\\to\\file")).toBe("path\\\\to\\\\file")
+		})
+
+		it("should handle combined escapes", () => {
+			const input = "const x = `${value}`"
+			const expected = "const x = \\`\\${value}\\`"
+			expect(escapeForTemplateLiteral(input)).toBe(expected)
+		})
+	})
+})
+
+describe("built-in skills integration", () => {
+	it("should have valid skill names matching directory names", async () => {
+		// Import the generated built-in skills
+		const { getBuiltInSkills, getBuiltInSkillContent } = await import("../built-in-skills")
+
+		const skills = getBuiltInSkills()
+
+		// Verify we have the expected skills
+		const skillNames = skills.map((s) => s.name)
+		expect(skillNames).toContain("create-mcp-server")
+		expect(skillNames).toContain("create-mode")
+
+		// Verify each skill has valid content
+		for (const skill of skills) {
+			expect(skill.source).toBe("built-in")
+			expect(skill.path).toBe("built-in")
+
+			const content = getBuiltInSkillContent(skill.name)
+			expect(content).not.toBeNull()
+			expect(content!.instructions.length).toBeGreaterThan(0)
+		}
+	})
+
+	it("should return null for non-existent skills", async () => {
+		const { getBuiltInSkillContent } = await import("../built-in-skills")
+
+		const content = getBuiltInSkillContent("non-existent-skill")
+		expect(content).toBeNull()
+	})
+})

+ 421 - 0
src/services/skills/built-in-skills.ts

@@ -0,0 +1,421 @@
+/**
+ * AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
+ *
+ * This file is generated by generate-built-in-skills.ts from the SKILL.md files
+ * in the built-in/ directory. To modify built-in skills, edit the corresponding
+ * SKILL.md file and run: pnpm generate:skills
+ */
+
+import { SkillMetadata, SkillContent } from "../../shared/skills"
+
+interface BuiltInSkillDefinition {
+	name: string
+	description: string
+	instructions: string
+}
+
+const BUILT_IN_SKILLS: Record<string, BuiltInSkillDefinition> = {
+	"create-mcp-server": {
+		name: "create-mcp-server",
+		description:
+			"Instructions for creating MCP (Model Context Protocol) servers that expose tools and resources for the agent to use. Use when the user asks to create a new MCP server or add MCP capabilities.",
+		instructions: `You have the ability to create an MCP server and add it to a configuration file that will then expose the tools and resources for you to use with \`use_mcp_tool\` and \`access_mcp_resource\`.
+
+When creating MCP servers, it's important to understand that they operate in a non-interactive environment. The server cannot initiate OAuth flows, open browser windows, or prompt for user input during runtime. All credentials and authentication tokens must be provided upfront through environment variables in the MCP settings configuration. For example, Spotify's API uses OAuth to get a refresh token for the user, but the MCP server cannot initiate this flow. While you can walk the user through obtaining an application client ID and secret, you may have to create a separate one-time setup script (like get-refresh-token.js) that captures and logs the final piece of the puzzle: the user's refresh token (i.e. you might run the script using execute_command which would open a browser for authentication, and then log the refresh token so that you can see it in the command output for you to use in the MCP settings configuration).
+
+Unless the user specifies otherwise, new local MCP servers should be created in your MCP servers directory. You can find the path to this directory by checking the MCP settings file, or ask the user where they'd like the server created.
+
+### MCP Server Types and Configuration
+
+MCP servers can be configured in two ways in the MCP settings file:
+
+1. Local (Stdio) Server Configuration:
+
+\`\`\`json
+{
+	"mcpServers": {
+		"local-weather": {
+			"command": "node",
+			"args": ["/path/to/weather-server/build/index.js"],
+			"env": {
+				"OPENWEATHER_API_KEY": "your-api-key"
+			}
+		}
+	}
+}
+\`\`\`
+
+2. Remote (SSE) Server Configuration:
+
+\`\`\`json
+{
+	"mcpServers": {
+		"remote-weather": {
+			"url": "https://api.example.com/mcp",
+			"headers": {
+				"Authorization": "Bearer your-api-key"
+			}
+		}
+	}
+}
+\`\`\`
+
+Common configuration options for both types:
+
+- \`disabled\`: (optional) Set to true to temporarily disable the server
+- \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60)
+- \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation
+- \`disabledTools\`: (optional) Array of tool names that are not included in the system prompt and won't be used
+
+### Example Local MCP Server
+
+For example, if the user wanted to give you the ability to retrieve weather information, you could create an MCP server that uses the OpenWeather API to get weather information, add it to the MCP settings configuration file, and then notice that you now have access to new tools and resources in the system prompt that you might use to show the user your new capabilities.
+
+The following example demonstrates how to build a local MCP server that provides weather data functionality using the Stdio transport. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS)
+
+1. Use the \`create-typescript-server\` tool to bootstrap a new project in your MCP servers directory:
+
+\`\`\`bash
+cd /path/to/your/mcp-servers
+npx @modelcontextprotocol/create-server weather-server
+cd weather-server
+# Install dependencies
+npm install axios zod @modelcontextprotocol/sdk
+\`\`\`
+
+This will create a new project with the following structure:
+
+\`\`\`
+weather-server/
+	├── package.json
+			{
+				...
+				"type": "module", // added by default, uses ES module syntax (import/export) rather than CommonJS (require/module.exports) (Important to know if you create additional scripts in this server repository like a get-refresh-token.js script)
+				"scripts": {
+					"build": "tsc && node -e \\"require('fs').chmodSync('build/index.js', '755')\\"",
+					...
+				}
+				...
+			}
+	├── tsconfig.json
+	└── src/
+			└── index.ts      # Main server implementation
+\`\`\`
+
+2. Replace \`src/index.ts\` with the following:
+
+\`\`\`typescript
+#!/usr/bin/env node
+import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"
+import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
+import { z } from "zod"
+import axios from "axios"
+
+const API_KEY = process.env.OPENWEATHER_API_KEY // provided by MCP config
+if (!API_KEY) {
+	throw new Error("OPENWEATHER_API_KEY environment variable is required")
+}
+
+// Define types for OpenWeather API responses
+interface WeatherData {
+	main: {
+		temp: number
+		humidity: number
+	}
+	weather: Array<{
+		description: string
+	}>
+	wind: {
+		speed: number
+	}
+}
+
+interface ForecastData {
+	list: Array<
+		WeatherData & {
+			dt_txt: string
+		}
+	>
+}
+
+// Create an MCP server
+const server = new McpServer({
+	name: "weather-server",
+	version: "0.1.0",
+})
+
+// Create axios instance for OpenWeather API
+const weatherApi = axios.create({
+	baseURL: "http://api.openweathermap.org/data/2.5",
+	params: {
+		appid: API_KEY,
+		units: "metric",
+	},
+})
+
+// Add a tool for getting weather forecasts
+server.tool(
+	"get_forecast",
+	{
+		city: z.string().describe("City name"),
+		days: z.number().min(1).max(5).optional().describe("Number of days (1-5)"),
+	},
+	async ({ city, days = 3 }) => {
+		try {
+			const response = await weatherApi.get<ForecastData>("forecast", {
+				params: {
+					q: city,
+					cnt: Math.min(days, 5) * 8,
+				},
+			})
+
+			return {
+				content: [
+					{
+						type: "text",
+						text: JSON.stringify(response.data.list, null, 2),
+					},
+				],
+			}
+		} catch (error) {
+			if (axios.isAxiosError(error)) {
+				return {
+					content: [
+						{
+							type: "text",
+							text: \`Weather API error: \${error.response?.data.message ?? error.message}\`,
+						},
+					],
+					isError: true,
+				}
+			}
+			throw error
+		}
+	},
+)
+
+// Add a resource for current weather in San Francisco
+server.resource("sf_weather", { uri: "weather://San Francisco/current", list: true }, async (uri) => {
+	try {
+		const response = weatherApi.get<WeatherData>("weather", {
+			params: { q: "San Francisco" },
+		})
+
+		return {
+			contents: [
+				{
+					uri: uri.href,
+					mimeType: "application/json",
+					text: JSON.stringify(
+						{
+							temperature: response.data.main.temp,
+							conditions: response.data.weather[0].description,
+							humidity: response.data.main.humidity,
+							wind_speed: response.data.wind.speed,
+							timestamp: new Date().toISOString(),
+						},
+						null,
+						2,
+					),
+				},
+			],
+		}
+	} catch (error) {
+		if (axios.isAxiosError(error)) {
+			throw new Error(\`Weather API error: \${error.response?.data.message ?? error.message}\`)
+		}
+		throw error
+	}
+})
+
+// Add a dynamic resource template for current weather by city
+server.resource(
+	"current_weather",
+	new ResourceTemplate("weather://{city}/current", { list: true }),
+	async (uri, { city }) => {
+		try {
+			const response = await weatherApi.get("weather", {
+				params: { q: city },
+			})
+
+			return {
+				contents: [
+					{
+						uri: uri.href,
+						mimeType: "application/json",
+						text: JSON.stringify(
+							{
+								temperature: response.data.main.temp,
+								conditions: response.data.weather[0].description,
+								humidity: response.data.main.humidity,
+								wind_speed: response.data.wind.speed,
+								timestamp: new Date().toISOString(),
+							},
+							null,
+							2,
+						),
+					},
+				],
+			}
+		} catch (error) {
+			if (axios.isAxiosError(error)) {
+				throw new Error(\`Weather API error: \${error.response?.data.message ?? error.message}\`)
+			}
+			throw error
+		}
+	},
+)
+
+// Start receiving messages on stdin and sending messages on stdout
+const transport = new StdioServerTransport()
+await server.connect(transport)
+console.error("Weather MCP server running on stdio")
+\`\`\`
+
+(Remember: This is just an example–you may use different dependencies, break the implementation up into multiple files, etc.)
+
+3. Build and compile the executable JavaScript file
+
+\`\`\`bash
+npm run build
+\`\`\`
+
+4. Whenever you need an environment variable such as an API key to configure the MCP server, walk the user through the process of getting the key. For example, they may need to create an account and go to a developer dashboard to generate the key. Provide step-by-step instructions and URLs to make it easy for the user to retrieve the necessary information. Then use the ask_followup_question tool to ask the user for the key, in this case the OpenWeather API key.
+
+5. Install the MCP Server by adding the MCP server configuration to the MCP settings file. On macOS/Linux this is typically at \`~/.roo-code/settings/mcp_settings.json\`, on Windows at \`%APPDATA%\\roo-code\\settings\\mcp_settings.json\`. The settings file may have other MCP servers already configured, so you would read it first and then add your new server to the existing \`mcpServers\` object.
+
+IMPORTANT: Regardless of what else you see in the MCP settings file, you must default any new MCP servers you create to disabled=false, alwaysAllow=[] and disabledTools=[].
+
+\`\`\`json
+{
+	"mcpServers": {
+		...,
+		"weather": {
+			"command": "node",
+			"args": ["/path/to/weather-server/build/index.js"],
+			"env": {
+				"OPENWEATHER_API_KEY": "user-provided-api-key"
+			}
+		},
+	}
+}
+\`\`\`
+
+(Note: the user may also ask you to install the MCP server to the Claude desktop app, in which case you would read then modify \`~/Library/Application\\ Support/Claude/claude_desktop_config.json\` on macOS for example. It follows the same format of a top level \`mcpServers\` object.)
+
+6. After you have edited the MCP settings configuration file, the system will automatically run all the servers and expose the available tools and resources in the 'Connected MCP Servers' section.
+
+7. Now that you have access to these new tools and resources, you may suggest ways the user can command you to invoke them - for example, with this new weather tool now available, you can invite the user to ask "what's the weather in San Francisco?"
+
+## Editing MCP Servers
+
+The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' in the system prompt), e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use write_to_file or apply_diff to make changes to the files.
+
+However some MCP servers may be running from installed packages rather than a local repository, in which case it may make more sense to create a new MCP server.
+
+# MCP Servers Are Not Always Necessary
+
+The user may not always request the use or creation of MCP servers. Instead, they might provide tasks that can be completed with existing tools. While using the MCP SDK to extend your capabilities can be useful, it's important to understand that this is just one specialized type of task you can accomplish. You should only implement MCP servers when the user explicitly requests it (e.g., "add a tool that...").
+
+Remember: The MCP documentation and example provided above are to help you understand and work with existing MCP servers or create new ones when requested by the user. You already have access to tools and capabilities that can be used to accomplish a wide range of tasks.`,
+	},
+	"create-mode": {
+		name: "create-mode",
+		description:
+			"Instructions for creating custom modes in Roo Code. Use when the user asks to create a new mode, edit an existing mode, or configure mode settings.",
+		instructions: `Custom modes can be configured in two ways:
+
+1. Globally via the custom modes file in your Roo Code settings directory (typically ~/.roo-code/settings/custom_modes.yaml on macOS/Linux or %APPDATA%\\roo-code\\settings\\custom_modes.yaml on Windows) - created automatically on startup
+2. Per-workspace via '.roomodes' in the workspace root directory
+
+When modes with the same slug exist in both files, the workspace-specific .roomodes version takes precedence. This allows projects to override global modes or define project-specific modes.
+
+If asked to create a project mode, create it in .roomodes in the workspace root. If asked to create a global mode, use the global custom modes file.
+
+- The following fields are required and must not be empty:
+
+    - slug: A valid slug (lowercase letters, numbers, and hyphens). Must be unique, and shorter is better.
+    - name: The display name for the mode
+    - roleDefinition: A detailed description of the mode's role and capabilities
+    - groups: Array of allowed tool groups (can be empty). Each group can be specified either as a string (e.g., "edit" to allow editing any file) or with file restrictions (e.g., ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }] to only allow editing markdown files)
+
+- The following fields are optional but highly recommended:
+
+    - description: A short, human-readable description of what this mode does (5 words)
+    - whenToUse: A clear description of when this mode should be selected and what types of tasks it's best suited for. This helps the Orchestrator mode make better decisions.
+    - customInstructions: Additional instructions for how the mode should operate
+
+- For multi-line text, include newline characters in the string like "This is the first line.\\nThis is the next line.\\n\\nThis is a double line break."
+
+Both files should follow this structure (in YAML format):
+
+customModes:
+
+- slug: designer # Required: unique slug with lowercase letters, numbers, and hyphens
+  name: Designer # Required: mode display name
+  description: UI/UX design systems expert # Optional but recommended: short description (5 words)
+  roleDefinition: >-
+  You are Roo, a UI/UX expert specializing in design systems and frontend development. Your expertise includes:
+    - Creating and maintaining design systems
+    - Implementing responsive and accessible web interfaces
+    - Working with CSS, HTML, and modern frontend frameworks
+    - Ensuring consistent user experiences across platforms # Required: non-empty
+      whenToUse: >-
+      Use this mode when creating or modifying UI components, implementing design systems,
+      or ensuring responsive web interfaces. This mode is especially effective with CSS,
+      HTML, and modern frontend frameworks. # Optional but recommended
+      groups: # Required: array of tool groups (can be empty)
+    - read # Read files group (read_file, search_files, list_files, codebase_search)
+    - edit # Edit files group (apply_diff, write_to_file) - allows editing any file
+    # Or with file restrictions:
+    # - - edit
+    # - fileRegex: \\.md$
+    # description: Markdown files only # Edit group that only allows editing markdown files
+    - browser # Browser group (browser_action)
+    - command # Command group (execute_command)
+    - mcp # MCP group (use_mcp_tool, access_mcp_resource)
+      customInstructions: Additional instructions for the Designer mode # Optional`,
+	},
+}
+
+/**
+ * Get all built-in skills as SkillMetadata objects
+ */
+export function getBuiltInSkills(): SkillMetadata[] {
+	return Object.values(BUILT_IN_SKILLS).map((skill) => ({
+		name: skill.name,
+		description: skill.description,
+		path: "built-in",
+		source: "built-in" as const,
+	}))
+}
+
+/**
+ * Get a specific built-in skill's full content by name
+ */
+export function getBuiltInSkillContent(name: string): SkillContent | null {
+	const skill = BUILT_IN_SKILLS[name]
+	if (!skill) return null
+
+	return {
+		name: skill.name,
+		description: skill.description,
+		path: "built-in",
+		source: "built-in" as const,
+		instructions: skill.instructions,
+	}
+}
+
+/**
+ * Check if a skill name is a built-in skill
+ */
+export function isBuiltInSkill(name: string): boolean {
+	return name in BUILT_IN_SKILLS
+}
+
+/**
+ * Get names of all built-in skills
+ */
+export function getBuiltInSkillNames(): string[] {
+	return Object.keys(BUILT_IN_SKILLS)
+}

+ 172 - 189
src/core/prompts/instructions/create-mcp-server.ts → src/services/skills/built-in/create-mcp-server/SKILL.md

@@ -1,24 +1,21 @@
-import { McpHub } from "../../../services/mcp/McpHub"
-import { DiffStrategy } from "../../../shared/tools"
+---
+name: create-mcp-server
+description: Instructions for creating MCP (Model Context Protocol) servers that expose tools and resources for the agent to use. Use when the user asks to create a new MCP server or add MCP capabilities.
+---
 
-export async function createMCPServerInstructions(
-	mcpHub: McpHub | undefined,
-	diffStrategy: DiffStrategy | undefined,
-): Promise<string> {
-	if (!diffStrategy || !mcpHub) throw new Error("Missing MCP Hub or Diff Strategy")
-
-	return `You have the ability to create an MCP server and add it to a configuration file that will then expose the tools and resources for you to use with \`use_mcp_tool\` and \`access_mcp_resource\`.
+You have the ability to create an MCP server and add it to a configuration file that will then expose the tools and resources for you to use with `use_mcp_tool` and `access_mcp_resource`.
 
 When creating MCP servers, it's important to understand that they operate in a non-interactive environment. The server cannot initiate OAuth flows, open browser windows, or prompt for user input during runtime. All credentials and authentication tokens must be provided upfront through environment variables in the MCP settings configuration. For example, Spotify's API uses OAuth to get a refresh token for the user, but the MCP server cannot initiate this flow. While you can walk the user through obtaining an application client ID and secret, you may have to create a separate one-time setup script (like get-refresh-token.js) that captures and logs the final piece of the puzzle: the user's refresh token (i.e. you might run the script using execute_command which would open a browser for authentication, and then log the refresh token so that you can see it in the command output for you to use in the MCP settings configuration).
 
-Unless the user specifies otherwise, new local MCP servers should be created in: ${await mcpHub.getMcpServersPath()}
+Unless the user specifies otherwise, new local MCP servers should be created in your MCP servers directory. You can find the path to this directory by checking the MCP settings file, or ask the user where they'd like the server created.
 
 ### MCP Server Types and Configuration
 
 MCP servers can be configured in two ways in the MCP settings file:
 
 1. Local (Stdio) Server Configuration:
-\`\`\`json
+
+```json
 {
 	"mcpServers": {
 		"local-weather": {
@@ -30,10 +27,11 @@ MCP servers can be configured in two ways in the MCP settings file:
 		}
 	}
 }
-\`\`\`
+```
 
 2. Remote (SSE) Server Configuration:
-\`\`\`json
+
+```json
 {
 	"mcpServers": {
 		"remote-weather": {
@@ -44,13 +42,14 @@ MCP servers can be configured in two ways in the MCP settings file:
 		}
 	}
 }
-\`\`\`
+```
 
 Common configuration options for both types:
-- \`disabled\`: (optional) Set to true to temporarily disable the server
-- \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60)
-- \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation
-- \`disabledTools\`: (optional) Array of tool names that are not included in the system prompt and won't be used
+
+- `disabled`: (optional) Set to true to temporarily disable the server
+- `timeout`: (optional) Maximum time in seconds to wait for server responses (default: 60)
+- `alwaysAllow`: (optional) Array of tool names that don't require user confirmation
+- `disabledTools`: (optional) Array of tool names that are not included in the system prompt and won't be used
 
 ### Example Local MCP Server
 
@@ -58,19 +57,19 @@ For example, if the user wanted to give you the ability to retrieve weather info
 
 The following example demonstrates how to build a local MCP server that provides weather data functionality using the Stdio transport. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS)
 
-1. Use the \`create-typescript-server\` tool to bootstrap a new project in the default MCP servers directory:
+1. Use the `create-typescript-server` tool to bootstrap a new project in your MCP servers directory:
 
-\`\`\`bash
-cd ${await mcpHub.getMcpServersPath()}
+```bash
+cd /path/to/your/mcp-servers
 npx @modelcontextprotocol/create-server weather-server
 cd weather-server
 # Install dependencies
 npm install axios zod @modelcontextprotocol/sdk
-\`\`\`
+```
 
 This will create a new project with the following structure:
 
-\`\`\`
+```
 weather-server/
 	├── package.json
 			{
@@ -85,201 +84,193 @@ weather-server/
 	├── tsconfig.json
 	└── src/
 			└── index.ts      # Main server implementation
-\`\`\`
+```
 
-2. Replace \`src/index.ts\` with the following:
+2. Replace `src/index.ts` with the following:
 
-\`\`\`typescript
+```typescript
 #!/usr/bin/env node
-import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
-import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
-import { z } from "zod";
-import axios from 'axios';
+import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"
+import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
+import { z } from "zod"
+import axios from "axios"
 
-const API_KEY = process.env.OPENWEATHER_API_KEY; // provided by MCP config
+const API_KEY = process.env.OPENWEATHER_API_KEY // provided by MCP config
 if (!API_KEY) {
-  throw new Error('OPENWEATHER_API_KEY environment variable is required');
+	throw new Error("OPENWEATHER_API_KEY environment variable is required")
 }
 
 // Define types for OpenWeather API responses
 interface WeatherData {
-  main: {
-    temp: number;
-    humidity: number;
-  };
-  weather: Array<{
-    description: string;
-  }>;
-  wind: {
-    speed: number;
-  };
+	main: {
+		temp: number
+		humidity: number
+	}
+	weather: Array<{
+		description: string
+	}>
+	wind: {
+		speed: number
+	}
 }
 
 interface ForecastData {
-  list: Array<WeatherData & {
-    dt_txt: string;
-  }>;
+	list: Array<
+		WeatherData & {
+			dt_txt: string
+		}
+	>
 }
 
 // Create an MCP server
 const server = new McpServer({
-  name: "weather-server",
-  version: "0.1.0"
-});
+	name: "weather-server",
+	version: "0.1.0",
+})
 
 // Create axios instance for OpenWeather API
 const weatherApi = axios.create({
-  baseURL: 'http://api.openweathermap.org/data/2.5',
-  params: {
-    appid: API_KEY,
-    units: 'metric',
-  },
-});
+	baseURL: "http://api.openweathermap.org/data/2.5",
+	params: {
+		appid: API_KEY,
+		units: "metric",
+	},
+})
 
 // Add a tool for getting weather forecasts
 server.tool(
-  "get_forecast",
-  {
-    city: z.string().describe("City name"),
-    days: z.number().min(1).max(5).optional().describe("Number of days (1-5)"),
-  },
-  async ({ city, days = 3 }) => {
-    try {
-      const response = await weatherApi.get<ForecastData>('forecast', {
-        params: {
-          q: city,
-          cnt: Math.min(days, 5) * 8,
-        },
-      });
-
-      return {
-        content: [
-          {
-            type: "text",
-            text: JSON.stringify(response.data.list, null, 2),
-          },
-        ],
-      };
-    } catch (error) {
-      if (axios.isAxiosError(error)) {
-        return {
-          content: [
-            {
-              type: "text",
-              text: \`Weather API error: \${
-                error.response?.data.message ?? error.message
-              }\`,
-            },
-          ],
-          isError: true,
-        };
-      }
-      throw error;
-    }
-  }
-);
+	"get_forecast",
+	{
+		city: z.string().describe("City name"),
+		days: z.number().min(1).max(5).optional().describe("Number of days (1-5)"),
+	},
+	async ({ city, days = 3 }) => {
+		try {
+			const response = await weatherApi.get<ForecastData>("forecast", {
+				params: {
+					q: city,
+					cnt: Math.min(days, 5) * 8,
+				},
+			})
+
+			return {
+				content: [
+					{
+						type: "text",
+						text: JSON.stringify(response.data.list, null, 2),
+					},
+				],
+			}
+		} catch (error) {
+			if (axios.isAxiosError(error)) {
+				return {
+					content: [
+						{
+							type: "text",
+							text: `Weather API error: ${error.response?.data.message ?? error.message}`,
+						},
+					],
+					isError: true,
+				}
+			}
+			throw error
+		}
+	},
+)
 
 // Add a resource for current weather in San Francisco
-server.resource(
-  "sf_weather",
-  { uri: "weather://San Francisco/current", list: true },
-  async (uri) => {
-    try {
-      const response = weatherApi.get<WeatherData>('weather', {
-        params: { q: "San Francisco" },
-      });
-
-      return {
-        contents: [
-          {
-            uri: uri.href,
-            mimeType: "application/json",
-            text: JSON.stringify(
-              {
-                temperature: response.data.main.temp,
-                conditions: response.data.weather[0].description,
-                humidity: response.data.main.humidity,
-                wind_speed: response.data.wind.speed,
-                timestamp: new Date().toISOString(),
-              },
-              null,
-              2
-            ),
-          },
-        ],
-      };
-    } catch (error) {
-      if (axios.isAxiosError(error)) {
-        throw new Error(\`Weather API error: \${
-          error.response?.data.message ?? error.message
-        }\`);
-      }
-      throw error;
-    }
-  }
-);
+server.resource("sf_weather", { uri: "weather://San Francisco/current", list: true }, async (uri) => {
+	try {
+		const response = weatherApi.get<WeatherData>("weather", {
+			params: { q: "San Francisco" },
+		})
+
+		return {
+			contents: [
+				{
+					uri: uri.href,
+					mimeType: "application/json",
+					text: JSON.stringify(
+						{
+							temperature: response.data.main.temp,
+							conditions: response.data.weather[0].description,
+							humidity: response.data.main.humidity,
+							wind_speed: response.data.wind.speed,
+							timestamp: new Date().toISOString(),
+						},
+						null,
+						2,
+					),
+				},
+			],
+		}
+	} catch (error) {
+		if (axios.isAxiosError(error)) {
+			throw new Error(`Weather API error: ${error.response?.data.message ?? error.message}`)
+		}
+		throw error
+	}
+})
 
 // Add a dynamic resource template for current weather by city
 server.resource(
-  "current_weather",
-  new ResourceTemplate("weather://{city}/current", { list: true }),
-  async (uri, { city }) => {
-    try {
-      const response = await weatherApi.get('weather', {
-        params: { q: city },
-      });
-
-      return {
-        contents: [
-          {
-            uri: uri.href,
-            mimeType: "application/json",
-            text: JSON.stringify(
-              {
-                temperature: response.data.main.temp,
-                conditions: response.data.weather[0].description,
-                humidity: response.data.main.humidity,
-                wind_speed: response.data.wind.speed,
-                timestamp: new Date().toISOString(),
-              },
-              null,
-              2
-            ),
-          },
-        ],
-      };
-    } catch (error) {
-      if (axios.isAxiosError(error)) {
-        throw new Error(\`Weather API error: \${
-          error.response?.data.message ?? error.message
-        }\`);
-      }
-      throw error;
-    }
-  }
-);
+	"current_weather",
+	new ResourceTemplate("weather://{city}/current", { list: true }),
+	async (uri, { city }) => {
+		try {
+			const response = await weatherApi.get("weather", {
+				params: { q: city },
+			})
+
+			return {
+				contents: [
+					{
+						uri: uri.href,
+						mimeType: "application/json",
+						text: JSON.stringify(
+							{
+								temperature: response.data.main.temp,
+								conditions: response.data.weather[0].description,
+								humidity: response.data.main.humidity,
+								wind_speed: response.data.wind.speed,
+								timestamp: new Date().toISOString(),
+							},
+							null,
+							2,
+						),
+					},
+				],
+			}
+		} catch (error) {
+			if (axios.isAxiosError(error)) {
+				throw new Error(`Weather API error: ${error.response?.data.message ?? error.message}`)
+			}
+			throw error
+		}
+	},
+)
 
 // Start receiving messages on stdin and sending messages on stdout
-const transport = new StdioServerTransport();
-await server.connect(transport);
-console.error('Weather MCP server running on stdio');
-\`\`\`
+const transport = new StdioServerTransport()
+await server.connect(transport)
+console.error("Weather MCP server running on stdio")
+```
 
 (Remember: This is just an example–you may use different dependencies, break the implementation up into multiple files, etc.)
 
 3. Build and compile the executable JavaScript file
 
-\`\`\`bash
+```bash
 npm run build
-\`\`\`
+```
 
 4. Whenever you need an environment variable such as an API key to configure the MCP server, walk the user through the process of getting the key. For example, they may need to create an account and go to a developer dashboard to generate the key. Provide step-by-step instructions and URLs to make it easy for the user to retrieve the necessary information. Then use the ask_followup_question tool to ask the user for the key, in this case the OpenWeather API key.
 
-5. Install the MCP Server by adding the MCP server configuration to the settings file located at '${await mcpHub.getMcpSettingsFilePath()}'. The settings file may have other MCP servers already configured, so you would read it first and then add your new server to the existing \`mcpServers\` object.
+5. Install the MCP Server by adding the MCP server configuration to the MCP settings file. On macOS/Linux this is typically at `~/.roo-code/settings/mcp_settings.json`, on Windows at `%APPDATA%\roo-code\settings\mcp_settings.json`. The settings file may have other MCP servers already configured, so you would read it first and then add your new server to the existing `mcpServers` object.
 
 IMPORTANT: Regardless of what else you see in the MCP settings file, you must default any new MCP servers you create to disabled=false, alwaysAllow=[] and disabledTools=[].
 
-\`\`\`json
+```json
 {
 	"mcpServers": {
 		...,
@@ -292,9 +283,9 @@ IMPORTANT: Regardless of what else you see in the MCP settings file, you must de
 		},
 	}
 }
-\`\`\`
+```
 
-(Note: the user may also ask you to install the MCP server to the Claude desktop app, in which case you would read then modify \`~/Library/Application\ Support/Claude/claude_desktop_config.json\` on macOS for example. It follows the same format of a top level \`mcpServers\` object.)
+(Note: the user may also ask you to install the MCP server to the Claude desktop app, in which case you would read then modify `~/Library/Application\ Support/Claude/claude_desktop_config.json` on macOS for example. It follows the same format of a top level `mcpServers` object.)
 
 6. After you have edited the MCP settings configuration file, the system will automatically run all the servers and expose the available tools and resources in the 'Connected MCP Servers' section.
 
@@ -302,14 +293,7 @@ IMPORTANT: Regardless of what else you see in the MCP settings file, you must de
 
 ## Editing MCP Servers
 
-The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' above: ${(() => {
-		if (!mcpHub) return "(None running currently)"
-		const servers = mcpHub
-			.getServers()
-			.map((server) => server.name)
-			.join(", ")
-		return servers || "(None running currently)"
-	})()}, e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use write_to_file${diffStrategy ? " or apply_diff" : ""} to make changes to the files.
+The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' in the system prompt), e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use write_to_file or apply_diff to make changes to the files.
 
 However some MCP servers may be running from installed packages rather than a local repository, in which case it may make more sense to create a new MCP server.
 
@@ -317,5 +301,4 @@ However some MCP servers may be running from installed packages rather than a lo
 
 The user may not always request the use or creation of MCP servers. Instead, they might provide tasks that can be completed with existing tools. While using the MCP SDK to extend your capabilities can be useful, it's important to understand that this is just one specialized type of task you can accomplish. You should only implement MCP servers when the user explicitly requests it (e.g., "add a tool that...").
 
-Remember: The MCP documentation and example provided above are to help you understand and work with existing MCP servers or create new ones when requested by the user. You already have access to tools and capabilities that can be used to accomplish a wide range of tasks.`
-}
+Remember: The MCP documentation and example provided above are to help you understand and work with existing MCP servers or create new ones when requested by the user. You already have access to tools and capabilities that can be used to accomplish a wide range of tasks.

+ 57 - 0
src/services/skills/built-in/create-mode/SKILL.md

@@ -0,0 +1,57 @@
+---
+name: create-mode
+description: Instructions for creating custom modes in Roo Code. Use when the user asks to create a new mode, edit an existing mode, or configure mode settings.
+---
+
+Custom modes can be configured in two ways:
+
+1. Globally via the custom modes file in your Roo Code settings directory (typically ~/.roo-code/settings/custom_modes.yaml on macOS/Linux or %APPDATA%\roo-code\settings\custom_modes.yaml on Windows) - created automatically on startup
+2. Per-workspace via '.roomodes' in the workspace root directory
+
+When modes with the same slug exist in both files, the workspace-specific .roomodes version takes precedence. This allows projects to override global modes or define project-specific modes.
+
+If asked to create a project mode, create it in .roomodes in the workspace root. If asked to create a global mode, use the global custom modes file.
+
+- The following fields are required and must not be empty:
+
+    - slug: A valid slug (lowercase letters, numbers, and hyphens). Must be unique, and shorter is better.
+    - name: The display name for the mode
+    - roleDefinition: A detailed description of the mode's role and capabilities
+    - groups: Array of allowed tool groups (can be empty). Each group can be specified either as a string (e.g., "edit" to allow editing any file) or with file restrictions (e.g., ["edit", { fileRegex: "\.md$", description: "Markdown files only" }] to only allow editing markdown files)
+
+- The following fields are optional but highly recommended:
+
+    - description: A short, human-readable description of what this mode does (5 words)
+    - whenToUse: A clear description of when this mode should be selected and what types of tasks it's best suited for. This helps the Orchestrator mode make better decisions.
+    - customInstructions: Additional instructions for how the mode should operate
+
+- For multi-line text, include newline characters in the string like "This is the first line.\nThis is the next line.\n\nThis is a double line break."
+
+Both files should follow this structure (in YAML format):
+
+customModes:
+
+- slug: designer # Required: unique slug with lowercase letters, numbers, and hyphens
+  name: Designer # Required: mode display name
+  description: UI/UX design systems expert # Optional but recommended: short description (5 words)
+  roleDefinition: >-
+  You are Roo, a UI/UX expert specializing in design systems and frontend development. Your expertise includes:
+    - Creating and maintaining design systems
+    - Implementing responsive and accessible web interfaces
+    - Working with CSS, HTML, and modern frontend frameworks
+    - Ensuring consistent user experiences across platforms # Required: non-empty
+      whenToUse: >-
+      Use this mode when creating or modifying UI components, implementing design systems,
+      or ensuring responsive web interfaces. This mode is especially effective with CSS,
+      HTML, and modern frontend frameworks. # Optional but recommended
+      groups: # Required: array of tool groups (can be empty)
+    - read # Read files group (read_file, search_files, list_files, codebase_search)
+    - edit # Edit files group (apply_diff, write_to_file) - allows editing any file
+    # Or with file restrictions:
+    # - - edit
+    # - fileRegex: \.md$
+    # description: Markdown files only # Edit group that only allows editing markdown files
+    - browser # Browser group (browser_action)
+    - command # Command group (execute_command)
+    - mcp # MCP group (use_mcp_tool, access_mcp_resource)
+      customInstructions: Additional instructions for the Designer mode # Optional

+ 300 - 0
src/services/skills/generate-built-in-skills.ts

@@ -0,0 +1,300 @@
+#!/usr/bin/env tsx
+/**
+ * Build script to generate built-in-skills.ts from SKILL.md files.
+ *
+ * This script scans the built-in/ directory for skill folders, parses each
+ * SKILL.md file using gray-matter, validates the frontmatter, and generates
+ * the built-in-skills.ts file.
+ *
+ * Run with: npx tsx src/services/skills/generate-built-in-skills.ts
+ */
+
+import * as fs from "fs/promises"
+import * as path from "path"
+import { execSync } from "child_process"
+import matter from "gray-matter"
+
+const BUILT_IN_DIR = path.join(__dirname, "built-in")
+const OUTPUT_FILE = path.join(__dirname, "built-in-skills.ts")
+
+interface SkillData {
+	name: string
+	description: string
+	instructions: string
+}
+
+interface ValidationError {
+	skillDir: string
+	errors: string[]
+}
+
+/**
+ * Validate a skill name according to Agent Skills spec:
+ * - 1-64 characters
+ * - lowercase letters, numbers, and hyphens only
+ * - must not start/end with hyphen
+ * - must not contain consecutive hyphens
+ */
+function validateSkillName(name: string): string[] {
+	const errors: string[] = []
+
+	if (name.length < 1 || name.length > 64) {
+		errors.push(`Name must be 1-64 characters (got ${name.length})`)
+	}
+
+	const nameFormat = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
+	if (!nameFormat.test(name)) {
+		errors.push(
+			"Name must be lowercase letters/numbers/hyphens only (no leading/trailing hyphen, no consecutive hyphens)",
+		)
+	}
+
+	return errors
+}
+
+/**
+ * Validate a skill description:
+ * - 1-1024 characters (after trimming)
+ */
+function validateDescription(description: string): string[] {
+	const errors: string[] = []
+	const trimmed = description.trim()
+
+	if (trimmed.length < 1 || trimmed.length > 1024) {
+		errors.push(`Description must be 1-1024 characters (got ${trimmed.length})`)
+	}
+
+	return errors
+}
+
+/**
+ * Parse and validate a single SKILL.md file
+ */
+async function parseSkillFile(
+	skillDir: string,
+	dirName: string,
+): Promise<{ skill?: SkillData; errors?: ValidationError }> {
+	const skillMdPath = path.join(skillDir, "SKILL.md")
+
+	try {
+		const fileContent = await fs.readFile(skillMdPath, "utf-8")
+		const { data: frontmatter, content: body } = matter(fileContent)
+
+		const errors: string[] = []
+
+		// Validate required fields
+		if (!frontmatter.name || typeof frontmatter.name !== "string") {
+			errors.push("Missing required 'name' field in frontmatter")
+		}
+		if (!frontmatter.description || typeof frontmatter.description !== "string") {
+			errors.push("Missing required 'description' field in frontmatter")
+		}
+
+		if (errors.length > 0) {
+			return { errors: { skillDir, errors } }
+		}
+
+		// Validate name matches directory name
+		if (frontmatter.name !== dirName) {
+			errors.push(`Frontmatter name "${frontmatter.name}" doesn't match directory name "${dirName}"`)
+		}
+
+		// Validate name format
+		errors.push(...validateSkillName(dirName))
+
+		// Validate description
+		errors.push(...validateDescription(frontmatter.description))
+
+		if (errors.length > 0) {
+			return { errors: { skillDir, errors } }
+		}
+
+		return {
+			skill: {
+				name: frontmatter.name,
+				description: frontmatter.description.trim(),
+				instructions: body.trim(),
+			},
+		}
+	} catch (error) {
+		return {
+			errors: {
+				skillDir,
+				errors: [`Failed to read or parse SKILL.md: ${error instanceof Error ? error.message : String(error)}`],
+			},
+		}
+	}
+}
+
+/**
+ * Escape a string for use in TypeScript template literal
+ */
+function escapeForTemplateLiteral(str: string): string {
+	return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${")
+}
+
+/**
+ * Generate the TypeScript code for built-in-skills.ts
+ */
+function generateTypeScript(skills: Record<string, SkillData>): string {
+	const skillEntries = Object.entries(skills)
+		.map(([key, skill]) => {
+			const escapedInstructions = escapeForTemplateLiteral(skill.instructions)
+			return `\t"${key}": {
+		name: "${skill.name}",
+		description: "${skill.description.replace(/"/g, '\\"')}",
+		instructions: \`${escapedInstructions}\`,
+	}`
+		})
+		.join(",\n")
+
+	return `/**
+	* AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
+	*
+	* This file is generated by generate-built-in-skills.ts from the SKILL.md files
+	* in the built-in/ directory. To modify built-in skills, edit the corresponding
+	* SKILL.md file and run: pnpm generate:skills
+	*/
+
+import { SkillMetadata, SkillContent } from "../../shared/skills"
+
+interface BuiltInSkillDefinition {
+	name: string
+	description: string
+	instructions: string
+}
+
+const BUILT_IN_SKILLS: Record<string, BuiltInSkillDefinition> = {
+${skillEntries}
+}
+
+/**
+ * Get all built-in skills as SkillMetadata objects
+ */
+export function getBuiltInSkills(): SkillMetadata[] {
+	return Object.values(BUILT_IN_SKILLS).map((skill) => ({
+		name: skill.name,
+		description: skill.description,
+		path: "built-in",
+		source: "built-in" as const,
+	}))
+}
+
+/**
+ * Get a specific built-in skill's full content by name
+ */
+export function getBuiltInSkillContent(name: string): SkillContent | null {
+	const skill = BUILT_IN_SKILLS[name]
+	if (!skill) return null
+
+	return {
+		name: skill.name,
+		description: skill.description,
+		path: "built-in",
+		source: "built-in" as const,
+		instructions: skill.instructions,
+	}
+}
+
+/**
+ * Check if a skill name is a built-in skill
+ */
+export function isBuiltInSkill(name: string): boolean {
+	return name in BUILT_IN_SKILLS
+}
+
+/**
+ * Get names of all built-in skills
+ */
+export function getBuiltInSkillNames(): string[] {
+	return Object.keys(BUILT_IN_SKILLS)
+}
+`
+}
+
+async function main() {
+	console.log("Generating built-in skills from SKILL.md files...")
+
+	// Check if built-in directory exists
+	try {
+		await fs.access(BUILT_IN_DIR)
+	} catch {
+		console.error(`Error: Built-in skills directory not found: ${BUILT_IN_DIR}`)
+		process.exit(1)
+	}
+
+	// Scan for skill directories
+	const entries = await fs.readdir(BUILT_IN_DIR)
+	const skills: Record<string, SkillData> = {}
+	const validationErrors: ValidationError[] = []
+
+	for (const entry of entries) {
+		const skillDir = path.join(BUILT_IN_DIR, entry)
+		const stats = await fs.stat(skillDir)
+
+		if (!stats.isDirectory()) {
+			continue
+		}
+
+		// Check if SKILL.md exists
+		const skillMdPath = path.join(skillDir, "SKILL.md")
+		try {
+			await fs.access(skillMdPath)
+		} catch {
+			console.warn(`Warning: No SKILL.md found in ${entry}, skipping`)
+			continue
+		}
+
+		const result = await parseSkillFile(skillDir, entry)
+
+		if (result.errors) {
+			validationErrors.push(result.errors)
+		} else if (result.skill) {
+			skills[entry] = result.skill
+			console.log(`  ✓ Parsed ${entry}`)
+		}
+	}
+
+	// Report validation errors
+	if (validationErrors.length > 0) {
+		console.error("\nValidation errors:")
+		for (const { skillDir, errors } of validationErrors) {
+			console.error(`\n  ${path.basename(skillDir)}:`)
+			for (const error of errors) {
+				console.error(`    - ${error}`)
+			}
+		}
+		process.exit(1)
+	}
+
+	// Check if any skills were found
+	if (Object.keys(skills).length === 0) {
+		console.error("Error: No valid skills found in built-in directory")
+		process.exit(1)
+	}
+
+	// Generate TypeScript
+	const output = generateTypeScript(skills)
+
+	// Write output file
+	await fs.writeFile(OUTPUT_FILE, output, "utf-8")
+
+	// Format with prettier to ensure stable output
+	// Run from workspace root (3 levels up from src/services/skills/) to find .prettierrc.json
+	const workspaceRoot = path.resolve(__dirname, "..", "..", "..")
+	try {
+		execSync(`npx prettier --write "${OUTPUT_FILE}"`, {
+			cwd: workspaceRoot,
+			stdio: "pipe",
+		})
+		console.log(`\n✓ Generated and formatted ${OUTPUT_FILE}`)
+	} catch {
+		console.log(`\n✓ Generated ${OUTPUT_FILE} (prettier not available)`)
+	}
+	console.log(`  Skills: ${Object.keys(skills).join(", ")}`)
+}
+
+main().catch((error) => {
+	console.error("Fatal error:", error)
+	process.exit(1)
+})

+ 2 - 2
src/shared/skills.ts

@@ -5,8 +5,8 @@
 export interface SkillMetadata {
 	name: string // Required: skill identifier
 	description: string // Required: when to use this skill
-	path: string // Absolute path to SKILL.md
-	source: "global" | "project" // Where the skill was discovered
+	path: string // Absolute path to SKILL.md (or "<built-in:name>" for built-in skills)
+	source: "global" | "project" | "built-in" // Where the skill was discovered
 	mode?: string // If set, skill is only available in this mode
 }
 

+ 10 - 8
src/shared/tools.ts

@@ -60,6 +60,7 @@ export const toolParamNames = [
 	"size",
 	"query",
 	"args",
+	"skill", // skill tool parameter
 	"start_line",
 	"end_line",
 	"todos",
@@ -103,9 +104,9 @@ export type NativeToolArgs = {
 	}
 	browser_action: BrowserActionParams
 	codebase_search: { query: string; path?: string }
-	fetch_instructions: { task: string }
 	generate_image: GenerateImageParams
 	run_slash_command: { command: string; args?: string }
+	skill: { skill: string; args?: string | null }
 	search_files: { path: string; regex: string; file_pattern?: string | null }
 	switch_mode: { mode_slug: string; reason: string }
 	update_todo_list: { todos: string }
@@ -167,11 +168,6 @@ export interface ReadFileToolUse extends ToolUse<"read_file"> {
 	params: Partial<Pick<Record<ToolParamName, string>, "args" | "path" | "start_line" | "end_line" | "files">>
 }
 
-export interface FetchInstructionsToolUse extends ToolUse<"fetch_instructions"> {
-	name: "fetch_instructions"
-	params: Partial<Pick<Record<ToolParamName, string>, "task">>
-}
-
 export interface WriteToFileToolUse extends ToolUse<"write_to_file"> {
 	name: "write_to_file"
 	params: Partial<Pick<Record<ToolParamName, string>, "path" | "content">>
@@ -232,6 +228,11 @@ export interface RunSlashCommandToolUse extends ToolUse<"run_slash_command"> {
 	params: Partial<Pick<Record<ToolParamName, string>, "command" | "args">>
 }
 
+export interface SkillToolUse extends ToolUse<"skill"> {
+	name: "skill"
+	params: Partial<Pick<Record<ToolParamName, string>, "skill" | "args">>
+}
+
 export interface GenerateImageToolUse extends ToolUse<"generate_image"> {
 	name: "generate_image"
 	params: Partial<Pick<Record<ToolParamName, string>, "prompt" | "path" | "image">>
@@ -248,7 +249,6 @@ export const TOOL_DISPLAY_NAMES: Record<ToolName, string> = {
 	execute_command: "run commands",
 	read_file: "read files",
 	read_command_output: "read command output",
-	fetch_instructions: "fetch instructions",
 	write_to_file: "write files",
 	apply_diff: "apply changes",
 	search_and_replace: "apply changes using search and replace",
@@ -267,6 +267,7 @@ export const TOOL_DISPLAY_NAMES: Record<ToolName, string> = {
 	codebase_search: "codebase search",
 	update_todo_list: "update todo list",
 	run_slash_command: "run slash command",
+	skill: "load skill",
 	generate_image: "generate images",
 	custom_tool: "use custom tools",
 } as const
@@ -274,7 +275,7 @@ export const TOOL_DISPLAY_NAMES: Record<ToolName, string> = {
 // Define available tool groups.
 export const TOOL_GROUPS: Record<ToolGroup, ToolGroupConfig> = {
 	read: {
-		tools: ["read_file", "fetch_instructions", "search_files", "list_files", "codebase_search"],
+		tools: ["read_file", "search_files", "list_files", "codebase_search"],
 	},
 	edit: {
 		tools: ["apply_diff", "write_to_file", "generate_image"],
@@ -303,6 +304,7 @@ export const ALWAYS_AVAILABLE_TOOLS: ToolName[] = [
 	"new_task",
 	"update_todo_list",
 	"run_slash_command",
+	"skill",
 ] as const
 
 /**

+ 62 - 11
webview-ui/src/components/chat/ChatRow.tsx

@@ -666,24 +666,75 @@ export const ChatRowContent = ({
 						</div>
 					</>
 				)
-			case "fetchInstructions":
+			case "skill": {
+				const skillInfo = tool
 				return (
 					<>
 						<div style={headerStyle}>
-							{toolIcon("file-code")}
-							<span style={{ fontWeight: "bold" }}>{t("chat:instructions.wantsToFetch")}</span>
+							{toolIcon("book")}
+							<span style={{ fontWeight: "bold" }}>
+								{message.type === "ask" ? t("chat:skill.wantsToLoad") : t("chat:skill.didLoad")}
+							</span>
 						</div>
-						<div className="pl-6">
-							<CodeAccordian
-								code={tool.content}
-								language="markdown"
-								isLoading={message.partial}
-								isExpanded={isExpanded}
-								onToggleExpand={handleToggleExpand}
-							/>
+						<div
+							style={{
+								marginTop: "4px",
+								backgroundColor: "var(--vscode-editor-background)",
+								border: "1px solid var(--vscode-editorGroup-border)",
+								borderRadius: "4px",
+								overflow: "hidden",
+								cursor: "pointer",
+							}}
+							onClick={handleToggleExpand}>
+							<ToolUseBlockHeader
+								className="group"
+								style={{
+									display: "flex",
+									alignItems: "center",
+									justifyContent: "space-between",
+									padding: "10px 12px",
+								}}>
+								<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
+									<span style={{ fontWeight: "500", fontSize: "var(--vscode-font-size)" }}>
+										{skillInfo.skill}
+									</span>
+									{skillInfo.source && (
+										<VSCodeBadge style={{ fontSize: "calc(var(--vscode-font-size) - 2px)" }}>
+											{skillInfo.source}
+										</VSCodeBadge>
+									)}
+								</div>
+								<span
+									className={`codicon codicon-chevron-${isExpanded ? "up" : "down"} opacity-0 group-hover:opacity-100 transition-opacity duration-200`}></span>
+							</ToolUseBlockHeader>
+							{isExpanded && (skillInfo.args || skillInfo.description) && (
+								<div
+									style={{
+										padding: "12px 16px",
+										borderTop: "1px solid var(--vscode-editorGroup-border)",
+										display: "flex",
+										flexDirection: "column",
+										gap: "8px",
+									}}>
+									{skillInfo.description && (
+										<div style={{ color: "var(--vscode-descriptionForeground)" }}>
+											{skillInfo.description}
+										</div>
+									)}
+									{skillInfo.args && (
+										<div>
+											<span style={{ fontWeight: "500" }}>Arguments: </span>
+											<span style={{ color: "var(--vscode-descriptionForeground)" }}>
+												{skillInfo.args}
+											</span>
+										</div>
+									)}
+								</div>
+							)}
 						</div>
 					</>
 				)
+			}
 			case "listFilesTopLevel":
 				return (
 					<>

+ 2 - 44
webview-ui/src/components/mcp/McpView.tsx

@@ -1,12 +1,6 @@
 import React, { useState } from "react"
 import { Trans } from "react-i18next"
-import {
-	VSCodeCheckbox,
-	VSCodeLink,
-	VSCodePanels,
-	VSCodePanelTab,
-	VSCodePanelView,
-} from "@vscode/webview-ui-toolkit/react"
+import { VSCodeLink, VSCodePanels, VSCodePanelTab, VSCodePanelView } from "@vscode/webview-ui-toolkit/react"
 
 import type { McpServer } from "@roo-code/types"
 
@@ -35,13 +29,7 @@ import McpEnabledToggle from "./McpEnabledToggle"
 import { McpErrorRow } from "./McpErrorRow"
 
 const McpView = () => {
-	const {
-		mcpServers: servers,
-		alwaysAllowMcp,
-		mcpEnabled,
-		enableMcpServerCreation,
-		setEnableMcpServerCreation,
-	} = useExtensionState()
+	const { mcpServers: servers, alwaysAllowMcp, mcpEnabled } = useExtensionState()
 
 	const { t } = useAppTranslation()
 	const { isOverThreshold, title, message } = useTooManyTools()
@@ -71,36 +59,6 @@ const McpView = () => {
 
 				{mcpEnabled && (
 					<>
-						<div style={{ marginBottom: 15 }}>
-							<VSCodeCheckbox
-								checked={enableMcpServerCreation}
-								onChange={(e: any) => {
-									setEnableMcpServerCreation(e.target.checked)
-									vscode.postMessage({ type: "enableMcpServerCreation", bool: e.target.checked })
-								}}>
-								<span style={{ fontWeight: "500" }}>{t("mcp:enableServerCreation.title")}</span>
-							</VSCodeCheckbox>
-							<div
-								style={{
-									fontSize: "12px",
-									marginTop: "5px",
-									color: "var(--vscode-descriptionForeground)",
-								}}>
-								<Trans i18nKey="mcp:enableServerCreation.description">
-									<VSCodeLink
-										href={buildDocLink(
-											"features/mcp/using-mcp-in-roo#how-to-use-roo-to-create-an-mcp-server",
-											"mcp_server_creation",
-										)}
-										style={{ display: "inline" }}>
-										Learn about server creation
-									</VSCodeLink>
-									<strong>new</strong>
-								</Trans>
-								<p style={{ marginTop: "8px" }}>{t("mcp:enableServerCreation.hint")}</p>
-							</div>
-						</div>
-
 						{/* Too Many Tools Warning */}
 						{isOverThreshold && (
 							<div style={{ marginBottom: 15 }}>

+ 0 - 5
webview-ui/src/context/ExtensionStateContext.tsx

@@ -102,8 +102,6 @@ export interface ExtensionStateContextType extends ExtensionState {
 	setTerminalOutputPreviewSize: (value: "small" | "medium" | "large") => void
 	mcpEnabled: boolean
 	setMcpEnabled: (value: boolean) => void
-	enableMcpServerCreation: boolean
-	setEnableMcpServerCreation: (value: boolean) => void
 	remoteControlEnabled: boolean
 	setRemoteControlEnabled: (value: boolean) => void
 	taskSyncEnabled: boolean
@@ -213,7 +211,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		screenshotQuality: 75,
 		terminalShellIntegrationTimeout: 4000,
 		mcpEnabled: true,
-		enableMcpServerCreation: false,
 		remoteControlEnabled: false,
 		taskSyncEnabled: false,
 		featureRoomoteControlEnabled: false,
@@ -553,8 +550,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 			setState((prevState) => ({ ...prevState, terminalShellIntegrationDisabled: value })),
 		setTerminalZdotdir: (value) => setState((prevState) => ({ ...prevState, terminalZdotdir: value })),
 		setMcpEnabled: (value) => setState((prevState) => ({ ...prevState, mcpEnabled: value })),
-		setEnableMcpServerCreation: (value) =>
-			setState((prevState) => ({ ...prevState, enableMcpServerCreation: value })),
 		setRemoteControlEnabled: (value) => setState((prevState) => ({ ...prevState, remoteControlEnabled: value })),
 		setTaskSyncEnabled: (value) => setState((prevState) => ({ ...prevState, taskSyncEnabled: value }) as any),
 		setFeatureRoomoteControlEnabled: (value) =>

+ 0 - 1
webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx

@@ -187,7 +187,6 @@ describe("mergeExtensionState", () => {
 		const baseState: ExtensionState = {
 			version: "",
 			mcpEnabled: false,
-			enableMcpServerCreation: false,
 			clineMessages: [],
 			taskHistory: [],
 			shouldShowAnnouncement: false,

+ 4 - 0
webview-ui/src/i18n/locales/ca/chat.json

@@ -324,6 +324,10 @@
 			"description": "S'han eliminat missatges més antics de la conversa per mantenir-se dins del límit de la finestra de context. Aquest és un enfocament ràpid però menys conservador del context en comparació amb la condensació."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "En Roo vol carregar una habilitat",
+		"didLoad": "En Roo ha carregat una habilitat"
+	},
 	"followUpSuggest": {
 		"copyToInput": "Copiar a l'entrada (o Shift + clic)",
 		"timerPrefix": "Aprovació automàtica habilitada. Seleccionant en {{seconds}}s…"

+ 4 - 0
webview-ui/src/i18n/locales/de/chat.json

@@ -324,6 +324,10 @@
 			"description": "Ältere Nachrichten wurden aus der Konversation entfernt, um innerhalb des Kontextfenster-Limits zu bleiben. Dies ist ein schnellerer, aber weniger kontexterhaltender Ansatz im Vergleich zur Komprimierung."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo möchte eine Fähigkeit laden",
+		"didLoad": "Roo hat eine Fähigkeit geladen"
+	},
 	"followUpSuggest": {
 		"copyToInput": "In Eingabefeld kopieren (oder Shift + Klick)",
 		"timerPrefix": "Automatische Genehmigung aktiviert. Wähle in {{seconds}}s…"

+ 3 - 2
webview-ui/src/i18n/locales/en/chat.json

@@ -203,8 +203,9 @@
 			"description": "Older messages were removed from the conversation to stay within the context window limit. This is a fast but less context-preserving approach compared to condensation."
 		}
 	},
-	"instructions": {
-		"wantsToFetch": "Roo wants to fetch detailed instructions to assist with the current task"
+	"skill": {
+		"wantsToLoad": "Roo wants to load a skill",
+		"didLoad": "Roo loaded a skill"
 	},
 	"fileOperations": {
 		"wantsToRead": "Roo wants to read this file",

+ 4 - 0
webview-ui/src/i18n/locales/es/chat.json

@@ -324,6 +324,10 @@
 			"description": "Se eliminaron mensajes más antiguos de la conversación para mantenerse dentro del límite de la ventana de contexto. Este es un enfoque rápido pero menos conservador del contexto en comparación con la condensación."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo quiere cargar una habilidad",
+		"didLoad": "Roo cargó una habilidad"
+	},
 	"followUpSuggest": {
 		"copyToInput": "Copiar a la entrada (o Shift + clic)",
 		"timerPrefix": "Aprobación automática habilitada. Seleccionando en {{seconds}}s…"

+ 4 - 0
webview-ui/src/i18n/locales/fr/chat.json

@@ -324,6 +324,10 @@
 			"description": "Les messages plus anciens ont été supprimés de la conversation pour rester dans la limite de la fenêtre de contexte. C'est une approche rapide mais moins conservatrice du contexte par rapport à la condensation."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo veut charger une compétence",
+		"didLoad": "Roo a chargé une compétence"
+	},
 	"followUpSuggest": {
 		"copyToInput": "Copier vers l'entrée (ou Shift + clic)",
 		"timerPrefix": "Approbation automatique activée. Sélection dans {{seconds}}s…"

+ 4 - 0
webview-ui/src/i18n/locales/hi/chat.json

@@ -324,6 +324,10 @@
 			"description": "संदर्भ विंडो सीमा के भीतर रहने के लिए बातचीत से पुराने संदेश हटा दिए गए। संघनन की तुलना में यह एक तेज़ लेकिन कम संदर्भ-संरक्षित दृष्टिकोण है।"
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo एक कौशल लोड करना चाहता है",
+		"didLoad": "Roo ने एक कौशल लोड किया"
+	},
 	"followUpSuggest": {
 		"copyToInput": "इनपुट में कॉपी करें (या Shift + क्लिक)",
 		"timerPrefix": "ऑटो-अनुमोदन सक्षम है। {{seconds}}s में चयन किया जा रहा है…"

+ 4 - 0
webview-ui/src/i18n/locales/id/chat.json

@@ -206,6 +206,10 @@
 			"description": "Pesan lama telah dihapus dari percakapan untuk tetap dalam batas jendela konteks. Ini adalah pendekatan yang cepat tetapi kurang mempertahankan konteks dibandingkan dengan kondensasi."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo ingin memuat keterampilan",
+		"didLoad": "Roo telah memuat keterampilan"
+	},
 	"instructions": {
 		"wantsToFetch": "Roo ingin mengambil instruksi detail untuk membantu tugas saat ini"
 	},

+ 4 - 0
webview-ui/src/i18n/locales/it/chat.json

@@ -324,6 +324,10 @@
 			"description": "I messaggi più vecchi sono stati rimossi dalla conversazione per rimanere entro il limite della finestra di contesto. Questo è un approccio veloce ma meno conservativo del contesto rispetto alla condensazione."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo vuole caricare una competenza",
+		"didLoad": "Roo ha caricato una competenza"
+	},
 	"followUpSuggest": {
 		"copyToInput": "Copia nell'input (o Shift + clic)",
 		"timerPrefix": "Approvazione automatica abilitata. Selezione tra {{seconds}}s…"

+ 4 - 0
webview-ui/src/i18n/locales/ja/chat.json

@@ -324,6 +324,10 @@
 			"description": "コンテキストウィンドウの制限内に収めるため、古いメッセージが会話から削除されました。これは圧縮と比較して高速ですが、コンテキストの保持性が低いアプローチです。"
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Rooはスキルを読み込もうとしています",
+		"didLoad": "Rooはスキルを読み込みました"
+	},
 	"followUpSuggest": {
 		"copyToInput": "入力欄にコピー(またはShift + クリック)",
 		"timerPrefix": "自動承認が有効です。{{seconds}}秒後に選択中…"

+ 4 - 0
webview-ui/src/i18n/locales/ko/chat.json

@@ -324,6 +324,10 @@
 			"description": "컨텍스트 윈도우 제한 내에 유지하기 위해 대화에서 오래된 메시지가 제거되었습니다. 이것은 압축에 비해 빠르지만 컨텍스트 보존 능력이 낮은 접근 방식입니다."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo가 스킬을 로드하려고 합니다",
+		"didLoad": "Roo가 스킬을 로드했습니다"
+	},
 	"followUpSuggest": {
 		"copyToInput": "입력창에 복사 (또는 Shift + 클릭)",
 		"timerPrefix": "자동 승인 활성화됨. {{seconds}}초 후 선택 중…"

+ 4 - 0
webview-ui/src/i18n/locales/nl/chat.json

@@ -346,6 +346,10 @@
 			"description": "Oudere berichten zijn uit het gesprek verwijderd om binnen de limiet van het contextvenster te blijven. Dit is een snelle maar minder contextbehoudende aanpak in vergelijking met samenvoeging."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo wil een vaardigheid laden",
+		"didLoad": "Roo heeft een vaardigheid geladen"
+	},
 	"followUpSuggest": {
 		"copyToInput": "Kopiëren naar invoer (zelfde als shift + klik)",
 		"timerPrefix": "Automatisch goedkeuren ingeschakeld. Selecteren in {{seconds}}s…"

+ 4 - 0
webview-ui/src/i18n/locales/pl/chat.json

@@ -324,6 +324,10 @@
 			"description": "Starsze wiadomości zostały usunięte z konwersacji, aby pozostać w granicach okna kontekstu. To szybsze, ale mniej zachowujące kontekst podejście w porównaniu z kondensacją."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo chce załadować umiejętność",
+		"didLoad": "Roo załadował umiejętność"
+	},
 	"followUpSuggest": {
 		"copyToInput": "Kopiuj do pola wprowadzania (lub Shift + kliknięcie)",
 		"timerPrefix": "Automatyczne zatwierdzanie włączone. Zaznaczanie za {{seconds}}s…"

+ 4 - 0
webview-ui/src/i18n/locales/pt-BR/chat.json

@@ -324,6 +324,10 @@
 			"description": "Mensagens mais antigas foram removidas da conversa para permanecer dentro do limite da janela de contexto. Esta é uma abordagem rápida, mas menos preservadora de contexto em comparação com a condensação."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo quer carregar uma habilidade",
+		"didLoad": "Roo carregou uma habilidade"
+	},
 	"followUpSuggest": {
 		"copyToInput": "Copiar para entrada (ou Shift + clique)",
 		"timerPrefix": "Aprovação automática ativada. Selecionando em {{seconds}}s…"

+ 4 - 0
webview-ui/src/i18n/locales/ru/chat.json

@@ -347,6 +347,10 @@
 			"description": "Более старые сообщения были удалены из разговора, чтобы остаться в пределах контекстного окна. Это быстрый, но менее сохраняющий контекст подход по сравнению со сжатием."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo хочет загрузить навык",
+		"didLoad": "Roo загрузил навык"
+	},
 	"followUpSuggest": {
 		"copyToInput": "Скопировать во ввод (то же, что shift + клик)",
 		"timerPrefix": "Автоматическое одобрение включено. Выбор через {{seconds}}s…"

+ 4 - 0
webview-ui/src/i18n/locales/tr/chat.json

@@ -325,6 +325,10 @@
 			"description": "Bağlam penceresi sınırında kalmak için eski mesajlar konuşmadan kaldırıldı. Bu, yoğunlaştırmaya kıyasla hızlı ancak daha az bağlam koruyucu bir yaklaşımdır."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo bir beceri yüklemek istiyor",
+		"didLoad": "Roo bir beceri yükledi"
+	},
 	"followUpSuggest": {
 		"copyToInput": "Giriş alanına kopyala (veya Shift + tıklama)",
 		"timerPrefix": "Otomatik onay etkinleştirildi. {{seconds}}s içinde seçim yapılıyor…"

+ 4 - 0
webview-ui/src/i18n/locales/vi/chat.json

@@ -325,6 +325,10 @@
 			"description": "Các tin nhắn cũ hơn đã bị xóa khỏi cuộc trò chuyện để giữ trong giới hạn cửa sổ ngữ cảnh. Đây là cách tiếp cận nhanh nhưng ít bảo toàn ngữ cảnh hơn so với cô đọng."
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo muốn tải một kỹ năng",
+		"didLoad": "Roo đã tải một kỹ năng"
+	},
 	"followUpSuggest": {
 		"copyToInput": "Sao chép vào ô nhập liệu (hoặc Shift + nhấp chuột)",
 		"timerPrefix": "Phê duyệt tự động được bật. Chọn trong {{seconds}}s…"

+ 4 - 0
webview-ui/src/i18n/locales/zh-CN/chat.json

@@ -325,6 +325,10 @@
 			"description": "为保持在上下文窗口限制内,已从对话中移除较旧的消息。与压缩相比,这是一种快速但上下文保留较少的方法。"
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo 想要加载技能",
+		"didLoad": "Roo 加载了技能"
+	},
 	"followUpSuggest": {
 		"copyToInput": "复制到输入框(或按住Shift点击)",
 		"timerPrefix": "自动批准已启用。{{seconds}}秒后选择中…"

+ 4 - 0
webview-ui/src/i18n/locales/zh-TW/chat.json

@@ -203,6 +203,10 @@
 			"description": "為保持在上下文視窗限制內,已從對話中移除較舊的訊息。與壓縮相比,這是一種快速但上下文保留較少的方法。"
 		}
 	},
+	"skill": {
+		"wantsToLoad": "Roo 想要載入技能",
+		"didLoad": "Roo 載入了技能"
+	},
 	"instructions": {
 		"wantsToFetch": "Roo 想要取得詳細指示以協助目前工作"
 	},