Просмотр исходного кода

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

Hannes Rudolph 2 недель назад
Родитель
Сommit
67e568f6bb
100 измененных файлов с 1714 добавлено и 4859 удалено
  1. 1 9
      apps/cli/src/ui/components/tools/types.ts
  2. 2 4
      apps/cli/src/ui/components/tools/utils.ts
  3. 0 413
      apps/web-roo-code/src/app/linear/page.tsx
  4. 0 20
      apps/web-roo-code/src/components/chromes/nav-bar.tsx
  5. 0 442
      apps/web-roo-code/src/components/linear/linear-issue-demo.tsx
  6. 0 144
      packages/types/src/__tests__/skills.test.ts
  7. 0 1
      packages/types/src/global-settings.ts
  8. 0 1
      packages/types/src/index.ts
  9. 0 71
      packages/types/src/skills.ts
  10. 1 1
      packages/types/src/tool.ts
  11. 3 14
      packages/types/src/vscode-extension-host.ts
  12. 18 16
      src/core/assistant-message/NativeToolCallParser.ts
  13. 10 11
      src/core/assistant-message/presentAssistantMessage.ts
  14. 5 8
      src/core/auto-approval/index.ts
  15. 0 4
      src/core/prompts/__tests__/add-custom-instructions.spec.ts
  16. 0 3
      src/core/prompts/__tests__/custom-system-prompt.spec.ts
  17. 2 13
      src/core/prompts/__tests__/system-prompt.spec.ts
  18. 0 62
      src/core/prompts/instructions/create-mode.ts
  19. 0 25
      src/core/prompts/instructions/instructions.ts
  20. 2 18
      src/core/prompts/sections/modes.ts
  21. 12 11
      src/core/prompts/sections/skills.ts
  22. 0 3
      src/core/prompts/system.ts
  23. 0 26
      src/core/prompts/tools/native-tools/fetch_instructions.ts
  24. 2 2
      src/core/prompts/tools/native-tools/index.ts
  25. 33 0
      src/core/prompts/tools/native-tools/skill.ts
  26. 0 2
      src/core/task/Task.ts
  27. 0 75
      src/core/tools/FetchInstructionsTool.ts
  28. 112 0
      src/core/tools/SkillTool.ts
  29. 345 0
      src/core/tools/__tests__/skillTool.spec.ts
  30. 0 3
      src/core/webview/ClineProvider.ts
  31. 1 36
      src/core/webview/__tests__/ClineProvider.spec.ts
  32. 0 1
      src/core/webview/__tests__/generateSystemPrompt.browser-capability.spec.ts
  33. 0 334
      src/core/webview/__tests__/skillsMessageHandler.spec.ts
  34. 0 2
      src/core/webview/generateSystemPrompt.ts
  35. 0 133
      src/core/webview/skillsMessageHandler.ts
  36. 0 21
      src/core/webview/webviewMessageHandler.ts
  37. 0 14
      src/i18n/locales/ca/skills.json
  38. 0 14
      src/i18n/locales/de/skills.json
  39. 0 14
      src/i18n/locales/en/skills.json
  40. 0 14
      src/i18n/locales/es/skills.json
  41. 0 14
      src/i18n/locales/fr/skills.json
  42. 0 14
      src/i18n/locales/hi/skills.json
  43. 0 14
      src/i18n/locales/id/skills.json
  44. 0 14
      src/i18n/locales/it/skills.json
  45. 0 14
      src/i18n/locales/ja/skills.json
  46. 0 14
      src/i18n/locales/ko/skills.json
  47. 0 14
      src/i18n/locales/nl/skills.json
  48. 0 14
      src/i18n/locales/pl/skills.json
  49. 0 14
      src/i18n/locales/pt-BR/skills.json
  50. 0 14
      src/i18n/locales/ru/skills.json
  51. 0 14
      src/i18n/locales/tr/skills.json
  52. 0 14
      src/i18n/locales/vi/skills.json
  53. 0 14
      src/i18n/locales/zh-CN/skills.json
  54. 0 14
      src/i18n/locales/zh-TW/skills.json
  55. 2 0
      src/package.json
  56. 49 158
      src/services/skills/SkillsManager.ts
  57. 16 307
      src/services/skills/__tests__/SkillsManager.spec.ts
  58. 175 0
      src/services/skills/__tests__/generate-built-in-skills.spec.ts
  59. 131 24
      src/services/skills/built-in-skills.ts
  60. 304 0
      src/services/skills/built-in/create-mcp-server/SKILL.md
  61. 57 0
      src/services/skills/built-in/create-mode/SKILL.md
  62. 302 0
      src/services/skills/generate-built-in-skills.ts
  63. 2 2
      src/shared/skills.ts
  64. 10 8
      src/shared/tools.ts
  65. 62 11
      webview-ui/src/components/chat/ChatRow.tsx
  66. 2 44
      webview-ui/src/components/mcp/McpView.tsx
  67. 0 254
      webview-ui/src/components/settings/CreateSkillDialog.tsx
  68. 0 7
      webview-ui/src/components/settings/SettingsView.tsx
  69. 0 61
      webview-ui/src/components/settings/SkillItem.tsx
  70. 0 228
      webview-ui/src/components/settings/SkillsSettings.tsx
  71. 0 404
      webview-ui/src/components/settings/__tests__/CreateSkillDialog.spec.tsx
  72. 0 25
      webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx
  73. 0 11
      webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx
  74. 0 25
      webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx
  75. 0 141
      webview-ui/src/components/settings/__tests__/SkillItem.spec.tsx
  76. 0 436
      webview-ui/src/components/settings/__tests__/SkillsSettings.spec.tsx
  77. 0 13
      webview-ui/src/context/ExtensionStateContext.tsx
  78. 0 1
      webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx
  79. 4 0
      webview-ui/src/i18n/locales/ca/chat.json
  80. 1 47
      webview-ui/src/i18n/locales/ca/settings.json
  81. 4 0
      webview-ui/src/i18n/locales/de/chat.json
  82. 1 47
      webview-ui/src/i18n/locales/de/settings.json
  83. 3 2
      webview-ui/src/i18n/locales/en/chat.json
  84. 0 46
      webview-ui/src/i18n/locales/en/settings.json
  85. 4 0
      webview-ui/src/i18n/locales/es/chat.json
  86. 1 47
      webview-ui/src/i18n/locales/es/settings.json
  87. 4 0
      webview-ui/src/i18n/locales/fr/chat.json
  88. 1 47
      webview-ui/src/i18n/locales/fr/settings.json
  89. 4 0
      webview-ui/src/i18n/locales/hi/chat.json
  90. 1 47
      webview-ui/src/i18n/locales/hi/settings.json
  91. 4 0
      webview-ui/src/i18n/locales/id/chat.json
  92. 1 47
      webview-ui/src/i18n/locales/id/settings.json
  93. 4 0
      webview-ui/src/i18n/locales/it/chat.json
  94. 1 47
      webview-ui/src/i18n/locales/it/settings.json
  95. 4 0
      webview-ui/src/i18n/locales/ja/chat.json
  96. 1 47
      webview-ui/src/i18n/locales/ja/settings.json
  97. 4 0
      webview-ui/src/i18n/locales/ko/chat.json
  98. 1 47
      webview-ui/src/i18n/locales/ko/settings.json
  99. 4 0
      webview-ui/src/i18n/locales/nl/chat.json
  100. 1 47
      webview-ui/src/i18n/locales/nl/settings.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 - 413
apps/web-roo-code/src/app/linear/page.tsx

@@ -1,413 +0,0 @@
-import {
-	ArrowRight,
-	CheckCircle,
-	CreditCard,
-	Eye,
-	GitBranch,
-	GitPullRequest,
-	Link2,
-	MessageSquare,
-	Settings,
-	Shield,
-} from "lucide-react"
-import type { LucideIcon } from "lucide-react"
-import type { Metadata } from "next"
-
-import { AnimatedBackground } from "@/components/homepage"
-import { LinearIssueDemo } from "@/components/linear/linear-issue-demo"
-import { Button } from "@/components/ui"
-import { EXTERNAL_LINKS } from "@/lib/constants"
-import { SEO } from "@/lib/seo"
-import { ogImageUrl } from "@/lib/og"
-
-const TITLE = "Roo Code for Linear"
-const DESCRIPTION = "Assign development work to @Roo Code directly from Linear. Get PRs back without switching tools."
-const OG_DESCRIPTION = "Turn Linear Issues into Pull Requests"
-const PATH = "/linear"
-
-// Featured Workflow section is temporarily commented out until video is ready
-// const LINEAR_DEMO_YOUTUBE_ID = ""
-
-export const metadata: Metadata = {
-	title: TITLE,
-	description: DESCRIPTION,
-	alternates: {
-		canonical: `${SEO.url}${PATH}`,
-	},
-	openGraph: {
-		title: TITLE,
-		description: DESCRIPTION,
-		url: `${SEO.url}${PATH}`,
-		siteName: SEO.name,
-		images: [
-			{
-				url: ogImageUrl(TITLE, OG_DESCRIPTION),
-				width: 1200,
-				height: 630,
-				alt: TITLE,
-			},
-		],
-		locale: SEO.locale,
-		type: "website",
-	},
-	twitter: {
-		card: SEO.twitterCard,
-		title: TITLE,
-		description: DESCRIPTION,
-		images: [ogImageUrl(TITLE, OG_DESCRIPTION)],
-	},
-	keywords: [
-		...SEO.keywords,
-		"linear integration",
-		"issue to PR",
-		"AI in Linear",
-		"engineering workflow automation",
-		"Roo Code Cloud",
-	],
-}
-
-// Invalidate cache when a request comes in, at most once every hour.
-export const revalidate = 3600
-
-type ValueProp = {
-	icon: LucideIcon
-	title: string
-	description: string
-}
-
-const VALUE_PROPS: ValueProp[] = [
-	{
-		icon: GitBranch,
-		title: "Work where you already work.",
-		description:
-			"Assign development work to @Roo Code directly from Linear. No new tools to learn, no context switching required.",
-	},
-	{
-		icon: Eye,
-		title: "Progress is visible.",
-		description:
-			"Watch progress unfold in real-time. Roo Code posts updates as comments, so your whole team stays in the loop.",
-	},
-	{
-		icon: MessageSquare,
-		title: "Mention for refinement.",
-		description:
-			'Need changes? Just comment "@Roo Code also add dark mode support" and the agent picks up where it left off.',
-	},
-	{
-		icon: Link2,
-		title: "Full traceability.",
-		description:
-			"Every PR links back to the originating issue. Every issue shows its linked PR. Your audit trail stays clean.",
-	},
-	{
-		icon: Settings,
-		title: "Organization-level setup.",
-		description:
-			"Connect once, use everywhere. Your team members can assign issues to @Roo Code without individual configuration.",
-	},
-	{
-		icon: Shield,
-		title: "Safe by design.",
-		description:
-			"Agents never touch main/master directly. They produce branches and PRs. You review and approve before merge.",
-	},
-]
-
-// type WorkflowStep = {
-// 	step: number
-// 	title: string
-// 	description: string
-// }
-
-// const WORKFLOW_STEPS: WorkflowStep[] = [
-// 	{
-// 		step: 1,
-// 		title: "Create an issue",
-// 		description: "Write your issue with acceptance criteria. Be as detailed as you like.",
-// 	},
-// 	{
-// 		step: 2,
-// 		title: "Call @Roo Code",
-// 		description: "Mention @Roo Code in a comment to start. The agent begins working immediately.",
-// 	},
-// 	{
-// 		step: 3,
-// 		title: "Watch progress",
-// 		description: "Roo Code posts status updates as comments. Refine with @-mentions if needed.",
-// 	},
-// 	{
-// 		step: 4,
-// 		title: "Review the PR",
-// 		description: "When ready, the PR link appears in the issue. Review, iterate, and ship.",
-// 	},
-// ]
-
-type OnboardingStep = {
-	icon: LucideIcon
-	title: string
-	description: string
-	link?: {
-		href: string
-		text: string
-	}
-}
-
-const ONBOARDING_STEPS: OnboardingStep[] = [
-	{
-		icon: CreditCard,
-		title: "1. Team Plan",
-		description: "Linear integration requires a Team plan.",
-		link: {
-			href: EXTERNAL_LINKS.CLOUD_APP_TEAM_TRIAL,
-			text: "Start a free trial",
-		},
-	},
-	{
-		icon: GitPullRequest,
-		title: "2. Connect GitHub",
-		description: "Link your repositories so Roo Code can open PRs on your behalf.",
-	},
-	{
-		icon: Settings,
-		title: "3. Connect Linear",
-		description: "Authorize via OAuth. No API keys to manage or rotate.",
-	},
-	{
-		icon: CheckCircle,
-		title: "4. Link & Start",
-		description: "Map your Linear project to a repo, then assign or mention @Roo Code.",
-	},
-]
-
-function LinearIcon({ className }: { className?: string }) {
-	return (
-		<svg className={className} viewBox="0 0 100 100" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
-			<path d="M1.22541 61.5228c-.2225-.9485.90748-1.5459 1.59638-.857L39.3342 97.1782c.6889.6889.0915 1.8189-.857 1.5964C20.0515 94.4522 5.54779 79.9485 1.22541 61.5228ZM.00189135 46.8891c-.01764375.2833.08887215.5599.28957165.7606L52.3503 99.7085c.2007.2007.4773.3075.7606.2896 2.3692-.1476 4.6938-.46 6.9624-.9259.7645-.157 1.0301-1.0963.4782-1.6481L2.57595 39.4485c-.55186-.5519-1.49117-.2863-1.648174.4782-.465915 2.2686-.77832 4.5932-.92588465 6.9624ZM4.21093 29.7054c-.16649.3738-.08169.8106.20765 1.1l64.77602 64.776c.2894.2894.7262.3742 1.1.2077 1.7861-.7956 3.5171-1.6927 5.1855-2.684.5521-.328.6373-1.0867.1832-1.5407L8.43566 24.3367c-.45409-.4541-1.21271-.3689-1.54074.1832-.99132 1.6684-1.88843 3.3994-2.68399 5.1855ZM12.6587 18.074c-.3701-.3701-.393-.9637-.0443-1.3541C21.7795 6.45931 35.1114 0 49.9519 0 77.5927 0 100 22.4073 100 50.0481c0 14.8405-6.4593 28.1724-16.7199 37.3375-.3903.3487-.984.3258-1.3542-.0443L12.6587 18.074Z" />
-		</svg>
-	)
-}
-
-export default function LinearPage(): JSX.Element {
-	return (
-		<>
-			{/* Hero Section */}
-			<section className="relative flex pt-32 pb-20 items-center overflow-hidden">
-				<AnimatedBackground />
-				<div className="container relative flex flex-col items-center h-full z-10 mx-auto px-4 sm:px-6 lg:px-8">
-					<div className="grid w-full max-w-6xl grid-cols-1 items-center gap-10 lg:grid-cols-2 lg:gap-12">
-						<div className="text-center lg:text-left">
-							<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-indigo-100 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300 text-sm font-medium mb-6">
-								<LinearIcon className="size-4" />
-								Powered by Roo Code Cloud
-							</div>
-							<h1 className="text-4xl font-bold tracking-tight mb-6 md:text-5xl lg:text-6xl">
-								Turn Linear Issues into <span className="text-indigo-500">Pull&nbsp;Requests</span>
-							</h1>
-							<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto lg:mx-0">
-								Assign development work to @Roo Code directly from Linear. Get PRs back without
-								switching tools.
-							</p>
-							<div className="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
-								<Button
-									size="xl"
-									className="bg-indigo-600 hover:bg-indigo-700 text-white transition-all duration-300 shadow-lg hover:shadow-indigo-500/25"
-									asChild>
-									<a
-										href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP_HOME}
-										target="_blank"
-										rel="noopener noreferrer"
-										className="flex items-center justify-center">
-										Get Started
-										<ArrowRight className="ml-2 size-5" />
-									</a>
-								</Button>
-							</div>
-						</div>
-
-						<div className="flex justify-center lg:justify-end">
-							<LinearIssueDemo />
-						</div>
-					</div>
-				</div>
-			</section>
-
-			{/* Value Props Section */}
-			<section className="py-24 bg-muted/30">
-				<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative">
-					<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2 z-1">
-						<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-full bg-indigo-500/10 dark:bg-indigo-700/20 blur-[140px]" />
-					</div>
-					<div className="text-center mb-16">
-						<h2 className="text-3xl font-bold tracking-tight sm:text-4xl mb-4">
-							Why your team will love using Roo Code in&nbsp;Linear
-						</h2>
-						<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
-							AI agents that understand context, keep your team in the loop, and deliver PRs you can
-							review.
-						</p>
-					</div>
-					<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl mx-auto relative">
-						{VALUE_PROPS.map((prop, index) => {
-							const Icon = prop.icon
-							return (
-								<div
-									key={index}
-									className="bg-background p-8 rounded-2xl border border-border hover:shadow-lg transition-all duration-300">
-									<div className="bg-indigo-100 dark:bg-indigo-900/20 w-12 h-12 rounded-lg flex items-center justify-center mb-6">
-										<Icon className="size-6 text-indigo-600 dark:text-indigo-400" />
-									</div>
-									<h3 className="text-xl font-semibold mb-3">{prop.title}</h3>
-									<p className="text-muted-foreground leading-relaxed">{prop.description}</p>
-								</div>
-							)
-						})}
-					</div>
-				</div>
-			</section>
-
-			{/* Featured Workflow Section - temporarily commented out until video is ready
-			<section id="demo" className="relative overflow-hidden border-t border-border py-24 lg:py-32">
-				<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
-					<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2 z-1">
-						<div className="absolute left-1/2 top-1/2 h-[400px] w-full -translate-x-1/2 -translate-y-1/2 rounded-full bg-blue-500/10 dark:bg-blue-700/20 blur-[140px]" />
-					</div>
-
-					<div className="mx-auto mb-12 max-w-5xl text-center">
-						<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 text-sm font-medium mb-6">
-							<Zap className="size-4" />
-							Featured Workflow
-						</div>
-						<h2 className="text-3xl font-bold tracking-tight sm:text-5xl mb-4">Issue to Shipped Feature</h2>
-						<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
-							Stay in Linear from assignment to review. Roo Code keeps the issue updated and links the PR
-							when it&apos;s ready.
-						</p>
-					</div>
-
-					<div className="relative mx-auto max-w-6xl">
-						<div className="grid grid-cols-1 lg:grid-cols-5 gap-8 lg:gap-10 items-center">
-							{/* YouTube Video Embed or Placeholder */}
-			{/*<div className="lg:col-span-3 overflow-hidden rounded-2xl border border-border bg-background shadow-lg">
-								{LINEAR_DEMO_YOUTUBE_ID ? (
-									<iframe
-										className="aspect-video w-full"
-										src={`https://www.youtube-nocookie.com/embed/${LINEAR_DEMO_YOUTUBE_ID}?rel=0`}
-										title="Roo Code Linear Integration Demo"
-										allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
-										referrerPolicy="strict-origin-when-cross-origin"
-										allowFullScreen
-									/>
-								) : (
-									<div className="aspect-video w-full flex flex-col items-center justify-center bg-gradient-to-br from-indigo-500/10 via-blue-500/5 to-purple-500/10 text-center p-8">
-										<LinearIcon className="size-16 text-indigo-500/50 mb-4" />
-										<p className="text-lg font-semibold text-foreground mb-2">
-											Demo Video Coming Soon
-										</p>
-										<p className="text-sm text-muted-foreground max-w-md">
-											See the workflow in action: assign an issue to @Roo Code and watch as it
-											analyzes requirements, writes code, and opens a PR.
-										</p>
-									</div>
-								)}
-							</div>
-
-							{/* Workflow Steps */}
-			{/*<div className="lg:col-span-2 space-y-3">
-								{WORKFLOW_STEPS.map((step) => (
-									<div
-										key={step.step}
-										className="relative border border-border rounded-xl bg-background p-4 transition-all duration-300 hover:shadow-md hover:border-blue-500/30">
-										<div className="flex items-start gap-3">
-											<div className="bg-blue-100 dark:bg-blue-900/30 w-7 h-7 rounded-full flex items-center justify-center text-blue-700 dark:text-blue-300 font-bold text-xs shrink-0 mt-0.5">
-												{step.step}
-											</div>
-											<div className="min-w-0">
-												<h3 className="text-base font-semibold text-foreground mb-0.5">
-													{step.title}
-												</h3>
-												<p className="text-sm leading-snug text-muted-foreground">
-													{step.description}
-												</p>
-											</div>
-										</div>
-									</div>
-								))}
-							</div>
-						</div>
-					</div>
-				</div>
-			</section>
-			*/}
-
-			{/* Onboarding Section */}
-			<section className="py-24 bg-muted/30">
-				<div className="container mx-auto px-4 sm:px-6 lg:px-8">
-					<div className="text-center mb-16">
-						<h2 className="text-3xl font-bold tracking-tight sm:text-4xl mb-4">Get started in minutes</h2>
-						<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
-							Connect Linear and start assigning issues to AI.
-						</p>
-					</div>
-					<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 max-w-5xl mx-auto">
-						{ONBOARDING_STEPS.map((step, index) => {
-							const Icon = step.icon
-							return (
-								<div key={index} className="text-center">
-									<div className="bg-indigo-100 dark:bg-indigo-900/20 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-6">
-										<Icon className="size-8 text-indigo-600 dark:text-indigo-400" />
-									</div>
-									<h3 className="text-lg font-semibold mb-2">{step.title}</h3>
-									<p className="text-muted-foreground">
-										{step.description}
-										{step.link && (
-											<>
-												{" "}
-												<a
-													href={step.link.href}
-													target="_blank"
-													rel="noopener noreferrer"
-													className="text-indigo-600 dark:text-indigo-400 hover:underline">
-													{step.link.text} →
-												</a>
-											</>
-										)}
-									</p>
-								</div>
-							)
-						})}
-					</div>
-				</div>
-			</section>
-
-			{/* CTA Section */}
-			<section className="py-24">
-				<div className="container mx-auto px-4 sm:px-6 lg:px-8">
-					<div className="mx-auto max-w-4xl rounded-3xl border border-border/50 bg-gradient-to-br from-indigo-500/10 via-purple-500/5 to-blue-500/5 p-8 text-center shadow-2xl backdrop-blur-xl dark:border-white/10 sm:p-16">
-						<h2 className="mb-6 text-3xl font-bold tracking-tight sm:text-4xl">
-							Start using Roo Code in Linear
-						</h2>
-						<p className="mx-auto mb-10 max-w-2xl text-lg text-muted-foreground">
-							Start a free 14 day Team trial.
-						</p>
-						<div className="flex flex-col justify-center space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
-							<Button
-								size="lg"
-								className="bg-foreground text-background hover:bg-foreground/90 transition-all duration-300"
-								asChild>
-								<a
-									href={EXTERNAL_LINKS.CLOUD_APP_TEAM_TRIAL}
-									target="_blank"
-									rel="noopener noreferrer"
-									className="flex items-center justify-center">
-									Start free trial
-									<ArrowRight className="ml-2 h-4 w-4" />
-								</a>
-							</Button>
-						</div>
-					</div>
-				</div>
-			</section>
-		</>
-	)
-}

+ 0 - 20
apps/web-roo-code/src/components/chromes/nav-bar.tsx

@@ -15,14 +15,6 @@ import { ScrollButton } from "@/components/ui"
 import ThemeToggle from "@/components/chromes/theme-toggle"
 import { Brain, ChevronDown, Cloud, Puzzle, Slack, X } from "lucide-react"
 
-function LinearIcon({ className }: { className?: string }) {
-	return (
-		<svg className={className} viewBox="0 0 100 100" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
-			<path d="M1.22541 61.5228c-.2225-.9485.90748-1.5459 1.59638-.857L39.3342 97.1782c.6889.6889.0915 1.8189-.857 1.5964C20.0515 94.4522 5.54779 79.9485 1.22541 61.5228ZM.00189135 46.8891c-.01764375.2833.08887215.5599.28957165.7606L52.3503 99.7085c.2007.2007.4773.3075.7606.2896 2.3692-.1476 4.6938-.46 6.9624-.9259.7645-.157 1.0301-1.0963.4782-1.6481L2.57595 39.4485c-.55186-.5519-1.49117-.2863-1.648174.4782-.465915 2.2686-.77832 4.5932-.92588465 6.9624ZM4.21093 29.7054c-.16649.3738-.08169.8106.20765 1.1l64.77602 64.776c.2894.2894.7262.3742 1.1.2077 1.7861-.7956 3.5171-1.6927 5.1855-2.684.5521-.328.6373-1.0867.1832-1.5407L8.43566 24.3367c-.45409-.4541-1.21271-.3689-1.54074.1832-.99132 1.6684-1.88843 3.3994-2.68399 5.1855ZM12.6587 18.074c-.3701-.3701-.393-.9637-.0443-1.3541C21.7795 6.45931 35.1114 0 49.9519 0 77.5927 0 100 22.4073 100 50.0481c0 14.8405-6.4593 28.1724-16.7199 37.3375-.3903.3487-.984.3258-1.3542-.0443L12.6587 18.074Z" />
-		</svg>
-	)
-}
-
 interface NavBarProps {
 	stars: string | null
 	downloads: string | null
@@ -68,12 +60,6 @@ export function NavBar({ stars, downloads }: NavBarProps) {
 								<Slack className="size-3 inline mr-2 -mt-0.5" />
 								Roo Code for Slack
 							</Link>
-							<Link
-								href="/linear"
-								className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground">
-								<LinearIcon className="size-3 inline mr-2 -mt-0.5" />
-								Roo Code for Linear
-							</Link>
 							<Link
 								href="/provider"
 								className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground">
@@ -216,12 +202,6 @@ export function NavBar({ stars, downloads }: NavBarProps) {
 								onClick={() => setIsMenuOpen(false)}>
 								Roo Code for Slack
 							</Link>
-							<Link
-								href="/linear"
-								className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
-								onClick={() => setIsMenuOpen(false)}>
-								Roo Code for Linear
-							</Link>
 							<Link
 								href="/provider"
 								className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"

+ 0 - 442
apps/web-roo-code/src/components/linear/linear-issue-demo.tsx

@@ -1,442 +0,0 @@
-"use client"
-
-import type { ReactNode } from "react"
-import { useEffect, useMemo, useRef, useState } from "react"
-import { ChevronRight, GitPullRequest, Paperclip, Send } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-
-type ActivityItem = {
-	id: string
-	kind: "comment" | "event" | "pr-link"
-	author?: string
-	avatarText?: string
-	avatarClassName?: string
-	body: ReactNode
-	timeLabel: string
-}
-
-function usePrefersReducedMotion(): boolean {
-	const [reduced, setReduced] = useState(false)
-
-	useEffect(() => {
-		const media = window.matchMedia("(prefers-reduced-motion: reduce)")
-		const onChange = () => setReduced(media.matches)
-		onChange()
-
-		if (typeof media.addEventListener === "function") {
-			media.addEventListener("change", onChange)
-			return () => media.removeEventListener("change", onChange)
-		}
-
-		media.addListener?.(onChange)
-		return () => media.removeListener?.(onChange)
-	}, [])
-
-	return reduced
-}
-
-type TypingDotsProps = {
-	className?: string
-}
-
-function TypingDots({ className }: TypingDotsProps): JSX.Element {
-	return (
-		<span className={cn("inline-flex items-center gap-0.5", className)} aria-hidden="true">
-			<span className="h-1 w-1 rounded-full bg-[#8B8D91] animate-pulse [animation-delay:0ms]" />
-			<span className="h-1 w-1 rounded-full bg-[#8B8D91] animate-pulse [animation-delay:180ms]" />
-			<span className="h-1 w-1 rounded-full bg-[#8B8D91] animate-pulse [animation-delay:360ms]" />
-		</span>
-	)
-}
-
-function LinearIcon({ className }: { className?: string }) {
-	return (
-		<svg className={className} viewBox="0 0 100 100" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
-			<path d="M1.22541 61.5228c-.2225-.9485.90748-1.5459 1.59638-.857L39.3342 97.1782c.6889.6889.0915 1.8189-.857 1.5964C20.0515 94.4522 5.54779 79.9485 1.22541 61.5228ZM.00189135 46.8891c-.01764375.2833.08887215.5599.28957165.7606L52.3503 99.7085c.2007.2007.4773.3075.7606.2896 2.3692-.1476 4.6938-.46 6.9624-.9259.7645-.157 1.0301-1.0963.4782-1.6481L2.57595 39.4485c-.55186-.5519-1.49117-.2863-1.648174.4782-.465915 2.2686-.77832 4.5932-.92588465 6.9624ZM4.21093 29.7054c-.16649.3738-.08169.8106.20765 1.1l64.77602 64.776c.2894.2894.7262.3742 1.1.2077 1.7861-.7956 3.5171-1.6927 5.1855-2.684.5521-.328.6373-1.0867.1832-1.5407L8.43566 24.3367c-.45409-.4541-1.21271-.3689-1.54074.1832-.99132 1.6684-1.88843 3.3994-2.68399 5.1855ZM12.6587 18.074c-.3701-.3701-.393-.9637-.0443-1.3541C21.7795 6.45931 35.1114 0 49.9519 0 77.5927 0 100 22.4073 100 50.0481c0 14.8405-6.4593 28.1724-16.7199 37.3375-.3903.3487-.984.3258-1.3542-.0443L12.6587 18.074Z" />
-		</svg>
-	)
-}
-
-type ActivityRowProps = {
-	item: ActivityItem
-	isNew: boolean
-	reduceMotion: boolean
-}
-
-function ActivityRow({ item, isNew, reduceMotion }: ActivityRowProps): JSX.Element {
-	let animation = ""
-	if (!reduceMotion && isNew) {
-		animation = "animate-in fade-in slide-in-from-bottom-1 duration-300"
-	}
-
-	// Event items (status changes, etc.) - compact inline format
-	if (item.kind === "event") {
-		return (
-			<div className={cn("flex items-center gap-2 text-[13px] text-[#8B8D91]", animation)}>
-				<div
-					className={cn(
-						"flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[9px] font-semibold",
-						item.avatarClassName,
-					)}>
-					{item.avatarText}
-				</div>
-				<span className="text-[#F8F8F9]">{item.author}</span>
-				<span>{item.body}</span>
-				<span className="text-[#5C5F66]">·</span>
-				<span>{item.timeLabel}</span>
-			</div>
-		)
-	}
-
-	// PR link events
-	if (item.kind === "pr-link") {
-		return (
-			<div className={cn("flex items-center gap-2 text-[13px] text-[#8B8D91]", animation)}>
-				<GitPullRequest className="h-4 w-4 shrink-0 text-emerald-500" />
-				<span>{item.body}</span>
-				<span className="text-[#5C5F66]">·</span>
-				<span>{item.timeLabel}</span>
-			</div>
-		)
-	}
-
-	// Comment items - more substantial with message body
-	return (
-		<div className={cn("flex gap-2.5", animation)}>
-			<div
-				className={cn(
-					"mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[9px] font-semibold",
-					item.avatarClassName,
-				)}>
-				{item.avatarText}
-			</div>
-			<div className="min-w-0 flex-1">
-				<div className="flex items-center gap-2 text-[13px]">
-					<span className="font-medium text-[#F8F8F9]">{item.author}</span>
-					<span className="text-[#5C5F66]">·</span>
-					<span className="text-[#8B8D91]">{item.timeLabel}</span>
-				</div>
-				<div className="mt-1 text-[13px] leading-relaxed text-[#D1D2D3]">{item.body}</div>
-			</div>
-		</div>
-	)
-}
-
-export type LinearIssueDemoProps = {
-	className?: string
-}
-
-export function LinearIssueDemo({ className }: LinearIssueDemoProps): JSX.Element {
-	const reduceMotion = usePrefersReducedMotion()
-	const [stepIndex, setStepIndex] = useState(0)
-	const scrollViewportRef = useRef<HTMLDivElement>(null)
-
-	const activityItems: ActivityItem[] = useMemo(
-		() => [
-			{
-				id: "a1",
-				kind: "comment",
-				author: "Jordan",
-				avatarText: "J",
-				avatarClassName: "bg-amber-600 text-white",
-				body: (
-					<span>
-						<span className="text-indigo-400">@Roo Code</span> Can you implement this feature?
-					</span>
-				),
-				timeLabel: "2m ago",
-			},
-			{
-				id: "a2",
-				kind: "comment",
-				author: "Roo Code",
-				avatarText: "R",
-				avatarClassName: "bg-indigo-600 text-white",
-				body: <span>Analyzing issue requirements and codebase...</span>,
-				timeLabel: "2m ago",
-			},
-			{
-				id: "a3",
-				kind: "event",
-				author: "Roo Code",
-				avatarText: "R",
-				avatarClassName: "bg-indigo-600 text-white",
-				body: <span>moved to In Progress</span>,
-				timeLabel: "2m ago",
-			},
-			{
-				id: "a4",
-				kind: "comment",
-				author: "Roo Code",
-				avatarText: "R",
-				avatarClassName: "bg-indigo-600 text-white",
-				body: <span>Planning implementation: Settings component with light/dark toggle.</span>,
-				timeLabel: "1m ago",
-			},
-			{
-				id: "a5",
-				kind: "comment",
-				author: "Jordan",
-				avatarText: "J",
-				avatarClassName: "bg-amber-600 text-white",
-				body: (
-					<span>
-						<span className="text-indigo-400">@Roo Code</span> Please also add a &quot;system&quot; option
-						that follows OS preference.
-					</span>
-				),
-				timeLabel: "1m ago",
-			},
-			{
-				id: "a6",
-				kind: "comment",
-				author: "Roo Code",
-				avatarText: "R",
-				avatarClassName: "bg-indigo-600 text-white",
-				body: (
-					<span>
-						Got it! Adding system preference detection using{" "}
-						<code className="rounded bg-white/10 px-1 py-0.5 text-[12px] text-[#F8F8F9]">
-							prefers-color-scheme
-						</code>
-					</span>
-				),
-				timeLabel: "30s ago",
-			},
-			{
-				id: "a7",
-				kind: "pr-link",
-				body: (
-					<span>
-						<span className="text-[#F8F8F9]">Roo Code</span> linked{" "}
-						<span className="text-emerald-400">PR #847</span>
-					</span>
-				),
-				timeLabel: "just now",
-			},
-			{
-				id: "a8",
-				kind: "comment",
-				author: "Roo Code",
-				avatarText: "R",
-				avatarClassName: "bg-indigo-600 text-white",
-				body: (
-					<div className="space-y-2">
-						<div>
-							PR ready for review:{" "}
-							<span className="text-indigo-400 hover:underline cursor-default">#847</span>
-						</div>
-						<div className="rounded-lg border border-white/10 bg-black/30 px-3 py-2 text-[12px]">
-							<div className="flex items-center gap-2 text-emerald-400">
-								<GitPullRequest className="h-3.5 w-3.5" />
-								<span className="font-medium">feat: add theme toggle with system preference</span>
-							</div>
-							<div className="mt-1 text-[#8B8D91]">+142 -12 · 3 files changed</div>
-						</div>
-					</div>
-				),
-				timeLabel: "just now",
-			},
-		],
-		[],
-	)
-
-	type DemoPhase =
-		| { kind: "issue" }
-		| { kind: "show"; activityIndex: number }
-		| { kind: "typing"; activityIndex: number }
-		| { kind: "reset" }
-
-	const phases: DemoPhase[] = useMemo(() => {
-		const next: DemoPhase[] = []
-
-		next.push({ kind: "issue" })
-
-		for (let activityIndex = 0; activityIndex < activityItems.length; activityIndex += 1) {
-			const item = activityItems[activityIndex]
-			if (item?.kind === "comment") {
-				next.push({ kind: "typing", activityIndex })
-			}
-			next.push({ kind: "show", activityIndex })
-		}
-		next.push({ kind: "reset" })
-		return next
-	}, [activityItems])
-
-	const lastShowPhaseIndex = useMemo(() => {
-		let lastIndex = -1
-		for (let idx = 0; idx < phases.length; idx += 1) {
-			if (phases[idx]?.kind === "show") lastIndex = idx
-		}
-		return lastIndex
-	}, [phases])
-
-	useEffect(() => {
-		if (reduceMotion) {
-			setStepIndex(lastShowPhaseIndex >= 0 ? lastShowPhaseIndex : 0)
-			return
-		}
-
-		const active = phases[stepIndex] ?? phases.at(0)
-		const isLastMessageShow = active?.kind === "show" && stepIndex === lastShowPhaseIndex
-		const durationMs = (() => {
-			const base = 2000
-			if (active?.kind === "reset") return 500
-			if (active?.kind === "issue") return 1500
-			if (active?.kind === "typing") return 800
-			return isLastMessageShow ? base * 2.5 : base
-		})()
-
-		const timer = window.setTimeout(() => {
-			const nextIndex = (stepIndex + 1) % phases.length
-			setStepIndex(nextIndex)
-		}, durationMs)
-
-		return () => window.clearTimeout(timer)
-	}, [lastShowPhaseIndex, phases, reduceMotion, stepIndex])
-
-	const activePhase = phases[stepIndex] ?? phases.at(0) ?? { kind: "issue" }
-
-	function getVisibleCount(phase: DemoPhase): number {
-		if (phase.kind === "reset" || phase.kind === "issue") return 0
-		if (phase.kind === "typing") return phase.activityIndex
-		return phase.activityIndex + 1
-	}
-
-	const visibleCount = getVisibleCount(activePhase)
-	const visibleActivities = activityItems.slice(0, visibleCount)
-	const typingTarget = activePhase.kind === "typing" ? activityItems[activePhase.activityIndex] : undefined
-
-	useEffect(() => {
-		const viewport = scrollViewportRef.current
-		if (!viewport) return
-
-		if (activePhase.kind === "reset" || activePhase.kind === "issue" || visibleCount <= 1) {
-			viewport.scrollTo({ top: 0, behavior: "auto" })
-			return
-		}
-
-		viewport.scrollTo({
-			top: viewport.scrollHeight,
-			behavior: reduceMotion ? "auto" : "smooth",
-		})
-	}, [activePhase.kind, reduceMotion, visibleCount])
-
-	const issueVisible = activePhase.kind !== "reset"
-
-	return (
-		<div
-			className={cn("w-full max-w-[540px] h-[520px] sm:h-[560px]", className)}
-			role="img"
-			aria-label="Animated Linear issue showing Roo Code responding to a comment">
-			<div
-				aria-hidden="true"
-				className="relative flex h-full flex-col overflow-hidden rounded-2xl border border-white/10 bg-[#1F2023] shadow-2xl shadow-black/40">
-				{/* Linear-style Header with breadcrumb */}
-				<div className="flex items-center gap-2 border-b border-white/10 px-4 py-2.5 text-[13px]">
-					<LinearIcon className="h-4 w-4 text-[#8B8D91]" />
-					<span className="text-[#8B8D91]">Frontend</span>
-					<ChevronRight className="h-3 w-3 text-[#5C5F66]" />
-					<span className="text-[#F8F8F9]">FE-312</span>
-					<div className="ml-auto flex items-center gap-2 text-[11px] text-[#8B8D91]">
-						<span className="h-2 w-2 rounded-full bg-[#27AE60]" />
-						Live demo
-					</div>
-				</div>
-
-				{/* Issue Content */}
-				<div
-					className={cn(
-						"flex flex-col flex-1 overflow-hidden transition-opacity duration-300 will-change-opacity",
-						issueVisible ? "opacity-100" : "opacity-0",
-					)}>
-					{/* Issue Title */}
-					<div className="px-4 pt-4 pb-3">
-						<h3 className="text-lg font-semibold text-[#F8F8F9] leading-tight">
-							Add dark mode toggle to settings
-						</h3>
-						<p className="mt-2 text-[13px] text-[#8B8D91] leading-relaxed">
-							Users should be able to switch between light and dark themes from the settings page. Persist
-							preference to localStorage and apply immediately.
-						</p>
-					</div>
-
-					{/* Activity Section */}
-					<div className="flex-1 overflow-hidden flex flex-col border-t border-white/10">
-						<div className="px-4 py-2.5 flex items-center justify-between">
-							<span className="text-[13px] font-medium text-[#F8F8F9]">Activity</span>
-							<span className="text-[12px] text-[#5C5F66]">Unsubscribe</span>
-						</div>
-						<div
-							ref={scrollViewportRef}
-							className="flex-1 overflow-y-auto px-4 pb-3 [scrollbar-width:thin] [scrollbar-color:rgba(255,255,255,0.1)_transparent]">
-							<div className="space-y-3">
-								{visibleActivities.map((item) => (
-									<ActivityRow
-										key={item.id}
-										item={item}
-										reduceMotion={reduceMotion}
-										isNew={
-											activePhase.kind === "show" &&
-											activityItems[activePhase.activityIndex]?.id === item.id
-										}
-									/>
-								))}
-
-								{typingTarget && typingTarget.kind === "comment" && (
-									<div
-										className={cn(
-											reduceMotion ? "" : "animate-in fade-in duration-300",
-											"flex gap-2.5",
-										)}>
-										<div
-											className={cn(
-												"mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[9px] font-semibold",
-												typingTarget.avatarClassName,
-											)}>
-											{typingTarget.avatarText}
-										</div>
-										<div className="min-w-0">
-											<div className="flex items-center gap-2 text-[13px]">
-												<span className="font-medium text-[#F8F8F9]">
-													{typingTarget.author}
-												</span>
-												<span className="text-[#8B8D91]">typing</span>
-												<TypingDots />
-											</div>
-										</div>
-									</div>
-								)}
-							</div>
-						</div>
-					</div>
-
-					{/* Comment Input */}
-					<div className="border-t border-white/10 px-4 py-3">
-						<div className="flex items-center gap-2 rounded-lg border border-white/10 bg-black/20 px-3 py-2">
-							<span className="flex-1 text-[13px] text-[#5C5F66]">Leave a comment...</span>
-							<Paperclip className="h-4 w-4 text-[#5C5F66]" />
-							<Send className="h-4 w-4 text-[#5C5F66]" />
-						</div>
-					</div>
-				</div>
-
-				{/* Progress indicator */}
-				<div className="flex items-center justify-center border-t border-white/10 px-4 py-2">
-					<div className="flex items-center gap-1">
-						{activityItems.map((item, idx) => (
-							<span
-								key={item.id}
-								className={cn(
-									"h-1 w-3 rounded-full transition-colors duration-300",
-									Math.max(0, visibleCount - 1) === idx ? "bg-indigo-400" : "bg-white/10",
-								)}
-							/>
-						))}
-					</div>
-				</div>
-			</div>
-		</div>
-	)
-}

+ 0 - 144
packages/types/src/__tests__/skills.test.ts

@@ -1,144 +0,0 @@
-import {
-	validateSkillName,
-	SkillNameValidationError,
-	SKILL_NAME_MIN_LENGTH,
-	SKILL_NAME_MAX_LENGTH,
-	SKILL_NAME_REGEX,
-} from "../skills.js"
-
-describe("validateSkillName", () => {
-	describe("valid names", () => {
-		it("accepts single lowercase word", () => {
-			expect(validateSkillName("myskill")).toEqual({ valid: true })
-		})
-
-		it("accepts lowercase letters and numbers", () => {
-			expect(validateSkillName("skill123")).toEqual({ valid: true })
-		})
-
-		it("accepts hyphenated words", () => {
-			expect(validateSkillName("my-skill")).toEqual({ valid: true })
-		})
-
-		it("accepts multiple hyphenated words", () => {
-			expect(validateSkillName("my-awesome-skill")).toEqual({ valid: true })
-		})
-
-		it("accepts single character", () => {
-			expect(validateSkillName("a")).toEqual({ valid: true })
-		})
-
-		it("accepts single digit", () => {
-			expect(validateSkillName("1")).toEqual({ valid: true })
-		})
-
-		it("accepts maximum length name (64 characters)", () => {
-			const maxLengthName = "a".repeat(SKILL_NAME_MAX_LENGTH)
-			expect(validateSkillName(maxLengthName)).toEqual({ valid: true })
-		})
-	})
-
-	describe("empty or missing names", () => {
-		it("rejects empty string", () => {
-			expect(validateSkillName("")).toEqual({
-				valid: false,
-				error: SkillNameValidationError.Empty,
-			})
-		})
-	})
-
-	describe("names that are too long", () => {
-		it("rejects names longer than 64 characters", () => {
-			const tooLongName = "a".repeat(SKILL_NAME_MAX_LENGTH + 1)
-			expect(validateSkillName(tooLongName)).toEqual({
-				valid: false,
-				error: SkillNameValidationError.TooLong,
-			})
-		})
-	})
-
-	describe("invalid format", () => {
-		it("rejects uppercase letters", () => {
-			expect(validateSkillName("MySkill")).toEqual({
-				valid: false,
-				error: SkillNameValidationError.InvalidFormat,
-			})
-		})
-
-		it("rejects leading hyphen", () => {
-			expect(validateSkillName("-myskill")).toEqual({
-				valid: false,
-				error: SkillNameValidationError.InvalidFormat,
-			})
-		})
-
-		it("rejects trailing hyphen", () => {
-			expect(validateSkillName("myskill-")).toEqual({
-				valid: false,
-				error: SkillNameValidationError.InvalidFormat,
-			})
-		})
-
-		it("rejects consecutive hyphens", () => {
-			expect(validateSkillName("my--skill")).toEqual({
-				valid: false,
-				error: SkillNameValidationError.InvalidFormat,
-			})
-		})
-
-		it("rejects spaces", () => {
-			expect(validateSkillName("my skill")).toEqual({
-				valid: false,
-				error: SkillNameValidationError.InvalidFormat,
-			})
-		})
-
-		it("rejects underscores", () => {
-			expect(validateSkillName("my_skill")).toEqual({
-				valid: false,
-				error: SkillNameValidationError.InvalidFormat,
-			})
-		})
-
-		it("rejects special characters", () => {
-			expect(validateSkillName("my@skill")).toEqual({
-				valid: false,
-				error: SkillNameValidationError.InvalidFormat,
-			})
-		})
-
-		it("rejects dots", () => {
-			expect(validateSkillName("my.skill")).toEqual({
-				valid: false,
-				error: SkillNameValidationError.InvalidFormat,
-			})
-		})
-	})
-})
-
-describe("SKILL_NAME_REGEX", () => {
-	it("matches valid names", () => {
-		expect(SKILL_NAME_REGEX.test("myskill")).toBe(true)
-		expect(SKILL_NAME_REGEX.test("my-skill")).toBe(true)
-		expect(SKILL_NAME_REGEX.test("skill123")).toBe(true)
-		expect(SKILL_NAME_REGEX.test("a1-b2-c3")).toBe(true)
-	})
-
-	it("does not match invalid names", () => {
-		expect(SKILL_NAME_REGEX.test("-start")).toBe(false)
-		expect(SKILL_NAME_REGEX.test("end-")).toBe(false)
-		expect(SKILL_NAME_REGEX.test("double--hyphen")).toBe(false)
-		expect(SKILL_NAME_REGEX.test("UPPER")).toBe(false)
-		expect(SKILL_NAME_REGEX.test("")).toBe(false)
-	})
-})
-
-describe("constants", () => {
-	it("has correct min length", () => {
-		expect(SKILL_NAME_MIN_LENGTH).toBe(1)
-	})
-
-	it("has correct max length", () => {
-		expect(SKILL_NAME_MAX_LENGTH).toBe(64)
-	})
-})

+ 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(),

+ 0 - 1
packages/types/src/index.ts

@@ -19,7 +19,6 @@ export * from "./message.js"
 export * from "./mode.js"
 export * from "./model.js"
 export * from "./provider-settings.js"
-export * from "./skills.js"
 export * from "./task.js"
 export * from "./todo.js"
 export * from "./telemetry.js"

+ 0 - 71
packages/types/src/skills.ts

@@ -1,71 +0,0 @@
-/**
- * Skill metadata for discovery (loaded at startup)
- * Only name and description are required for now
- */
-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
-	mode?: string // If set, skill is only available in this mode
-}
-
-/**
- * Skill name validation constants per agentskills.io specification:
- * https://agentskills.io/specification
- *
- * Name constraints:
- * - 1-64 characters
- * - Lowercase letters, numbers, and hyphens only
- * - Must not start or end with a hyphen
- * - Must not contain consecutive hyphens
- */
-export const SKILL_NAME_MIN_LENGTH = 1
-export const SKILL_NAME_MAX_LENGTH = 64
-
-/**
- * Regex pattern for valid skill names.
- * Matches: lowercase letters/numbers, optionally followed by groups of hyphen + lowercase letters/numbers.
- * This ensures no leading/trailing hyphens and no consecutive hyphens.
- */
-export const SKILL_NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
-
-/**
- * Error codes for skill name validation.
- * These can be mapped to translation keys in the frontend or error messages in the backend.
- */
-export enum SkillNameValidationError {
-	Empty = "empty",
-	TooLong = "too_long",
-	InvalidFormat = "invalid_format",
-}
-
-/**
- * Result of skill name validation.
- */
-export interface SkillNameValidationResult {
-	valid: boolean
-	error?: SkillNameValidationError
-}
-
-/**
- * Validate a skill name according to agentskills.io specification.
- *
- * @param name - The skill name to validate
- * @returns Validation result with error code if invalid
- */
-export function validateSkillName(name: string): SkillNameValidationResult {
-	if (!name || name.length < SKILL_NAME_MIN_LENGTH) {
-		return { valid: false, error: SkillNameValidationError.Empty }
-	}
-
-	if (name.length > SKILL_NAME_MAX_LENGTH) {
-		return { valid: false, error: SkillNameValidationError.TooLong }
-	}
-
-	if (!SKILL_NAME_REGEX.test(name)) {
-		return { valid: false, error: SkillNameValidationError.InvalidFormat }
-	}
-
-	return { valid: true }
-}

+ 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

+ 3 - 14
packages/types/src/vscode-extension-host.ts

@@ -18,7 +18,6 @@ import type { CloudUserInfo, CloudOrganizationMembership, OrganizationAllowList,
 import type { SerializedCustomToolDefinition } from "./custom-tool.js"
 import type { GitCommit } from "./git.js"
 import type { McpServer } from "./mcp.js"
-import type { SkillMetadata } from "./skills.js"
 import type { ModelRecord, RouterModels } from "./model.js"
 import type { OpenAiCodexRateLimitInfo } from "./providers/openai-codex-rate-limits.js"
 import type { WorktreeIncludeStatus } from "./worktree.js"
@@ -109,7 +108,6 @@ export interface ExtensionMessage {
 		| "worktreeIncludeStatus"
 		| "branchWorktreeIncludeResult"
 		| "folderSelected"
-		| "skills"
 	text?: string
 	payload?: any // eslint-disable-line @typescript-eslint/no-explicit-any
 	checkpointWarning?: {
@@ -204,7 +202,6 @@ export interface ExtensionMessage {
 	stepIndex?: number // For browserSessionNavigate: the target step index to display
 	tools?: SerializedCustomToolDefinition[] // For customToolsResult
 	modes?: { slug: string; name: string }[] // For modes response
-	skills?: SkillMetadata[] // For skills response
 	aggregatedCosts?: {
 		// For taskWithAggregatedCosts response
 		totalCost: number
@@ -362,7 +359,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 +498,6 @@ export interface WebviewMessage {
 		| "deleteMessageConfirm"
 		| "submitEditedMessage"
 		| "editMessageConfirm"
-		| "enableMcpServerCreation"
 		| "remoteControlEnabled"
 		| "taskSyncEnabled"
 		| "searchCommits"
@@ -605,11 +600,6 @@ export interface WebviewMessage {
 		| "createWorktreeInclude"
 		| "checkoutBranch"
 		| "browseForWorktreePath"
-		// Skills messages
-		| "requestSkills"
-		| "createSkill"
-		| "deleteSkill"
-		| "openSkillFile"
 	text?: string
 	editedMessageContent?: string
 	tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"
@@ -644,9 +634,6 @@ export interface WebviewMessage {
 	timeout?: number
 	payload?: WebViewMessagePayload
 	source?: "global" | "project"
-	skillName?: string // For skill operations (createSkill, deleteSkill, openSkillFile)
-	skillMode?: string // For skill operations (mode restriction)
-	skillDescription?: string // For createSkill (skill description)
 	requestId?: string
 	ids?: string[]
 	hasSystemPromptOverride?: boolean
@@ -790,7 +777,6 @@ export interface ClineSayTool {
 		| "codebaseSearch"
 		| "readFile"
 		| "readCommandOutput"
-		| "fetchInstructions"
 		| "listFilesTopLevel"
 		| "listFilesRecursive"
 		| "searchFiles"
@@ -801,6 +787,7 @@ export interface ClineSayTool {
 		| "imageGenerated"
 		| "runSlashCommand"
 		| "updateTodoList"
+		| "skill"
 	path?: string
 	// For readCommandOutput
 	readStart?: number
@@ -847,6 +834,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
+}
+
+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 - 334
src/core/webview/__tests__/skillsMessageHandler.spec.ts

@@ -1,334 +0,0 @@
-// npx vitest run src/core/webview/__tests__/skillsMessageHandler.spec.ts
-
-import type { SkillMetadata, WebviewMessage } from "@roo-code/types"
-import type { ClineProvider } from "../ClineProvider"
-
-// Mock vscode first
-vi.mock("vscode", () => {
-	const showErrorMessage = vi.fn()
-
-	return {
-		window: {
-			showErrorMessage,
-		},
-	}
-})
-
-// Mock open-file
-vi.mock("../../../integrations/misc/open-file", () => ({
-	openFile: vi.fn(),
-}))
-
-// Mock i18n
-vi.mock("../../../i18n", () => ({
-	t: (key: string, params?: Record<string, any>) => {
-		const translations: Record<string, string> = {
-			"skills:errors.missing_create_fields": "Missing required fields: skillName, source, or skillDescription",
-			"skills:errors.manager_unavailable": "Skills manager not available",
-			"skills:errors.missing_delete_fields": "Missing required fields: skillName or source",
-			"skills:errors.skill_not_found": `Skill "${params?.name}" not found`,
-		}
-		return translations[key] || key
-	},
-}))
-
-import * as vscode from "vscode"
-import { openFile } from "../../../integrations/misc/open-file"
-import { handleRequestSkills, handleCreateSkill, handleDeleteSkill, handleOpenSkillFile } from "../skillsMessageHandler"
-
-describe("skillsMessageHandler", () => {
-	const mockLog = vi.fn()
-	const mockPostMessageToWebview = vi.fn()
-	const mockGetSkillsMetadata = vi.fn()
-	const mockCreateSkill = vi.fn()
-	const mockDeleteSkill = vi.fn()
-	const mockGetSkill = vi.fn()
-
-	const createMockProvider = (hasSkillsManager: boolean = true): ClineProvider => {
-		const skillsManager = hasSkillsManager
-			? {
-					getSkillsMetadata: mockGetSkillsMetadata,
-					createSkill: mockCreateSkill,
-					deleteSkill: mockDeleteSkill,
-					getSkill: mockGetSkill,
-				}
-			: undefined
-
-		return {
-			log: mockLog,
-			postMessageToWebview: mockPostMessageToWebview,
-			getSkillsManager: () => skillsManager,
-		} as unknown as ClineProvider
-	}
-
-	const mockSkills: SkillMetadata[] = [
-		{
-			name: "test-skill",
-			description: "Test skill description",
-			path: "/path/to/test-skill/SKILL.md",
-			source: "global",
-		},
-		{
-			name: "project-skill",
-			description: "Project skill description",
-			path: "/project/.roo/skills/project-skill/SKILL.md",
-			source: "project",
-			mode: "code",
-		},
-	]
-
-	beforeEach(() => {
-		vi.clearAllMocks()
-	})
-
-	describe("handleRequestSkills", () => {
-		it("returns skills when skills manager is available", async () => {
-			const provider = createMockProvider(true)
-			mockGetSkillsMetadata.mockReturnValue(mockSkills)
-
-			const result = await handleRequestSkills(provider)
-
-			expect(result).toEqual(mockSkills)
-			expect(mockPostMessageToWebview).toHaveBeenCalledWith({ type: "skills", skills: mockSkills })
-		})
-
-		it("returns empty skills when skills manager is not available", async () => {
-			const provider = createMockProvider(false)
-
-			const result = await handleRequestSkills(provider)
-
-			expect(result).toEqual([])
-			expect(mockPostMessageToWebview).toHaveBeenCalledWith({ type: "skills", skills: [] })
-		})
-
-		it("handles errors and returns empty skills", async () => {
-			const provider = createMockProvider(true)
-			mockGetSkillsMetadata.mockImplementation(() => {
-				throw new Error("Test error")
-			})
-
-			const result = await handleRequestSkills(provider)
-
-			expect(result).toEqual([])
-			expect(mockLog).toHaveBeenCalled()
-			expect(mockPostMessageToWebview).toHaveBeenCalledWith({ type: "skills", skills: [] })
-		})
-	})
-
-	describe("handleCreateSkill", () => {
-		it("creates a skill successfully", async () => {
-			const provider = createMockProvider(true)
-			mockCreateSkill.mockResolvedValue("/path/to/new-skill/SKILL.md")
-			mockGetSkillsMetadata.mockReturnValue(mockSkills)
-
-			const result = await handleCreateSkill(provider, {
-				type: "createSkill",
-				skillName: "new-skill",
-				source: "global",
-				skillDescription: "New skill description",
-			} as WebviewMessage)
-
-			expect(result).toEqual(mockSkills)
-			expect(mockCreateSkill).toHaveBeenCalledWith("new-skill", "global", "New skill description", undefined)
-			expect(openFile).toHaveBeenCalledWith("/path/to/new-skill/SKILL.md")
-			expect(mockPostMessageToWebview).toHaveBeenCalledWith({ type: "skills", skills: mockSkills })
-		})
-
-		it("creates a skill with mode restriction", async () => {
-			const provider = createMockProvider(true)
-			mockCreateSkill.mockResolvedValue("/path/to/new-skill/SKILL.md")
-			mockGetSkillsMetadata.mockReturnValue(mockSkills)
-
-			const result = await handleCreateSkill(provider, {
-				type: "createSkill",
-				skillName: "new-skill",
-				source: "project",
-				skillDescription: "New skill description",
-				skillMode: "code",
-			} as WebviewMessage)
-
-			expect(result).toEqual(mockSkills)
-			expect(mockCreateSkill).toHaveBeenCalledWith("new-skill", "project", "New skill description", "code")
-		})
-
-		it("returns undefined when required fields are missing", async () => {
-			const provider = createMockProvider(true)
-
-			const result = await handleCreateSkill(provider, {
-				type: "createSkill",
-				skillName: "new-skill",
-				// missing source and skillDescription
-			} as WebviewMessage)
-
-			expect(result).toBeUndefined()
-			expect(mockLog).toHaveBeenCalledWith(
-				"Error creating skill: Missing required fields: skillName, source, or skillDescription",
-			)
-			expect(vscode.window.showErrorMessage).toHaveBeenCalledWith(
-				"Failed to create skill: Missing required fields: skillName, source, or skillDescription",
-			)
-		})
-
-		it("returns undefined when skills manager is not available", async () => {
-			const provider = createMockProvider(false)
-
-			const result = await handleCreateSkill(provider, {
-				type: "createSkill",
-				skillName: "new-skill",
-				source: "global",
-				skillDescription: "New skill description",
-			} as WebviewMessage)
-
-			expect(result).toBeUndefined()
-			expect(mockLog).toHaveBeenCalledWith("Error creating skill: Skills manager not available")
-			expect(vscode.window.showErrorMessage).toHaveBeenCalledWith(
-				"Failed to create skill: Skills manager not available",
-			)
-		})
-	})
-
-	describe("handleDeleteSkill", () => {
-		it("deletes a skill successfully", async () => {
-			const provider = createMockProvider(true)
-			mockDeleteSkill.mockResolvedValue(undefined)
-			mockGetSkillsMetadata.mockReturnValue([mockSkills[1]])
-
-			const result = await handleDeleteSkill(provider, {
-				type: "deleteSkill",
-				skillName: "test-skill",
-				source: "global",
-			} as WebviewMessage)
-
-			expect(result).toEqual([mockSkills[1]])
-			expect(mockDeleteSkill).toHaveBeenCalledWith("test-skill", "global", undefined)
-			expect(mockPostMessageToWebview).toHaveBeenCalledWith({ type: "skills", skills: [mockSkills[1]] })
-		})
-
-		it("deletes a skill with mode restriction", async () => {
-			const provider = createMockProvider(true)
-			mockDeleteSkill.mockResolvedValue(undefined)
-			mockGetSkillsMetadata.mockReturnValue([mockSkills[0]])
-
-			const result = await handleDeleteSkill(provider, {
-				type: "deleteSkill",
-				skillName: "project-skill",
-				source: "project",
-				skillMode: "code",
-			} as WebviewMessage)
-
-			expect(result).toEqual([mockSkills[0]])
-			expect(mockDeleteSkill).toHaveBeenCalledWith("project-skill", "project", "code")
-		})
-
-		it("returns undefined when required fields are missing", async () => {
-			const provider = createMockProvider(true)
-
-			const result = await handleDeleteSkill(provider, {
-				type: "deleteSkill",
-				skillName: "test-skill",
-				// missing source
-			} as WebviewMessage)
-
-			expect(result).toBeUndefined()
-			expect(mockLog).toHaveBeenCalledWith("Error deleting skill: Missing required fields: skillName or source")
-			expect(vscode.window.showErrorMessage).toHaveBeenCalledWith(
-				"Failed to delete skill: Missing required fields: skillName or source",
-			)
-		})
-
-		it("returns undefined when skills manager is not available", async () => {
-			const provider = createMockProvider(false)
-
-			const result = await handleDeleteSkill(provider, {
-				type: "deleteSkill",
-				skillName: "test-skill",
-				source: "global",
-			} as WebviewMessage)
-
-			expect(result).toBeUndefined()
-			expect(mockLog).toHaveBeenCalledWith("Error deleting skill: Skills manager not available")
-			expect(vscode.window.showErrorMessage).toHaveBeenCalledWith(
-				"Failed to delete skill: Skills manager not available",
-			)
-		})
-	})
-
-	describe("handleOpenSkillFile", () => {
-		it("opens a skill file successfully", async () => {
-			const provider = createMockProvider(true)
-			mockGetSkill.mockReturnValue(mockSkills[0])
-
-			await handleOpenSkillFile(provider, {
-				type: "openSkillFile",
-				skillName: "test-skill",
-				source: "global",
-			} as WebviewMessage)
-
-			expect(mockGetSkill).toHaveBeenCalledWith("test-skill", "global", undefined)
-			expect(openFile).toHaveBeenCalledWith("/path/to/test-skill/SKILL.md")
-		})
-
-		it("opens a skill file with mode restriction", async () => {
-			const provider = createMockProvider(true)
-			mockGetSkill.mockReturnValue(mockSkills[1])
-
-			await handleOpenSkillFile(provider, {
-				type: "openSkillFile",
-				skillName: "project-skill",
-				source: "project",
-				skillMode: "code",
-			} as WebviewMessage)
-
-			expect(mockGetSkill).toHaveBeenCalledWith("project-skill", "project", "code")
-			expect(openFile).toHaveBeenCalledWith("/project/.roo/skills/project-skill/SKILL.md")
-		})
-
-		it("shows error when required fields are missing", async () => {
-			const provider = createMockProvider(true)
-
-			await handleOpenSkillFile(provider, {
-				type: "openSkillFile",
-				skillName: "test-skill",
-				// missing source
-			} as WebviewMessage)
-
-			expect(mockLog).toHaveBeenCalledWith(
-				"Error opening skill file: Missing required fields: skillName or source",
-			)
-			expect(vscode.window.showErrorMessage).toHaveBeenCalledWith(
-				"Failed to open skill file: Missing required fields: skillName or source",
-			)
-		})
-
-		it("shows error when skills manager is not available", async () => {
-			const provider = createMockProvider(false)
-
-			await handleOpenSkillFile(provider, {
-				type: "openSkillFile",
-				skillName: "test-skill",
-				source: "global",
-			} as WebviewMessage)
-
-			expect(mockLog).toHaveBeenCalledWith("Error opening skill file: Skills manager not available")
-			expect(vscode.window.showErrorMessage).toHaveBeenCalledWith(
-				"Failed to open skill file: Skills manager not available",
-			)
-		})
-
-		it("shows error when skill is not found", async () => {
-			const provider = createMockProvider(true)
-			mockGetSkill.mockReturnValue(undefined)
-
-			await handleOpenSkillFile(provider, {
-				type: "openSkillFile",
-				skillName: "nonexistent-skill",
-				source: "global",
-			} as WebviewMessage)
-
-			expect(mockLog).toHaveBeenCalledWith('Error opening skill file: Skill "nonexistent-skill" not found')
-			expect(vscode.window.showErrorMessage).toHaveBeenCalledWith(
-				'Failed to open skill file: Skill "nonexistent-skill" not found',
-			)
-		})
-	})
-})

+ 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,

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

@@ -1,133 +0,0 @@
-import * as vscode from "vscode"
-
-import type { SkillMetadata, WebviewMessage } from "@roo-code/types"
-
-import type { ClineProvider } from "./ClineProvider"
-import { openFile } from "../../integrations/misc/open-file"
-import { t } from "../../i18n"
-
-/**
- * Handles the requestSkills message - returns all skills metadata
- */
-export async function handleRequestSkills(provider: ClineProvider): Promise<SkillMetadata[]> {
-	try {
-		const skillsManager = provider.getSkillsManager()
-		if (skillsManager) {
-			const skills = skillsManager.getSkillsMetadata()
-			await provider.postMessageToWebview({ type: "skills", skills })
-			return skills
-		} else {
-			await provider.postMessageToWebview({ type: "skills", skills: [] })
-			return []
-		}
-	} catch (error) {
-		provider.log(`Error fetching skills: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`)
-		await provider.postMessageToWebview({ type: "skills", skills: [] })
-		return []
-	}
-}
-
-/**
- * Handles the createSkill message - creates a new skill
- */
-export async function handleCreateSkill(
-	provider: ClineProvider,
-	message: WebviewMessage,
-): Promise<SkillMetadata[] | undefined> {
-	try {
-		const skillName = message.skillName
-		const source = message.source
-		const skillDescription = message.skillDescription
-		const skillMode = message.skillMode
-
-		if (!skillName || !source || !skillDescription) {
-			throw new Error(t("skills:errors.missing_create_fields"))
-		}
-
-		const skillsManager = provider.getSkillsManager()
-		if (!skillsManager) {
-			throw new Error(t("skills:errors.manager_unavailable"))
-		}
-
-		const createdPath = await skillsManager.createSkill(skillName, source, skillDescription, skillMode)
-
-		// Open the created file in the editor
-		openFile(createdPath)
-
-		// Send updated skills list
-		const skills = skillsManager.getSkillsMetadata()
-		await provider.postMessageToWebview({ type: "skills", skills })
-		return skills
-	} catch (error) {
-		const errorMessage = error instanceof Error ? error.message : String(error)
-		provider.log(`Error creating skill: ${errorMessage}`)
-		vscode.window.showErrorMessage(`Failed to create skill: ${errorMessage}`)
-		return undefined
-	}
-}
-
-/**
- * Handles the deleteSkill message - deletes a skill
- */
-export async function handleDeleteSkill(
-	provider: ClineProvider,
-	message: WebviewMessage,
-): Promise<SkillMetadata[] | undefined> {
-	try {
-		const skillName = message.skillName
-		const source = message.source
-		const skillMode = message.skillMode
-
-		if (!skillName || !source) {
-			throw new Error(t("skills:errors.missing_delete_fields"))
-		}
-
-		const skillsManager = provider.getSkillsManager()
-		if (!skillsManager) {
-			throw new Error(t("skills:errors.manager_unavailable"))
-		}
-
-		await skillsManager.deleteSkill(skillName, source, skillMode)
-
-		// Send updated skills list
-		const skills = skillsManager.getSkillsMetadata()
-		await provider.postMessageToWebview({ type: "skills", skills })
-		return skills
-	} catch (error) {
-		const errorMessage = error instanceof Error ? error.message : String(error)
-		provider.log(`Error deleting skill: ${errorMessage}`)
-		vscode.window.showErrorMessage(`Failed to delete skill: ${errorMessage}`)
-		return undefined
-	}
-}
-
-/**
- * Handles the openSkillFile message - opens a skill file in the editor
- */
-export async function handleOpenSkillFile(provider: ClineProvider, message: WebviewMessage): Promise<void> {
-	try {
-		const skillName = message.skillName
-		const source = message.source
-		const skillMode = message.skillMode
-
-		if (!skillName || !source) {
-			throw new Error(t("skills:errors.missing_delete_fields"))
-		}
-
-		const skillsManager = provider.getSkillsManager()
-		if (!skillsManager) {
-			throw new Error(t("skills:errors.manager_unavailable"))
-		}
-
-		const skill = skillsManager.getSkill(skillName, source, skillMode)
-		if (!skill) {
-			throw new Error(t("skills:errors.skill_not_found", { name: skillName }))
-		}
-
-		openFile(skill.path)
-	} catch (error) {
-		const errorMessage = error instanceof Error ? error.message : String(error)
-		provider.log(`Error opening skill file: ${errorMessage}`)
-		vscode.window.showErrorMessage(`Failed to open skill file: ${errorMessage}`)
-	}
-}

+ 0 - 21
src/core/webview/webviewMessageHandler.ts

@@ -32,7 +32,6 @@ import { ClineProvider } from "./ClineProvider"
 import { BrowserSessionPanelManager } from "./BrowserSessionPanelManager"
 import { handleCheckpointRestoreOperation } from "./checkpointRestoreHandler"
 import { generateErrorDiagnostics } from "./diagnosticsHandler"
-import { handleRequestSkills, handleCreateSkill, handleDeleteSkill, handleOpenSkillFile } from "./skillsMessageHandler"
 import { changeLanguage, t } from "../../i18n"
 import { Package } from "../../shared/package"
 import { type RouterName, toRouterName } from "../../shared/api"
@@ -1444,10 +1443,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 })
@@ -2975,22 +2970,6 @@ export const webviewMessageHandler = async (
 			}
 			break
 		}
-		case "requestSkills": {
-			await handleRequestSkills(provider)
-			break
-		}
-		case "createSkill": {
-			await handleCreateSkill(provider, message)
-			break
-		}
-		case "deleteSkill": {
-			await handleDeleteSkill(provider, message)
-			break
-		}
-		case "openSkillFile": {
-			await handleOpenSkillFile(provider, message)
-			break
-		}
 		case "openCommandFile": {
 			try {
 				if (message.text) {

+ 0 - 14
src/i18n/locales/ca/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "El nom de l'habilitat ha de tenir entre 1 i {{maxLength}} caràcters (s'han rebut {{length}})",
-		"name_format": "El nom de l'habilitat només pot contenir lletres minúscules, números i guions (sense guions inicials o finals, sense guions consecutius)",
-		"description_length": "La descripció de l'habilitat ha de tenir entre 1 i 1024 caràcters (s'han rebut {{length}})",
-		"no_workspace": "No es pot crear l'habilitat del projecte: no hi ha cap carpeta d'espai de treball oberta",
-		"already_exists": "L'habilitat \"{{name}}\" ja existeix a {{path}}",
-		"not_found": "No s'ha trobat l'habilitat \"{{name}}\" a {{source}}{{modeInfo}}",
-		"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}}\""
-	}
-}

+ 0 - 14
src/i18n/locales/de/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "Skill-Name muss 1-{{maxLength}} Zeichen lang sein (erhalten: {{length}})",
-		"name_format": "Skill-Name darf nur Kleinbuchstaben, Zahlen und Bindestriche enthalten (keine führenden oder nachgestellten Bindestriche, keine aufeinanderfolgenden Bindestriche)",
-		"description_length": "Skill-Beschreibung muss 1-1024 Zeichen lang sein (erhalten: {{length}})",
-		"no_workspace": "Projekt-Skill kann nicht erstellt werden: kein Workspace-Ordner ist geöffnet",
-		"already_exists": "Skill \"{{name}}\" existiert bereits unter {{path}}",
-		"not_found": "Skill \"{{name}}\" nicht gefunden in {{source}}{{modeInfo}}",
-		"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"
-	}
-}

+ 0 - 14
src/i18n/locales/en/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "Skill name must be 1-{{maxLength}} characters (got {{length}})",
-		"name_format": "Skill name must be lowercase letters/numbers/hyphens only (no leading/trailing hyphen, no consecutive hyphens)",
-		"description_length": "Skill description must be 1-1024 characters (got {{length}})",
-		"no_workspace": "Cannot create project skill: no workspace folder is open",
-		"already_exists": "Skill \"{{name}}\" already exists at {{path}}",
-		"not_found": "Skill \"{{name}}\" not found in {{source}}{{modeInfo}}",
-		"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"
-	}
-}

+ 0 - 14
src/i18n/locales/es/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "El nombre de la habilidad debe tener entre 1 y {{maxLength}} caracteres (se recibieron {{length}})",
-		"name_format": "El nombre de la habilidad solo puede contener letras minúsculas, números y guiones (sin guiones al inicio o al final, sin guiones consecutivos)",
-		"description_length": "La descripción de la habilidad debe tener entre 1 y 1024 caracteres (se recibieron {{length}})",
-		"no_workspace": "No se puede crear la habilidad del proyecto: no hay ninguna carpeta de espacio de trabajo abierta",
-		"already_exists": "La habilidad \"{{name}}\" ya existe en {{path}}",
-		"not_found": "No se encontró la habilidad \"{{name}}\" en {{source}}{{modeInfo}}",
-		"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}}\""
-	}
-}

+ 0 - 14
src/i18n/locales/fr/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "Le nom de la compétence doit contenir entre 1 et {{maxLength}} caractères ({{length}} reçu)",
-		"name_format": "Le nom de la compétence ne peut contenir que des lettres minuscules, des chiffres et des traits d'union (pas de trait d'union initial ou final, pas de traits d'union consécutifs)",
-		"description_length": "La description de la compétence doit contenir entre 1 et 1024 caractères ({{length}} reçu)",
-		"no_workspace": "Impossible de créer la compétence de projet : aucun dossier d'espace de travail n'est ouvert",
-		"already_exists": "La compétence \"{{name}}\" existe déjà à {{path}}",
-		"not_found": "Compétence \"{{name}}\" introuvable dans {{source}}{{modeInfo}}",
-		"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"
-	}
-}

+ 0 - 14
src/i18n/locales/hi/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "स्किल का नाम 1-{{maxLength}} वर्णों का होना चाहिए ({{length}} प्राप्त हुआ)",
-		"name_format": "स्किल के नाम में केवल छोटे अक्षर, संख्याएं और हाइफ़न हो सकते हैं (शुरुआत या अंत में हाइफ़न नहीं, लगातार हाइफ़न नहीं)",
-		"description_length": "स्किल का विवरण 1-1024 वर्णों का होना चाहिए ({{length}} प्राप्त हुआ)",
-		"no_workspace": "प्रोजेक्ट स्किल नहीं बनाया जा सकता: कोई वर्कस्पेस फ़ोल्डर खुला नहीं है",
-		"already_exists": "स्किल \"{{name}}\" पहले से {{path}} पर मौजूद है",
-		"not_found": "स्किल \"{{name}}\" {{source}}{{modeInfo}} में नहीं मिला",
-		"missing_create_fields": "आवश्यक फ़ील्ड गायब हैं: skillName, source, या skillDescription",
-		"manager_unavailable": "स्किल मैनेजर उपलब्ध नहीं है",
-		"missing_delete_fields": "आवश्यक फ़ील्ड गायब हैं: skillName या source",
-		"skill_not_found": "स्किल \"{{name}}\" नहीं मिला"
-	}
-}

+ 0 - 14
src/i18n/locales/id/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "Nama skill harus 1-{{maxLength}} karakter (diterima {{length}})",
-		"name_format": "Nama skill hanya boleh berisi huruf kecil, angka, dan tanda hubung (tanpa tanda hubung di awal atau akhir, tanpa tanda hubung berturut-turut)",
-		"description_length": "Deskripsi skill harus 1-1024 karakter (diterima {{length}})",
-		"no_workspace": "Tidak dapat membuat skill proyek: tidak ada folder workspace yang terbuka",
-		"already_exists": "Skill \"{{name}}\" sudah ada di {{path}}",
-		"not_found": "Skill \"{{name}}\" tidak ditemukan di {{source}}{{modeInfo}}",
-		"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"
-	}
-}

+ 0 - 14
src/i18n/locales/it/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "Il nome della skill deve essere di 1-{{maxLength}} caratteri (ricevuti {{length}})",
-		"name_format": "Il nome della skill può contenere solo lettere minuscole, numeri e trattini (senza trattini iniziali o finali, senza trattini consecutivi)",
-		"description_length": "La descrizione della skill deve essere di 1-1024 caratteri (ricevuti {{length}})",
-		"no_workspace": "Impossibile creare la skill del progetto: nessuna cartella di workspace aperta",
-		"already_exists": "La skill \"{{name}}\" esiste già in {{path}}",
-		"not_found": "Skill \"{{name}}\" non trovata in {{source}}{{modeInfo}}",
-		"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"
-	}
-}

+ 0 - 14
src/i18n/locales/ja/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "スキル名は1-{{maxLength}}文字である必要があります({{length}}文字を受信)",
-		"name_format": "スキル名には小文字、数字、ハイフンのみ使用できます(先頭または末尾のハイフン、連続するハイフンは不可)",
-		"description_length": "スキルの説明は1-1024文字である必要があります({{length}}文字を受信)",
-		"no_workspace": "プロジェクトスキルを作成できません:ワークスペースフォルダが開かれていません",
-		"already_exists": "スキル「{{name}}」は既に{{path}}に存在します",
-		"not_found": "スキル「{{name}}」が{{source}}{{modeInfo}}に見つかりません",
-		"missing_create_fields": "必須フィールドが不足しています:skillName、source、またはskillDescription",
-		"manager_unavailable": "スキルマネージャーが利用できません",
-		"missing_delete_fields": "必須フィールドが不足しています:skillNameまたはsource",
-		"skill_not_found": "スキル「{{name}}」が見つかりません"
-	}
-}

+ 0 - 14
src/i18n/locales/ko/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "스킬 이름은 1-{{maxLength}}자여야 합니다({{length}}자 수신됨)",
-		"name_format": "스킬 이름은 소문자, 숫자, 하이픈만 포함할 수 있습니다(앞뒤 하이픈 없음, 연속 하이픈 없음)",
-		"description_length": "스킬 설명은 1-1024자여야 합니다({{length}}자 수신됨)",
-		"no_workspace": "프로젝트 스킬을 생성할 수 없습니다: 열린 작업 공간 폴더가 없습니다",
-		"already_exists": "스킬 \"{{name}}\"이(가) 이미 {{path}}에 존재합니다",
-		"not_found": "{{source}}{{modeInfo}}에서 스킬 \"{{name}}\"을(를) 찾을 수 없습니다",
-		"missing_create_fields": "필수 필드 누락: skillName, source 또는 skillDescription",
-		"manager_unavailable": "스킬 관리자를 사용할 수 없습니다",
-		"missing_delete_fields": "필수 필드 누락: skillName 또는 source",
-		"skill_not_found": "스킬 \"{{name}}\"을(를) 찾을 수 없습니다"
-	}
-}

+ 0 - 14
src/i18n/locales/nl/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "Vaardigheidsnaam moet 1-{{maxLength}} tekens lang zijn ({{length}} ontvangen)",
-		"name_format": "Vaardigheidsnaam mag alleen kleine letters, cijfers en koppeltekens bevatten (geen voorloop- of achterloop-koppeltekens, geen opeenvolgende koppeltekens)",
-		"description_length": "Vaardigheidsbeschrijving moet 1-1024 tekens lang zijn ({{length}} ontvangen)",
-		"no_workspace": "Kan projectvaardigheid niet aanmaken: geen werkruimtemap geopend",
-		"already_exists": "Vaardigheid \"{{name}}\" bestaat al op {{path}}",
-		"not_found": "Vaardigheid \"{{name}}\" niet gevonden in {{source}}{{modeInfo}}",
-		"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"
-	}
-}

+ 0 - 14
src/i18n/locales/pl/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "Nazwa umiejętności musi mieć 1-{{maxLength}} znaków (otrzymano {{length}})",
-		"name_format": "Nazwa umiejętności może zawierać tylko małe litery, cyfry i myślniki (bez myślników na początku lub końcu, bez następujących po sobie myślników)",
-		"description_length": "Opis umiejętności musi mieć 1-1024 znaków (otrzymano {{length}})",
-		"no_workspace": "Nie można utworzyć umiejętności projektu: nie otwarto folderu obszaru roboczego",
-		"already_exists": "Umiejętność \"{{name}}\" już istnieje w {{path}}",
-		"not_found": "Nie znaleziono umiejętności \"{{name}}\" w {{source}}{{modeInfo}}",
-		"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}}\""
-	}
-}

+ 0 - 14
src/i18n/locales/pt-BR/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "O nome da habilidade deve ter de 1 a {{maxLength}} caracteres (recebido {{length}})",
-		"name_format": "O nome da habilidade só pode conter letras minúsculas, números e hifens (sem hifens iniciais ou finais, sem hifens consecutivos)",
-		"description_length": "A descrição da habilidade deve ter de 1 a 1024 caracteres (recebido {{length}})",
-		"no_workspace": "Não é possível criar habilidade do projeto: nenhuma pasta de espaço de trabalho está aberta",
-		"already_exists": "A habilidade \"{{name}}\" já existe em {{path}}",
-		"not_found": "Habilidade \"{{name}}\" não encontrada em {{source}}{{modeInfo}}",
-		"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"
-	}
-}

+ 0 - 14
src/i18n/locales/ru/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "Имя навыка должно быть от 1 до {{maxLength}} символов (получено {{length}})",
-		"name_format": "Имя навыка может содержать только строчные буквы, цифры и дефисы (без начальных или конечных дефисов, без последовательных дефисов)",
-		"description_length": "Описание навыка должно быть от 1 до 1024 символов (получено {{length}})",
-		"no_workspace": "Невозможно создать навык проекта: не открыта папка рабочего пространства",
-		"already_exists": "Навык \"{{name}}\" уже существует в {{path}}",
-		"not_found": "Навык \"{{name}}\" не найден в {{source}}{{modeInfo}}",
-		"missing_create_fields": "Отсутствуют обязательные поля: skillName, source или skillDescription",
-		"manager_unavailable": "Менеджер навыков недоступен",
-		"missing_delete_fields": "Отсутствуют обязательные поля: skillName или source",
-		"skill_not_found": "Навык \"{{name}}\" не найден"
-	}
-}

+ 0 - 14
src/i18n/locales/tr/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "Beceri adı 1-{{maxLength}} karakter olmalıdır ({{length}} alındı)",
-		"name_format": "Beceri adı yalnızca küçük harfler, rakamlar ve tire içerebilir (başta veya sonda tire yok, ardışık tire yok)",
-		"description_length": "Beceri açıklaması 1-1024 karakter olmalıdır ({{length}} alındı)",
-		"no_workspace": "Proje becerisi oluşturulamıyor: açık çalışma alanı klasörü yok",
-		"already_exists": "\"{{name}}\" becerisi zaten {{path}} konumunda mevcut",
-		"not_found": "\"{{name}}\" becerisi {{source}}{{modeInfo}} içinde bulunamadı",
-		"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ı"
-	}
-}

+ 0 - 14
src/i18n/locales/vi/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "Tên kỹ năng phải từ 1-{{maxLength}} ký tự (nhận được {{length}})",
-		"name_format": "Tên kỹ năng chỉ có thể chứa chữ cái thường, số và dấu gạch ngang (không có dấu gạch ngang đầu hoặc cuối, không có dấu gạch ngang liên tiếp)",
-		"description_length": "Mô tả kỹ năng phải từ 1-1024 ký tự (nhận được {{length}})",
-		"no_workspace": "Không thể tạo kỹ năng dự án: không có thư mục vùng làm việc nào được mở",
-		"already_exists": "Kỹ năng \"{{name}}\" đã tồn tại tại {{path}}",
-		"not_found": "Không tìm thấy kỹ năng \"{{name}}\" trong {{source}}{{modeInfo}}",
-		"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}}\""
-	}
-}

+ 0 - 14
src/i18n/locales/zh-CN/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "技能名称必须为 1-{{maxLength}} 个字符(收到 {{length}} 个)",
-		"name_format": "技能名称只能包含小写字母、数字和连字符(不能有前导或尾随连字符,不能有连续连字符)",
-		"description_length": "技能描述必须为 1-1024 个字符(收到 {{length}} 个)",
-		"no_workspace": "无法创建项目技能:未打开工作区文件夹",
-		"already_exists": "技能 \"{{name}}\" 已存在于 {{path}}",
-		"not_found": "在 {{source}}{{modeInfo}} 中未找到技能 \"{{name}}\"",
-		"missing_create_fields": "缺少必填字段:skillName、source 或 skillDescription",
-		"manager_unavailable": "技能管理器不可用",
-		"missing_delete_fields": "缺少必填字段:skillName 或 source",
-		"skill_not_found": "未找到技能 \"{{name}}\""
-	}
-}

+ 0 - 14
src/i18n/locales/zh-TW/skills.json

@@ -1,14 +0,0 @@
-{
-	"errors": {
-		"name_length": "技能名稱必須為 1-{{maxLength}} 個字元(收到 {{length}} 個)",
-		"name_format": "技能名稱只能包含小寫字母、數字和連字號(不能有前導或尾隨連字號,不能有連續連字號)",
-		"description_length": "技能描述必須為 1-1024 個字元(收到 {{length}} 個)",
-		"no_workspace": "無法建立專案技能:未開啟工作區資料夾",
-		"already_exists": "技能「{{name}}」已存在於 {{path}}",
-		"not_found": "在 {{source}}{{modeInfo}} 中找不到技能「{{name}}」",
-		"missing_create_fields": "缺少必填欄位:skillName、source 或 skillDescription",
-		"manager_unavailable": "技能管理器無法使用",
-		"missing_delete_fields": "缺少必填欄位:skillName 或 source",
-		"skill_not_found": "找不到技能「{{name}}」"
-	}
-}

+ 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",

+ 49 - 158
src/services/skills/SkillsManager.ts

@@ -1,6 +1,5 @@
 import * as fs from "fs/promises"
 import * as path from "path"
-import * as os from "os"
 import * as vscode from "vscode"
 import matter from "gray-matter"
 
@@ -9,12 +8,7 @@ import { getGlobalRooDirectory } from "../roo-config"
 import { directoryExists, fileExists } from "../roo-config"
 import { SkillMetadata, SkillContent } from "../../shared/skills"
 import { modes, getAllModes } from "../../shared/modes"
-import {
-	validateSkillName as validateSkillNameShared,
-	SkillNameValidationError,
-	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 }
@@ -123,11 +117,23 @@ export class SkillsManager {
 				return
 			}
 
-			// Validate skill name per agentskills.io spec using shared validation
-			const nameValidation = validateSkillNameShared(effectiveSkillName)
-			if (!nameValidation.valid) {
-				const errorMessage = this.getSkillNameErrorMessage(effectiveSkillName, nameValidation.error!)
-				console.error(`Skill name "${effectiveSkillName}" is invalid: ${errorMessage}`)
+			// Strict spec validation (https://agentskills.io/specification)
+			// Name constraints:
+			// - 1-64 chars
+			// - lowercase letters/numbers/hyphens only
+			// - must not start/end with hyphen
+			// - must not contain consecutive hyphens
+			if (effectiveSkillName.length < 1 || effectiveSkillName.length > 64) {
+				console.error(
+					`Skill name "${effectiveSkillName}" is invalid: name must be 1-64 characters (got ${effectiveSkillName.length})`,
+				)
+				return
+			}
+			const nameFormat = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
+			if (!nameFormat.test(effectiveSkillName)) {
+				console.error(
+					`Skill name "${effectiveSkillName}" is invalid: must be lowercase letters/numbers/hyphens only (no leading/trailing hyphen, no consecutive hyphens)`,
+				)
 				return
 			}
 
@@ -159,13 +165,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 +201,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 +241,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)
 
@@ -234,146 +265,6 @@ export class SkillsManager {
 		}
 	}
 
-	/**
-	 * Get all skills metadata (for UI display)
-	 * Returns skills from all sources without content
-	 */
-	getSkillsMetadata(): SkillMetadata[] {
-		return this.getAllSkills()
-	}
-
-	/**
-	 * Get a skill by name, source, and optionally mode
-	 */
-	getSkill(name: string, source: "global" | "project", mode?: string): SkillMetadata | undefined {
-		const skillKey = this.getSkillKey(name, source, mode)
-		return this.skills.get(skillKey)
-	}
-
-	/**
-	 * Validate skill name per agentskills.io spec using shared validation.
-	 * Converts error codes to user-friendly error messages.
-	 */
-	private validateSkillName(name: string): { valid: boolean; error?: string } {
-		const result = validateSkillNameShared(name)
-		if (!result.valid) {
-			return { valid: false, error: this.getSkillNameErrorMessage(name, result.error!) }
-		}
-		return { valid: true }
-	}
-
-	/**
-	 * Convert skill name validation error code to a user-friendly error message.
-	 */
-	private getSkillNameErrorMessage(name: string, error: SkillNameValidationError): string {
-		switch (error) {
-			case SkillNameValidationError.Empty:
-				return t("skills:errors.name_length", { maxLength: SKILL_NAME_MAX_LENGTH, length: name.length })
-			case SkillNameValidationError.TooLong:
-				return t("skills:errors.name_length", { maxLength: SKILL_NAME_MAX_LENGTH, length: name.length })
-			case SkillNameValidationError.InvalidFormat:
-				return t("skills:errors.name_format")
-		}
-	}
-
-	/**
-	 * Create a new skill
-	 * @param name - Skill name (must be valid per agentskills.io spec)
-	 * @param source - "global" or "project"
-	 * @param description - Skill description
-	 * @param mode - Optional mode restriction (creates in skills-{mode}/ directory)
-	 * @returns Path to created SKILL.md file
-	 */
-	async createSkill(name: string, source: "global" | "project", description: string, mode?: string): Promise<string> {
-		// Validate skill name
-		const validation = this.validateSkillName(name)
-		if (!validation.valid) {
-			throw new Error(validation.error)
-		}
-
-		// Validate description
-		const trimmedDescription = description.trim()
-		if (trimmedDescription.length < 1 || trimmedDescription.length > 1024) {
-			throw new Error(t("skills:errors.description_length", { length: trimmedDescription.length }))
-		}
-
-		// Determine base directory
-		let baseDir: string
-		if (source === "global") {
-			baseDir = getGlobalRooDirectory()
-		} else {
-			const provider = this.providerRef.deref()
-			if (!provider?.cwd) {
-				throw new Error(t("skills:errors.no_workspace"))
-			}
-			baseDir = path.join(provider.cwd, ".roo")
-		}
-
-		// Determine skills directory (with optional mode suffix)
-		const skillsDirName = mode ? `skills-${mode}` : "skills"
-		const skillsDir = path.join(baseDir, skillsDirName)
-		const skillDir = path.join(skillsDir, name)
-		const skillMdPath = path.join(skillDir, "SKILL.md")
-
-		// Check if skill already exists
-		if (await fileExists(skillMdPath)) {
-			throw new Error(t("skills:errors.already_exists", { name, path: skillMdPath }))
-		}
-
-		// Create the skill directory
-		await fs.mkdir(skillDir, { recursive: true })
-
-		// Generate SKILL.md content with frontmatter
-		const titleName = name
-			.split("-")
-			.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
-			.join(" ")
-
-		const skillContent = `---
-name: ${name}
-description: ${trimmedDescription}
----
-
-# ${titleName}
-
-## Instructions
-
-Add your skill instructions here.
-`
-
-		// Write the SKILL.md file
-		await fs.writeFile(skillMdPath, skillContent, "utf-8")
-
-		// Refresh skills list
-		await this.discoverSkills()
-
-		return skillMdPath
-	}
-
-	/**
-	 * Delete a skill
-	 * @param name - Skill name to delete
-	 * @param source - Where the skill is located
-	 * @param mode - Optional mode (to locate in skills-{mode}/ directory)
-	 */
-	async deleteSkill(name: string, source: "global" | "project", mode?: string): Promise<void> {
-		// Find the skill
-		const skill = this.getSkill(name, source, mode)
-		if (!skill) {
-			const modeInfo = mode ? ` (mode: ${mode})` : ""
-			throw new Error(t("skills:errors.not_found", { name, source, modeInfo }))
-		}
-
-		// Get the skill directory (parent of SKILL.md)
-		const skillDir = path.dirname(skill.path)
-
-		// Delete the entire skill directory
-		await fs.rm(skillDir, { recursive: true, force: true })
-
-		// Refresh skills list
-		await this.discoverSkills()
-	}
-
 	/**
 	 * Get all skills directories to scan, including mode-specific directories.
 	 */

+ 16 - 307
src/services/skills/__tests__/SkillsManager.spec.ts

@@ -1,29 +1,16 @@
 import * as path from "path"
 
 // Use vi.hoisted to ensure mocks are available during hoisting
-const {
-	mockStat,
-	mockReadFile,
-	mockReaddir,
-	mockHomedir,
-	mockDirectoryExists,
-	mockFileExists,
-	mockRealpath,
-	mockMkdir,
-	mockWriteFile,
-	mockRm,
-} = vi.hoisted(() => ({
-	mockStat: vi.fn(),
-	mockReadFile: vi.fn(),
-	mockReaddir: vi.fn(),
-	mockHomedir: vi.fn(),
-	mockDirectoryExists: vi.fn(),
-	mockFileExists: vi.fn(),
-	mockRealpath: vi.fn(),
-	mockMkdir: vi.fn(),
-	mockWriteFile: vi.fn(),
-	mockRm: vi.fn(),
-}))
+const { mockStat, mockReadFile, mockReaddir, mockHomedir, mockDirectoryExists, mockFileExists, mockRealpath } =
+	vi.hoisted(() => ({
+		mockStat: vi.fn(),
+		mockReadFile: vi.fn(),
+		mockReaddir: vi.fn(),
+		mockHomedir: vi.fn(),
+		mockDirectoryExists: vi.fn(),
+		mockFileExists: vi.fn(),
+		mockRealpath: vi.fn(),
+	}))
 
 // Platform-agnostic test paths
 // Use forward slashes for consistency, then normalize with path.normalize
@@ -41,17 +28,11 @@ vi.mock("fs/promises", () => ({
 		readFile: mockReadFile,
 		readdir: mockReaddir,
 		realpath: mockRealpath,
-		mkdir: mockMkdir,
-		writeFile: mockWriteFile,
-		rm: mockRm,
 	},
 	stat: mockStat,
 	readFile: mockReadFile,
 	readdir: mockReaddir,
 	realpath: mockRealpath,
-	mkdir: mockMkdir,
-	writeFile: mockWriteFile,
-	rm: mockRm,
 }))
 
 // Mock os module
@@ -82,20 +63,12 @@ vi.mock("../../roo-config", () => ({
 	fileExists: mockFileExists,
 }))
 
-// Mock i18n
-vi.mock("../../../i18n", () => ({
-	t: (key: string, params?: Record<string, any>) => {
-		const translations: Record<string, string> = {
-			"skills:errors.name_length": `Skill name must be 1-${params?.maxLength} characters (got ${params?.length})`,
-			"skills:errors.name_format":
-				"Skill name must be lowercase letters/numbers/hyphens only (no leading/trailing hyphen, no consecutive hyphens)",
-			"skills:errors.description_length": `Skill description must be 1-1024 characters (got ${params?.length})`,
-			"skills:errors.no_workspace": "Cannot create project skill: no workspace folder is open",
-			"skills:errors.already_exists": `Skill "${params?.name}" already exists at ${params?.path}`,
-			"skills:errors.not_found": `Skill "${params?.name}" not found in ${params?.source}${params?.modeInfo}`,
-		}
-		return translations[key] || key
-	},
+// 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"
@@ -862,268 +835,4 @@ description: A test skill
 			expect(skills).toHaveLength(0)
 		})
 	})
-
-	describe("getSkillsMetadata", () => {
-		it("should return all skills metadata", async () => {
-			const testSkillDir = p(globalSkillsDir, "test-skill")
-			const testSkillMd = p(testSkillDir, "SKILL.md")
-
-			mockDirectoryExists.mockImplementation(async (dir: string) => {
-				return dir === globalSkillsDir
-			})
-
-			mockRealpath.mockImplementation(async (pathArg: string) => pathArg)
-
-			mockReaddir.mockImplementation(async (dir: string) => {
-				if (dir === globalSkillsDir) {
-					return ["test-skill"]
-				}
-				return []
-			})
-
-			mockStat.mockImplementation(async (pathArg: string) => {
-				if (pathArg === testSkillDir) {
-					return { isDirectory: () => true }
-				}
-				throw new Error("Not found")
-			})
-
-			mockFileExists.mockImplementation(async (file: string) => {
-				return file === testSkillMd
-			})
-
-			mockReadFile.mockResolvedValue(`---
-name: test-skill
-description: A test skill
----
-Instructions`)
-
-			await skillsManager.discoverSkills()
-
-			const metadata = skillsManager.getSkillsMetadata()
-
-			expect(metadata).toHaveLength(1)
-			expect(metadata[0].name).toBe("test-skill")
-			expect(metadata[0].description).toBe("A test skill")
-		})
-	})
-
-	describe("getSkill", () => {
-		it("should return a skill by name, source, and mode", async () => {
-			const testSkillDir = p(globalSkillsDir, "test-skill")
-			const testSkillMd = p(testSkillDir, "SKILL.md")
-
-			mockDirectoryExists.mockImplementation(async (dir: string) => {
-				return dir === globalSkillsDir
-			})
-
-			mockRealpath.mockImplementation(async (pathArg: string) => pathArg)
-
-			mockReaddir.mockImplementation(async (dir: string) => {
-				if (dir === globalSkillsDir) {
-					return ["test-skill"]
-				}
-				return []
-			})
-
-			mockStat.mockImplementation(async (pathArg: string) => {
-				if (pathArg === testSkillDir) {
-					return { isDirectory: () => true }
-				}
-				throw new Error("Not found")
-			})
-
-			mockFileExists.mockImplementation(async (file: string) => {
-				return file === testSkillMd
-			})
-
-			mockReadFile.mockResolvedValue(`---
-name: test-skill
-description: A test skill
----
-Instructions`)
-
-			await skillsManager.discoverSkills()
-
-			const skill = skillsManager.getSkill("test-skill", "global")
-
-			expect(skill).toBeDefined()
-			expect(skill?.name).toBe("test-skill")
-			expect(skill?.source).toBe("global")
-		})
-
-		it("should return undefined for non-existent skill", async () => {
-			mockDirectoryExists.mockResolvedValue(false)
-			mockRealpath.mockImplementation(async (p: string) => p)
-			mockReaddir.mockResolvedValue([])
-
-			await skillsManager.discoverSkills()
-
-			const skill = skillsManager.getSkill("non-existent", "global")
-
-			expect(skill).toBeUndefined()
-		})
-	})
-
-	describe("createSkill", () => {
-		it("should create a new global skill", async () => {
-			// Setup: no existing skills
-			mockDirectoryExists.mockResolvedValue(false)
-			mockRealpath.mockImplementation(async (p: string) => p)
-			mockReaddir.mockResolvedValue([])
-			mockFileExists.mockResolvedValue(false)
-			mockMkdir.mockResolvedValue(undefined)
-			mockWriteFile.mockResolvedValue(undefined)
-
-			const createdPath = await skillsManager.createSkill("new-skill", "global", "A new skill description")
-
-			expect(createdPath).toBe(p(GLOBAL_ROO_DIR, "skills", "new-skill", "SKILL.md"))
-			expect(mockMkdir).toHaveBeenCalledWith(p(GLOBAL_ROO_DIR, "skills", "new-skill"), { recursive: true })
-			expect(mockWriteFile).toHaveBeenCalled()
-
-			// Verify the content written
-			const writeCall = mockWriteFile.mock.calls[0]
-			expect(writeCall[0]).toBe(p(GLOBAL_ROO_DIR, "skills", "new-skill", "SKILL.md"))
-			expect(writeCall[1]).toContain("name: new-skill")
-			expect(writeCall[1]).toContain("description: A new skill description")
-		})
-
-		it("should create a mode-specific skill", async () => {
-			mockDirectoryExists.mockResolvedValue(false)
-			mockRealpath.mockImplementation(async (p: string) => p)
-			mockReaddir.mockResolvedValue([])
-			mockFileExists.mockResolvedValue(false)
-			mockMkdir.mockResolvedValue(undefined)
-			mockWriteFile.mockResolvedValue(undefined)
-
-			const createdPath = await skillsManager.createSkill("code-skill", "global", "A code skill", "code")
-
-			expect(createdPath).toBe(p(GLOBAL_ROO_DIR, "skills-code", "code-skill", "SKILL.md"))
-		})
-
-		it("should create a project skill", async () => {
-			mockDirectoryExists.mockResolvedValue(false)
-			mockRealpath.mockImplementation(async (p: string) => p)
-			mockReaddir.mockResolvedValue([])
-			mockFileExists.mockResolvedValue(false)
-			mockMkdir.mockResolvedValue(undefined)
-			mockWriteFile.mockResolvedValue(undefined)
-
-			const createdPath = await skillsManager.createSkill("project-skill", "project", "A project skill")
-
-			expect(createdPath).toBe(p(PROJECT_DIR, ".roo", "skills", "project-skill", "SKILL.md"))
-		})
-
-		it("should throw error for invalid skill name", async () => {
-			await expect(skillsManager.createSkill("Invalid-Name", "global", "Description")).rejects.toThrow(
-				"Skill name must be lowercase letters/numbers/hyphens only",
-			)
-		})
-
-		it("should throw error for skill name that is too long", async () => {
-			const longName = "a".repeat(65)
-			await expect(skillsManager.createSkill(longName, "global", "Description")).rejects.toThrow(
-				"Skill name must be 1-64 characters",
-			)
-		})
-
-		it("should throw error for skill name starting with hyphen", async () => {
-			await expect(skillsManager.createSkill("-invalid", "global", "Description")).rejects.toThrow(
-				"Skill name must be lowercase letters/numbers/hyphens only",
-			)
-		})
-
-		it("should throw error for skill name ending with hyphen", async () => {
-			await expect(skillsManager.createSkill("invalid-", "global", "Description")).rejects.toThrow(
-				"Skill name must be lowercase letters/numbers/hyphens only",
-			)
-		})
-
-		it("should throw error for skill name with consecutive hyphens", async () => {
-			await expect(skillsManager.createSkill("invalid--name", "global", "Description")).rejects.toThrow(
-				"Skill name must be lowercase letters/numbers/hyphens only",
-			)
-		})
-
-		it("should throw error for empty description", async () => {
-			await expect(skillsManager.createSkill("valid-name", "global", "   ")).rejects.toThrow(
-				"Skill description must be 1-1024 characters",
-			)
-		})
-
-		it("should throw error for description that is too long", async () => {
-			const longDesc = "d".repeat(1025)
-			await expect(skillsManager.createSkill("valid-name", "global", longDesc)).rejects.toThrow(
-				"Skill description must be 1-1024 characters",
-			)
-		})
-
-		it("should throw error if skill already exists", async () => {
-			mockFileExists.mockResolvedValue(true)
-
-			await expect(skillsManager.createSkill("existing-skill", "global", "Description")).rejects.toThrow(
-				"already exists",
-			)
-		})
-	})
-
-	describe("deleteSkill", () => {
-		it("should delete an existing skill", async () => {
-			const testSkillDir = p(globalSkillsDir, "test-skill")
-			const testSkillMd = p(testSkillDir, "SKILL.md")
-
-			// Setup: skill exists
-			mockDirectoryExists.mockImplementation(async (dir: string) => {
-				return dir === globalSkillsDir
-			})
-
-			mockRealpath.mockImplementation(async (pathArg: string) => pathArg)
-
-			mockReaddir.mockImplementation(async (dir: string) => {
-				if (dir === globalSkillsDir) {
-					return ["test-skill"]
-				}
-				return []
-			})
-
-			mockStat.mockImplementation(async (pathArg: string) => {
-				if (pathArg === testSkillDir) {
-					return { isDirectory: () => true }
-				}
-				throw new Error("Not found")
-			})
-
-			mockFileExists.mockImplementation(async (file: string) => {
-				return file === testSkillMd
-			})
-
-			mockReadFile.mockResolvedValue(`---
-name: test-skill
-description: A test skill
----
-Instructions`)
-
-			mockRm.mockResolvedValue(undefined)
-
-			await skillsManager.discoverSkills()
-
-			// Verify skill exists
-			expect(skillsManager.getSkill("test-skill", "global")).toBeDefined()
-
-			// Delete the skill
-			await skillsManager.deleteSkill("test-skill", "global")
-
-			expect(mockRm).toHaveBeenCalledWith(testSkillDir, { recursive: true, force: true })
-		})
-
-		it("should throw error if skill does not exist", async () => {
-			mockDirectoryExists.mockResolvedValue(false)
-			mockRealpath.mockImplementation(async (p: string) => p)
-			mockReaddir.mockResolvedValue([])
-
-			await skillsManager.discoverSkills()
-
-			await expect(skillsManager.deleteSkill("non-existent", "global")).rejects.toThrow("not found")
-		})
-	})
 })

+ 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()
+	})
+})

+ 131 - 24
src/core/prompts/instructions/create-mcp-server.ts → src/services/skills/built-in-skills.ts

@@ -1,17 +1,31 @@
-import { McpHub } from "../../../services/mcp/McpHub"
-import { DiffStrategy } from "../../../shared/tools"
-
-export async function createMCPServerInstructions(
-	mcpHub: McpHub | undefined,
-	diffStrategy: DiffStrategy | undefined,
-): Promise<string> {
-	if (!diffStrategy || !mcpHub) throw new Error("Missing MCP Hub or Diff Strategy")
+/**
+ * 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
+ *
+ * Generated at: 2026-01-28T23:09:14.137Z
+ */
+
+import { SkillMetadata, SkillContent } from "../../shared/skills"
+
+interface BuiltInSkillDefinition {
+	name: string
+	description: string
+	instructions: string
+}
 
-	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\`.
+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: ${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
 
@@ -58,10 +72,10 @@ 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()}
+cd /path/to/your/mcp-servers
 npx @modelcontextprotocol/create-server weather-server
 cd weather-server
 # Install dependencies
@@ -77,7 +91,7 @@ weather-server/
 				...
 				"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')\"",
+					"build": "tsc && node -e \\"require('fs').chmodSync('build/index.js', '755')\\"",
 					...
 				}
 				...
@@ -275,7 +289,7 @@ 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=[].
 
@@ -294,7 +308,7 @@ 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 +316,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 +324,105 @@ 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.`,
+	},
+	"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)
 }

+ 304 - 0
src/services/skills/built-in/create-mcp-server/SKILL.md

@@ -0,0 +1,304 @@
+---
+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.
+---
+
+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.

+ 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

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

@@ -0,0 +1,302 @@
+#!/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
+	*
+	* Generated at: ${new Date().toISOString()}
+	*/
+
+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 }
 	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 - 254
webview-ui/src/components/settings/CreateSkillDialog.tsx

@@ -1,254 +0,0 @@
-import React, { useState, useCallback, useMemo } from "react"
-import { validateSkillName as validateSkillNameShared, SkillNameValidationError } from "@roo-code/types"
-
-import { getAllModes } from "@roo/modes"
-
-import { useAppTranslation } from "@/i18n/TranslationContext"
-import { useExtensionState } from "@/context/ExtensionStateContext"
-import {
-	Button,
-	Dialog,
-	DialogContent,
-	DialogDescription,
-	DialogFooter,
-	DialogHeader,
-	DialogTitle,
-	Select,
-	SelectContent,
-	SelectItem,
-	SelectTrigger,
-	SelectValue,
-} from "@/components/ui"
-import { vscode } from "@/utils/vscode"
-
-interface CreateSkillDialogProps {
-	open: boolean
-	onOpenChange: (open: boolean) => void
-	onSkillCreated: () => void
-	hasWorkspace: boolean
-}
-
-/**
- * Map skill name validation error codes to translation keys.
- */
-const getSkillNameErrorTranslationKey = (error: SkillNameValidationError): string => {
-	switch (error) {
-		case SkillNameValidationError.Empty:
-			return "settings:skills.validation.nameRequired"
-		case SkillNameValidationError.TooLong:
-			return "settings:skills.validation.nameTooLong"
-		case SkillNameValidationError.InvalidFormat:
-			return "settings:skills.validation.nameInvalid"
-	}
-}
-
-/**
- * Validate skill name using shared validation from @roo-code/types.
- * Returns a translation key for the error, or null if valid.
- */
-const validateSkillName = (name: string): string | null => {
-	const result = validateSkillNameShared(name)
-	if (!result.valid) {
-		return getSkillNameErrorTranslationKey(result.error!)
-	}
-	return null
-}
-
-/**
- * Validate description according to agentskills.io spec:
- * - Required field
- * - 1-1024 characters
- */
-const validateDescription = (description: string): string | null => {
-	if (!description) return "settings:skills.validation.descriptionRequired"
-	if (description.length > 1024) return "settings:skills.validation.descriptionTooLong"
-	return null
-}
-
-// Sentinel value for "Any mode" since Radix Select doesn't allow empty string values
-const MODE_ANY = "__any__"
-
-export const CreateSkillDialog: React.FC<CreateSkillDialogProps> = ({
-	open,
-	onOpenChange,
-	onSkillCreated,
-	hasWorkspace,
-}) => {
-	const { t } = useAppTranslation()
-	const { customModes } = useExtensionState()
-
-	const [name, setName] = useState("")
-	const [description, setDescription] = useState("")
-	const [source, setSource] = useState<"global" | "project">(hasWorkspace ? "project" : "global")
-	const [mode, setMode] = useState<string>(MODE_ANY)
-	const [nameError, setNameError] = useState<string | null>(null)
-	const [descriptionError, setDescriptionError] = useState<string | null>(null)
-
-	// Get available modes for the dropdown (built-in + custom modes)
-	const availableModes = useMemo(() => {
-		return getAllModes(customModes).map((m) => ({ slug: m.slug, name: m.name }))
-	}, [customModes])
-
-	const resetForm = useCallback(() => {
-		setName("")
-		setDescription("")
-		setSource(hasWorkspace ? "project" : "global")
-		setMode(MODE_ANY)
-		setNameError(null)
-		setDescriptionError(null)
-	}, [hasWorkspace])
-
-	const handleClose = useCallback(() => {
-		resetForm()
-		onOpenChange(false)
-	}, [resetForm, onOpenChange])
-
-	const handleNameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
-		const value = e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, "")
-		setName(value)
-		setNameError(null)
-	}, [])
-
-	const handleDescriptionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
-		setDescription(e.target.value)
-		setDescriptionError(null)
-	}, [])
-
-	const handleCreate = useCallback(() => {
-		// Validate fields
-		const nameValidationError = validateSkillName(name)
-		const descValidationError = validateDescription(description)
-
-		if (nameValidationError) {
-			setNameError(nameValidationError)
-			return
-		}
-
-		if (descValidationError) {
-			setDescriptionError(descValidationError)
-			return
-		}
-
-		// Send message to create skill
-		// Convert MODE_ANY sentinel value to undefined for the backend
-		vscode.postMessage({
-			type: "createSkill",
-			skillName: name,
-			source,
-			skillDescription: description,
-			skillMode: mode === MODE_ANY ? undefined : mode,
-		})
-
-		// Close dialog and notify parent
-		handleClose()
-		onSkillCreated()
-	}, [name, description, source, mode, handleClose, onSkillCreated])
-
-	return (
-		<Dialog open={open} onOpenChange={onOpenChange}>
-			<DialogContent className="sm:max-w-md">
-				<DialogHeader>
-					<DialogTitle>{t("settings:skills.createDialog.title")}</DialogTitle>
-					<DialogDescription>{t("settings:skills.createDialog.description")}</DialogDescription>
-				</DialogHeader>
-
-				<div className="flex flex-col gap-4 py-4">
-					{/* Name Input */}
-					<div className="flex flex-col gap-1.5">
-						<label htmlFor="skill-name" className="text-sm font-medium text-vscode-foreground">
-							{t("settings:skills.createDialog.nameLabel")} *
-						</label>
-						<input
-							id="skill-name"
-							type="text"
-							value={name}
-							onChange={handleNameChange}
-							placeholder={t("settings:skills.createDialog.namePlaceholder")}
-							maxLength={64}
-							className="w-full bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded px-3 py-2 text-sm focus:outline-none focus:border-vscode-focusBorder"
-						/>
-						<span className="text-xs text-vscode-descriptionForeground">
-							{t("settings:skills.createDialog.nameHint")}
-						</span>
-						{nameError && <span className="text-xs text-vscode-errorForeground">{t(nameError)}</span>}
-					</div>
-
-					{/* Description Input */}
-					<div className="flex flex-col gap-1.5">
-						<label htmlFor="skill-description" className="text-sm font-medium text-vscode-foreground">
-							{t("settings:skills.createDialog.descriptionLabel")} *
-						</label>
-						<textarea
-							id="skill-description"
-							value={description}
-							onChange={handleDescriptionChange}
-							placeholder={t("settings:skills.createDialog.descriptionPlaceholder")}
-							maxLength={1024}
-							rows={3}
-							className="w-full bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded px-3 py-2 text-sm focus:outline-none focus:border-vscode-focusBorder resize-none"
-						/>
-						<span className="text-xs text-vscode-descriptionForeground">
-							{t("settings:skills.createDialog.descriptionHint")}
-						</span>
-						{descriptionError && (
-							<span className="text-xs text-vscode-errorForeground">{t(descriptionError)}</span>
-						)}
-					</div>
-
-					{/* Source Selection */}
-					<div className="flex flex-col gap-1.5">
-						<label className="text-sm font-medium text-vscode-foreground">
-							{t("settings:skills.createDialog.sourceLabel")}
-						</label>
-						<Select value={source} onValueChange={(value) => setSource(value as "global" | "project")}>
-							<SelectTrigger className="w-full">
-								<SelectValue />
-							</SelectTrigger>
-							<SelectContent>
-								<SelectItem value="global">{t("settings:skills.source.global")}</SelectItem>
-								{hasWorkspace && (
-									<SelectItem value="project">{t("settings:skills.source.project")}</SelectItem>
-								)}
-							</SelectContent>
-						</Select>
-						<span className="text-xs text-vscode-descriptionForeground">
-							{t("settings:skills.createDialog.sourceHint")}
-						</span>
-					</div>
-
-					{/* Mode Selection (Optional) */}
-					<div className="flex flex-col gap-1.5">
-						<label className="text-sm font-medium text-vscode-foreground">
-							{t("settings:skills.createDialog.modeLabel")}
-						</label>
-						<Select value={mode} onValueChange={setMode}>
-							<SelectTrigger className="w-full">
-								<SelectValue placeholder={t("settings:skills.createDialog.modePlaceholder")} />
-							</SelectTrigger>
-							<SelectContent>
-								<SelectItem value={MODE_ANY}>{t("settings:skills.createDialog.modeAny")}</SelectItem>
-								{availableModes.map((m) => (
-									<SelectItem key={m.slug} value={m.slug}>
-										{m.name}
-									</SelectItem>
-								))}
-							</SelectContent>
-						</Select>
-						<span className="text-xs text-vscode-descriptionForeground">
-							{t("settings:skills.createDialog.modeHint")}
-						</span>
-					</div>
-				</div>
-
-				<DialogFooter>
-					<Button variant="secondary" onClick={handleClose}>
-						{t("settings:skills.createDialog.cancel")}
-					</Button>
-					<Button variant="primary" onClick={handleCreate} disabled={!name || !description}>
-						{t("settings:skills.createDialog.create")}
-					</Button>
-				</DialogFooter>
-			</DialogContent>
-		</Dialog>
-	)
-}

+ 0 - 7
webview-ui/src/components/settings/SettingsView.tsx

@@ -29,7 +29,6 @@ import {
 	Users2,
 	ArrowLeft,
 	GitCommitVertical,
-	Zap,
 } from "lucide-react"
 
 import {
@@ -78,7 +77,6 @@ import { About } from "./About"
 import { Section } from "./Section"
 import PromptsSettings from "./PromptsSettings"
 import { SlashCommandsSettings } from "./SlashCommandsSettings"
-import { SkillsSettings } from "./SkillsSettings"
 import { UISettings } from "./UISettings"
 import ModesView from "../modes/ModesView"
 import McpView from "../mcp/McpView"
@@ -101,7 +99,6 @@ export const sectionNames = [
 	"providers",
 	"autoApprove",
 	"slashCommands",
-	"skills",
 	"browser",
 	"checkpoints",
 	"notifications",
@@ -519,7 +516,6 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
 			{ id: "mcp", icon: Server },
 			{ id: "autoApprove", icon: CheckCheck },
 			{ id: "slashCommands", icon: SquareSlash },
-			{ id: "skills", icon: Zap },
 			{ id: "browser", icon: SquareMousePointer },
 			{ id: "checkpoints", icon: GitCommitVertical },
 			{ id: "notifications", icon: Bell },
@@ -810,9 +806,6 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
 						{/* Slash Commands Section */}
 						{renderTab === "slashCommands" && <SlashCommandsSettings />}
 
-						{/* Skills Section */}
-						{renderTab === "skills" && <SkillsSettings />}
-
 						{/* Browser Section */}
 						{renderTab === "browser" && (
 							<BrowserSettings

+ 0 - 61
webview-ui/src/components/settings/SkillItem.tsx

@@ -1,61 +0,0 @@
-import React from "react"
-import { Edit, Trash2 } from "lucide-react"
-
-import type { SkillMetadata } from "@roo-code/types"
-
-import { useAppTranslation } from "@/i18n/TranslationContext"
-import { Button, StandardTooltip } from "@/components/ui"
-
-interface SkillItemProps {
-	skill: SkillMetadata
-	onEdit: () => void
-	onDelete: () => void
-}
-
-export const SkillItem: React.FC<SkillItemProps> = ({ skill, onEdit, onDelete }) => {
-	const { t } = useAppTranslation()
-
-	return (
-		<div className="px-4 py-2 text-sm flex items-center group hover:bg-vscode-list-hoverBackground">
-			{/* Skill name and description */}
-			<div className="flex-1 min-w-0 cursor-pointer" onClick={onEdit}>
-				<div className="flex items-center gap-2">
-					<span className="truncate text-vscode-foreground">{skill.name}</span>
-					{skill.mode && (
-						<span className="px-1.5 py-0.5 text-xs rounded bg-vscode-badge-background text-vscode-badge-foreground shrink-0">
-							{skill.mode}
-						</span>
-					)}
-				</div>
-				{skill.description && (
-					<div className="text-xs text-vscode-descriptionForeground truncate mt-0.5">{skill.description}</div>
-				)}
-			</div>
-
-			{/* Action buttons */}
-			<div className="flex items-center gap-2 ml-2">
-				<StandardTooltip content={t("settings:skills.editSkill")}>
-					<Button
-						variant="ghost"
-						size="icon"
-						tabIndex={-1}
-						onClick={onEdit}
-						className="size-6 flex items-center justify-center opacity-60 hover:opacity-100">
-						<Edit className="w-4 h-4" />
-					</Button>
-				</StandardTooltip>
-
-				<StandardTooltip content={t("settings:skills.deleteSkill")}>
-					<Button
-						variant="ghost"
-						size="icon"
-						tabIndex={-1}
-						onClick={onDelete}
-						className="size-6 flex items-center justify-center opacity-60 hover:opacity-100 hover:text-red-400">
-						<Trash2 className="w-4 h-4" />
-					</Button>
-				</StandardTooltip>
-			</div>
-		</div>
-	)
-}

+ 0 - 228
webview-ui/src/components/settings/SkillsSettings.tsx

@@ -1,228 +0,0 @@
-import React, { useState, useEffect, useMemo, useCallback } from "react"
-import { Plus, Globe, Folder } from "lucide-react"
-import { Trans } from "react-i18next"
-
-import type { SkillMetadata } from "@roo-code/types"
-
-import { useAppTranslation } from "@/i18n/TranslationContext"
-import { useExtensionState } from "@/context/ExtensionStateContext"
-import {
-	AlertDialog,
-	AlertDialogAction,
-	AlertDialogCancel,
-	AlertDialogContent,
-	AlertDialogDescription,
-	AlertDialogFooter,
-	AlertDialogHeader,
-	AlertDialogTitle,
-	Button,
-} from "@/components/ui"
-import { vscode } from "@/utils/vscode"
-import { buildDocLink } from "@/utils/docLinks"
-
-import { SectionHeader } from "./SectionHeader"
-import { Section } from "./Section"
-import { SearchableSetting } from "./SearchableSetting"
-import { SkillItem } from "./SkillItem"
-import { CreateSkillDialog } from "./CreateSkillDialog"
-import type { SectionName } from "./SettingsView"
-
-export const SkillsSettings: React.FC = () => {
-	const { t } = useAppTranslation()
-	const { cwd, skills: rawSkills } = useExtensionState()
-	const skills = useMemo(() => rawSkills ?? [], [rawSkills])
-
-	const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
-	const [skillToDelete, setSkillToDelete] = useState<SkillMetadata | null>(null)
-	const [createDialogOpen, setCreateDialogOpen] = useState(false)
-
-	// Check if we're in a workspace/project
-	const hasWorkspace = Boolean(cwd)
-
-	const handleRefresh = useCallback(() => {
-		vscode.postMessage({ type: "requestSkills" })
-	}, [])
-
-	// Request skills when component mounts
-	useEffect(() => {
-		handleRefresh()
-	}, [handleRefresh])
-
-	const handleDeleteClick = useCallback((skill: SkillMetadata) => {
-		setSkillToDelete(skill)
-		setDeleteDialogOpen(true)
-	}, [])
-
-	const handleDeleteConfirm = useCallback(() => {
-		if (skillToDelete) {
-			vscode.postMessage({
-				type: "deleteSkill",
-				skillName: skillToDelete.name,
-				source: skillToDelete.source,
-				skillMode: skillToDelete.mode,
-			})
-			setDeleteDialogOpen(false)
-			setSkillToDelete(null)
-		}
-	}, [skillToDelete])
-
-	const handleDeleteCancel = useCallback(() => {
-		setDeleteDialogOpen(false)
-		setSkillToDelete(null)
-	}, [])
-
-	const handleEditClick = useCallback((skill: SkillMetadata) => {
-		vscode.postMessage({
-			type: "openSkillFile",
-			skillName: skill.name,
-			source: skill.source,
-			skillMode: skill.mode,
-		})
-	}, [])
-
-	// No-op callback - the backend sends updated skills list via ExtensionStateContext
-	const handleSkillCreated = useCallback(() => {}, [])
-
-	// Group skills by source
-	const projectSkills = useMemo(() => skills.filter((skill) => skill.source === "project"), [skills])
-
-	const globalSkills = useMemo(() => skills.filter((skill) => skill.source === "global"), [skills])
-
-	return (
-		<div>
-			<SectionHeader>{t("settings:sections.skills")}</SectionHeader>
-
-			<Section>
-				{/* Description section */}
-				<SearchableSetting
-					settingId="skills-description"
-					section={"skills" as SectionName}
-					label={t("settings:sections.skills")}
-					className="mb-4">
-					<p className="text-sm text-vscode-descriptionForeground mb-2">
-						<Trans
-							i18nKey="settings:skills.description"
-							components={{
-								DocsLink: (
-									<a
-										href={buildDocLink("features/skills", "skills_settings")}
-										target="_blank"
-										rel="noopener noreferrer"
-										className="text-vscode-textLink-foreground hover:underline">
-										Docs
-									</a>
-								),
-							}}
-						/>
-					</p>
-				</SearchableSetting>
-
-				{/* Project Skills Section - Only show if in a workspace */}
-				{hasWorkspace && (
-					<SearchableSetting
-						settingId="skills-project"
-						section={"skills" as SectionName}
-						label={t("settings:skills.projectSkills")}
-						className="mb-6">
-						<div className="flex items-center justify-between mb-2">
-							<div className="flex items-center gap-1.5">
-								<Folder className="w-3 h-3" />
-								<h4 className="text-sm font-medium m-0">{t("settings:skills.projectSkills")}</h4>
-							</div>
-							<Button
-								variant="ghost"
-								size="sm"
-								onClick={() => setCreateDialogOpen(true)}
-								className="h-6 px-2 text-xs opacity-60 hover:opacity-100">
-								<Plus className="w-3 h-3 mr-1" />
-								{t("settings:skills.addSkill")}
-							</Button>
-						</div>
-						<div className="border border-vscode-panel-border rounded-md">
-							{projectSkills.length > 0 ? (
-								projectSkills.map((skill) => (
-									<SkillItem
-										key={`project-${skill.name}-${skill.mode || "any"}`}
-										skill={skill}
-										onEdit={() => handleEditClick(skill)}
-										onDelete={() => handleDeleteClick(skill)}
-									/>
-								))
-							) : (
-								<div className="px-4 py-6 text-sm text-vscode-descriptionForeground text-center">
-									{t("settings:skills.noProjectSkills")}
-								</div>
-							)}
-						</div>
-					</SearchableSetting>
-				)}
-
-				{/* Global Skills Section */}
-				<SearchableSetting
-					settingId="skills-global"
-					section={"skills" as SectionName}
-					label={t("settings:skills.globalSkills")}
-					className="mb-6">
-					<div className="flex items-center justify-between mb-2">
-						<div className="flex items-center gap-1.5">
-							<Globe className="w-3 h-3" />
-							<h4 className="text-sm font-medium m-0">{t("settings:skills.globalSkills")}</h4>
-						</div>
-						<Button
-							variant="ghost"
-							size="sm"
-							onClick={() => setCreateDialogOpen(true)}
-							className="h-6 px-2 text-xs opacity-60 hover:opacity-100">
-							<Plus className="w-3 h-3 mr-1" />
-							{t("settings:skills.addSkill")}
-						</Button>
-					</div>
-					<div className="border border-vscode-panel-border rounded-md">
-						{globalSkills.length > 0 ? (
-							globalSkills.map((skill) => (
-								<SkillItem
-									key={`global-${skill.name}-${skill.mode || "any"}`}
-									skill={skill}
-									onEdit={() => handleEditClick(skill)}
-									onDelete={() => handleDeleteClick(skill)}
-								/>
-							))
-						) : (
-							<div className="px-4 py-6 text-sm text-vscode-descriptionForeground text-center">
-								{t("settings:skills.noGlobalSkills")}
-							</div>
-						)}
-					</div>
-				</SearchableSetting>
-			</Section>
-
-			{/* Delete Confirmation Dialog */}
-			<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
-				<AlertDialogContent>
-					<AlertDialogHeader>
-						<AlertDialogTitle>{t("settings:skills.deleteDialog.title")}</AlertDialogTitle>
-						<AlertDialogDescription>
-							{t("settings:skills.deleteDialog.description", { name: skillToDelete?.name })}
-						</AlertDialogDescription>
-					</AlertDialogHeader>
-					<AlertDialogFooter>
-						<AlertDialogCancel onClick={handleDeleteCancel}>
-							{t("settings:skills.deleteDialog.cancel")}
-						</AlertDialogCancel>
-						<AlertDialogAction onClick={handleDeleteConfirm}>
-							{t("settings:skills.deleteDialog.confirm")}
-						</AlertDialogAction>
-					</AlertDialogFooter>
-				</AlertDialogContent>
-			</AlertDialog>
-
-			{/* Create Skill Dialog */}
-			<CreateSkillDialog
-				open={createDialogOpen}
-				onOpenChange={setCreateDialogOpen}
-				onSkillCreated={handleSkillCreated}
-				hasWorkspace={hasWorkspace}
-			/>
-		</div>
-	)
-}

+ 0 - 404
webview-ui/src/components/settings/__tests__/CreateSkillDialog.spec.tsx

@@ -1,404 +0,0 @@
-import { render, screen, fireEvent, waitFor } from "@/utils/test-utils"
-
-import { vscode } from "@/utils/vscode"
-
-import { CreateSkillDialog } from "../CreateSkillDialog"
-
-// Mock vscode
-vi.mock("@/utils/vscode", () => ({
-	vscode: {
-		postMessage: vi.fn(),
-	},
-}))
-
-// Mock the translation hook
-vi.mock("@/i18n/TranslationContext", () => ({
-	useAppTranslation: () => ({
-		t: (key: string) => key,
-	}),
-}))
-
-// Create a variable to hold the mock state
-let mockExtensionState: any = {}
-
-// Mock the useExtensionState hook
-vi.mock("@/context/ExtensionStateContext", () => ({
-	ExtensionStateContextProvider: ({ children }: any) => children,
-	useExtensionState: () => mockExtensionState,
-}))
-
-// Mock UI components
-vi.mock("@/components/ui", () => ({
-	Button: ({ children, onClick, disabled, variant }: any) => (
-		<button onClick={onClick} disabled={disabled} data-variant={variant} data-testid="button">
-			{children}
-		</button>
-	),
-	Dialog: ({ children, open }: any) => (
-		<div data-testid="dialog" data-open={open}>
-			{open && children}
-		</div>
-	),
-	DialogContent: ({ children }: any) => <div data-testid="dialog-content">{children}</div>,
-	DialogHeader: ({ children }: any) => <div data-testid="dialog-header">{children}</div>,
-	DialogTitle: ({ children }: any) => <div data-testid="dialog-title">{children}</div>,
-	DialogDescription: ({ children }: any) => <div data-testid="dialog-description">{children}</div>,
-	DialogFooter: ({ children }: any) => <div data-testid="dialog-footer">{children}</div>,
-	Select: ({ children, value, onValueChange }: any) => (
-		<div data-testid="select" data-value={value}>
-			{children}
-			<input
-				type="hidden"
-				data-testid="select-input"
-				value={value}
-				onChange={(e) => onValueChange(e.target.value)}
-			/>
-		</div>
-	),
-	SelectTrigger: ({ children }: any) => <div data-testid="select-trigger">{children}</div>,
-	SelectValue: ({ placeholder }: any) => <span data-testid="select-value">{placeholder}</span>,
-	SelectContent: ({ children }: any) => <div data-testid="select-content">{children}</div>,
-	SelectItem: ({ children, value }: any) => (
-		<div data-testid={`select-item-${value}`} data-value={value}>
-			{children}
-		</div>
-	),
-}))
-
-describe("CreateSkillDialog", () => {
-	const mockOnOpenChange = vi.fn()
-	const mockOnSkillCreated = vi.fn()
-
-	beforeEach(() => {
-		vi.clearAllMocks()
-		mockExtensionState = {
-			customModes: [{ slug: "custom-mode", name: "Custom Mode" }],
-		}
-	})
-
-	it("renders dialog when open is true", () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		expect(screen.getByTestId("dialog")).toHaveAttribute("data-open", "true")
-		expect(screen.getByTestId("dialog-title")).toBeInTheDocument()
-	})
-
-	it("does not render dialog content when open is false", () => {
-		render(
-			<CreateSkillDialog
-				open={false}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		expect(screen.getByTestId("dialog")).toHaveAttribute("data-open", "false")
-		expect(screen.queryByTestId("dialog-title")).not.toBeInTheDocument()
-	})
-
-	it("renders name input field", () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		const nameInput = screen.getByPlaceholderText("settings:skills.createDialog.namePlaceholder")
-		expect(nameInput).toBeInTheDocument()
-	})
-
-	it("renders description textarea", () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		const descInput = screen.getByPlaceholderText("settings:skills.createDialog.descriptionPlaceholder")
-		expect(descInput).toBeInTheDocument()
-	})
-
-	it("transforms name input to lowercase with only allowed characters", () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		const nameInput = screen.getByPlaceholderText(
-			"settings:skills.createDialog.namePlaceholder",
-		) as HTMLInputElement
-		fireEvent.change(nameInput, { target: { value: "Test-Skill_123!" } })
-
-		// Should be transformed to lowercase and remove invalid characters
-		expect(nameInput.value).toBe("test-skill123")
-	})
-
-	it("disables create button when name is empty", () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		const buttons = screen.getAllByTestId("button")
-		const createButton = buttons.find((btn) => btn.getAttribute("data-variant") === "primary")
-
-		expect(createButton).toBeDisabled()
-	})
-
-	it("disables create button when description is empty", () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		const nameInput = screen.getByPlaceholderText("settings:skills.createDialog.namePlaceholder")
-		fireEvent.change(nameInput, { target: { value: "valid-name" } })
-
-		const buttons = screen.getAllByTestId("button")
-		const createButton = buttons.find((btn) => btn.getAttribute("data-variant") === "primary")
-
-		expect(createButton).toBeDisabled()
-	})
-
-	it("enables create button when both name and description are provided", () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		const nameInput = screen.getByPlaceholderText("settings:skills.createDialog.namePlaceholder")
-		const descInput = screen.getByPlaceholderText("settings:skills.createDialog.descriptionPlaceholder")
-
-		fireEvent.change(nameInput, { target: { value: "valid-name" } })
-		fireEvent.change(descInput, { target: { value: "Valid description" } })
-
-		const buttons = screen.getAllByTestId("button")
-		const createButton = buttons.find((btn) => btn.getAttribute("data-variant") === "primary")
-
-		expect(createButton).not.toBeDisabled()
-	})
-
-	it("calls vscode.postMessage with correct data when creating skill", async () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		const nameInput = screen.getByPlaceholderText("settings:skills.createDialog.namePlaceholder")
-		const descInput = screen.getByPlaceholderText("settings:skills.createDialog.descriptionPlaceholder")
-
-		fireEvent.change(nameInput, { target: { value: "my-skill" } })
-		fireEvent.change(descInput, { target: { value: "My skill description" } })
-
-		const buttons = screen.getAllByTestId("button")
-		const createButton = buttons.find((btn) => btn.getAttribute("data-variant") === "primary")
-
-		fireEvent.click(createButton!)
-
-		await waitFor(() => {
-			expect(vscode.postMessage).toHaveBeenCalledWith({
-				type: "createSkill",
-				skillName: "my-skill",
-				source: "project",
-				skillDescription: "My skill description",
-				skillMode: undefined,
-			})
-		})
-	})
-
-	it("calls onSkillCreated after creating skill", async () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		const nameInput = screen.getByPlaceholderText("settings:skills.createDialog.namePlaceholder")
-		const descInput = screen.getByPlaceholderText("settings:skills.createDialog.descriptionPlaceholder")
-
-		fireEvent.change(nameInput, { target: { value: "my-skill" } })
-		fireEvent.change(descInput, { target: { value: "My skill description" } })
-
-		const buttons = screen.getAllByTestId("button")
-		const createButton = buttons.find((btn) => btn.getAttribute("data-variant") === "primary")
-
-		fireEvent.click(createButton!)
-
-		await waitFor(() => {
-			expect(mockOnSkillCreated).toHaveBeenCalled()
-		})
-	})
-
-	it("closes dialog after creating skill", async () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		const nameInput = screen.getByPlaceholderText("settings:skills.createDialog.namePlaceholder")
-		const descInput = screen.getByPlaceholderText("settings:skills.createDialog.descriptionPlaceholder")
-
-		fireEvent.change(nameInput, { target: { value: "my-skill" } })
-		fireEvent.change(descInput, { target: { value: "My skill description" } })
-
-		const buttons = screen.getAllByTestId("button")
-		const createButton = buttons.find((btn) => btn.getAttribute("data-variant") === "primary")
-
-		fireEvent.click(createButton!)
-
-		await waitFor(() => {
-			expect(mockOnOpenChange).toHaveBeenCalledWith(false)
-		})
-	})
-
-	it("closes dialog when cancel button is clicked", () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		const buttons = screen.getAllByTestId("button")
-		const cancelButton = buttons.find((btn) => btn.getAttribute("data-variant") === "secondary")
-
-		fireEvent.click(cancelButton!)
-
-		expect(mockOnOpenChange).toHaveBeenCalledWith(false)
-	})
-
-	it("defaults to project source when hasWorkspace is true", () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		const select = screen.getAllByTestId("select")[0]
-		expect(select).toHaveAttribute("data-value", "project")
-	})
-
-	it("defaults to global source when hasWorkspace is false", () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={false}
-			/>,
-		)
-
-		const select = screen.getAllByTestId("select")[0]
-		expect(select).toHaveAttribute("data-value", "global")
-	})
-
-	it("renders source selection dropdown", () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		expect(screen.getByTestId("select-item-global")).toBeInTheDocument()
-		expect(screen.getByTestId("select-item-project")).toBeInTheDocument()
-	})
-
-	it("renders mode selection dropdown", () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		// Should have "Any mode" option (uses __any__ sentinel value)
-		expect(screen.getByTestId("select-item-__any__")).toBeInTheDocument()
-		// Should have built-in modes
-		expect(screen.getByTestId("select-item-code")).toBeInTheDocument()
-		expect(screen.getByTestId("select-item-architect")).toBeInTheDocument()
-		// Should have custom modes from state
-		expect(screen.getByTestId("select-item-custom-mode")).toBeInTheDocument()
-	})
-
-	it("clears form after successful skill creation", async () => {
-		render(
-			<CreateSkillDialog
-				open={true}
-				onOpenChange={mockOnOpenChange}
-				onSkillCreated={mockOnSkillCreated}
-				hasWorkspace={true}
-			/>,
-		)
-
-		const nameInput = screen.getByPlaceholderText(
-			"settings:skills.createDialog.namePlaceholder",
-		) as HTMLInputElement
-		const descInput = screen.getByPlaceholderText(
-			"settings:skills.createDialog.descriptionPlaceholder",
-		) as HTMLTextAreaElement
-
-		fireEvent.change(nameInput, { target: { value: "test-skill" } })
-		fireEvent.change(descInput, { target: { value: "Test description" } })
-
-		const buttons = screen.getAllByTestId("button")
-		const createButton = buttons.find((btn) => btn.getAttribute("data-variant") === "primary")
-
-		fireEvent.click(createButton!)
-
-		// After clicking create, the dialog should close via onOpenChange
-		await waitFor(() => {
-			expect(mockOnOpenChange).toHaveBeenCalledWith(false)
-		})
-	})
-})

+ 0 - 25
webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx

@@ -54,31 +54,6 @@ vi.mock("@src/components/ui", () => ({
 	TooltipProvider: ({ children }: any) => <>{children}</>,
 	TooltipTrigger: ({ children }: any) => <>{children}</>,
 	TooltipContent: ({ children }: any) => <div>{children}</div>,
-	// Add Dialog components (used by CreateSkillDialog)
-	Dialog: ({ children, open }: any) => (open ? <div data-testid="dialog">{children}</div> : null),
-	DialogContent: ({ children, className }: any) => (
-		<div data-testid="dialog-content" className={className}>
-			{children}
-		</div>
-	),
-	DialogHeader: ({ children }: any) => <div data-testid="dialog-header">{children}</div>,
-	DialogTitle: ({ children }: any) => <div data-testid="dialog-title">{children}</div>,
-	DialogDescription: ({ children }: any) => <div data-testid="dialog-description">{children}</div>,
-	DialogFooter: ({ children }: any) => <div data-testid="dialog-footer">{children}</div>,
-	// Add Select components (used by CreateSkillDialog)
-	Select: ({ children, value, onValueChange: _onValueChange }: any) => (
-		<div data-testid="select" data-value={value}>
-			{children}
-		</div>
-	),
-	SelectContent: ({ children }: any) => <div data-testid="select-content">{children}</div>,
-	SelectItem: ({ children, value }: any) => (
-		<div data-testid={`select-item-${value}`} data-value={value}>
-			{children}
-		</div>
-	),
-	SelectTrigger: ({ children }: any) => <div data-testid="select-trigger">{children}</div>,
-	SelectValue: ({ placeholder }: any) => <div data-testid="select-value">{placeholder}</div>,
 }))
 
 // Mock ModesView and McpView since they're rendered during indexing

+ 0 - 11
webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx

@@ -212,17 +212,6 @@ vi.mock("@/components/ui", () => ({
 	CollapsibleContent: ({ children, className }: any) => (
 		<div className={`collapsible-content-mock ${className || ""}`}>{children}</div>
 	),
-	// Add Dialog components (used by CreateSkillDialog)
-	Dialog: ({ children, open }: any) => (open ? <div data-testid="dialog">{children}</div> : null),
-	DialogContent: ({ children, className }: any) => (
-		<div data-testid="dialog-content" className={className}>
-			{children}
-		</div>
-	),
-	DialogHeader: ({ children }: any) => <div data-testid="dialog-header">{children}</div>,
-	DialogTitle: ({ children }: any) => <div data-testid="dialog-title">{children}</div>,
-	DialogDescription: ({ children }: any) => <div data-testid="dialog-description">{children}</div>,
-	DialogFooter: ({ children }: any) => <div data-testid="dialog-footer">{children}</div>,
 }))
 
 // Mock window.postMessage to trigger state hydration

+ 0 - 25
webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx

@@ -55,31 +55,6 @@ vi.mock("@src/components/ui", () => ({
 	Popover: ({ children }: any) => <>{children}</>,
 	PopoverTrigger: ({ children }: any) => <>{children}</>,
 	PopoverContent: ({ children }: any) => <div>{children}</div>,
-	// Add Dialog components (used by CreateSkillDialog)
-	Dialog: ({ children, open }: any) => (open ? <div data-testid="dialog">{children}</div> : null),
-	DialogContent: ({ children, className }: any) => (
-		<div data-testid="dialog-content" className={className}>
-			{children}
-		</div>
-	),
-	DialogHeader: ({ children }: any) => <div data-testid="dialog-header">{children}</div>,
-	DialogTitle: ({ children }: any) => <div data-testid="dialog-title">{children}</div>,
-	DialogDescription: ({ children }: any) => <div data-testid="dialog-description">{children}</div>,
-	DialogFooter: ({ children }: any) => <div data-testid="dialog-footer">{children}</div>,
-	// Add Select components (used by CreateSkillDialog)
-	Select: ({ children, value, onValueChange: _onValueChange }: any) => (
-		<div data-testid="select" data-value={value}>
-			{children}
-		</div>
-	),
-	SelectContent: ({ children }: any) => <div data-testid="select-content">{children}</div>,
-	SelectItem: ({ children, value }: any) => (
-		<div data-testid={`select-item-${value}`} data-value={value}>
-			{children}
-		</div>
-	),
-	SelectTrigger: ({ children }: any) => <div data-testid="select-trigger">{children}</div>,
-	SelectValue: ({ placeholder }: any) => <div data-testid="select-value">{placeholder}</div>,
 }))
 
 // Mock ModesView and McpView since they're rendered during indexing

+ 0 - 141
webview-ui/src/components/settings/__tests__/SkillItem.spec.tsx

@@ -1,141 +0,0 @@
-import { render, screen, fireEvent } from "@/utils/test-utils"
-
-import type { SkillMetadata } from "@roo-code/types"
-
-import { SkillItem } from "../SkillItem"
-
-// Mock vscode
-vi.mock("@/utils/vscode", () => ({
-	vscode: {
-		postMessage: vi.fn(),
-	},
-}))
-
-// Mock the translation hook
-vi.mock("@/i18n/TranslationContext", () => ({
-	useAppTranslation: () => ({
-		t: (key: string) => key,
-	}),
-}))
-
-// Mock UI components
-vi.mock("@/components/ui", () => ({
-	Button: ({ children, onClick, className, tabIndex }: any) => (
-		<button onClick={onClick} className={className} tabIndex={tabIndex} data-testid="button">
-			{children}
-		</button>
-	),
-	StandardTooltip: ({ children, content }: any) => (
-		<div title={content} data-testid="tooltip">
-			{children}
-		</div>
-	),
-}))
-
-const mockSkill: SkillMetadata = {
-	name: "test-skill",
-	description: "A test skill description",
-	path: "/path/to/skill/SKILL.md",
-	source: "project",
-}
-
-const mockSkillWithMode: SkillMetadata = {
-	name: "mode-specific-skill",
-	description: "A mode-specific skill",
-	path: "/path/to/skill/SKILL.md",
-	source: "global",
-	mode: "architect",
-}
-
-describe("SkillItem", () => {
-	const mockOnEdit = vi.fn()
-	const mockOnDelete = vi.fn()
-
-	beforeEach(() => {
-		vi.clearAllMocks()
-	})
-
-	it("renders skill name", () => {
-		render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
-
-		expect(screen.getByText("test-skill")).toBeInTheDocument()
-	})
-
-	it("renders skill description", () => {
-		render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
-
-		expect(screen.getByText("A test skill description")).toBeInTheDocument()
-	})
-
-	it("renders mode badge when skill has mode", () => {
-		render(<SkillItem skill={mockSkillWithMode} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
-
-		expect(screen.getByText("architect")).toBeInTheDocument()
-	})
-
-	it("does not render mode badge when skill has no mode", () => {
-		render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
-
-		// Should not have any mode badge
-		const container = screen.getByText("test-skill").parentElement
-		expect(container?.querySelector(".bg-vscode-badge-background")).toBeNull()
-	})
-
-	it("calls onEdit when edit button is clicked", () => {
-		render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
-
-		const buttons = screen.getAllByTestId("button")
-		// First button is edit
-		fireEvent.click(buttons[0])
-
-		expect(mockOnEdit).toHaveBeenCalledTimes(1)
-	})
-
-	it("calls onDelete when delete button is clicked", () => {
-		render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
-
-		const buttons = screen.getAllByTestId("button")
-		// Second button is delete
-		fireEvent.click(buttons[1])
-
-		expect(mockOnDelete).toHaveBeenCalledTimes(1)
-	})
-
-	it("calls onEdit when clicking on skill name area", () => {
-		render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
-
-		const nameElement = screen.getByText("test-skill")
-		fireEvent.click(nameElement)
-
-		expect(mockOnEdit).toHaveBeenCalledTimes(1)
-	})
-
-	it("renders without description when not provided", () => {
-		const skillWithoutDescription: SkillMetadata = {
-			name: "no-desc-skill",
-			description: "",
-			path: "/path/to/skill/SKILL.md",
-			source: "project",
-		}
-
-		render(<SkillItem skill={skillWithoutDescription} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
-
-		expect(screen.getByText("no-desc-skill")).toBeInTheDocument()
-		// Description div should not be rendered when empty
-		expect(screen.queryByText("A test skill description")).not.toBeInTheDocument()
-	})
-
-	it("renders with proper styling classes", () => {
-		const { container } = render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
-
-		const itemDiv = container.firstChild
-		expect(itemDiv).toHaveClass("hover:bg-vscode-list-hoverBackground")
-	})
-
-	it("renders both edit and delete buttons", () => {
-		render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
-
-		const buttons = screen.getAllByTestId("button")
-		expect(buttons).toHaveLength(2)
-	})
-})

+ 0 - 436
webview-ui/src/components/settings/__tests__/SkillsSettings.spec.tsx

@@ -1,436 +0,0 @@
-import { render, screen, fireEvent, waitFor } from "@/utils/test-utils"
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
-
-import type { SkillMetadata } from "@roo-code/types"
-
-import { ExtensionStateContextProvider } from "@/context/ExtensionStateContext"
-import { vscode } from "@/utils/vscode"
-
-import { SkillsSettings } from "../SkillsSettings"
-
-// Mock vscode
-vi.mock("@/utils/vscode", () => ({
-	vscode: {
-		postMessage: vi.fn(),
-	},
-}))
-
-// Mock the translation hook
-vi.mock("@/i18n/TranslationContext", () => ({
-	useAppTranslation: () => ({
-		t: (key: string, params?: any) => {
-			if (params?.name) {
-				return `${key} ${params.name}`
-			}
-			return key
-		},
-	}),
-}))
-
-// Mock the doc links utility
-vi.mock("@/utils/docLinks", () => ({
-	buildDocLink: (path: string, anchor?: string) => `https://docs.example.com/${path}${anchor ? `#${anchor}` : ""}`,
-}))
-
-// Mock UI components
-vi.mock("@/components/ui", () => ({
-	AlertDialog: ({ children, open }: any) => (
-		<div data-testid="alert-dialog" data-open={open}>
-			{open && children}
-		</div>
-	),
-	AlertDialogContent: ({ children }: any) => <div data-testid="alert-dialog-content">{children}</div>,
-	AlertDialogHeader: ({ children }: any) => <div data-testid="alert-dialog-header">{children}</div>,
-	AlertDialogTitle: ({ children }: any) => <div data-testid="alert-dialog-title">{children}</div>,
-	AlertDialogDescription: ({ children }: any) => <div data-testid="alert-dialog-description">{children}</div>,
-	AlertDialogFooter: ({ children }: any) => <div data-testid="alert-dialog-footer">{children}</div>,
-	AlertDialogAction: ({ children, onClick }: any) => (
-		<button data-testid="alert-dialog-action" onClick={onClick}>
-			{children}
-		</button>
-	),
-	AlertDialogCancel: ({ children, onClick }: any) => (
-		<button data-testid="alert-dialog-cancel" onClick={onClick}>
-			{children}
-		</button>
-	),
-	Button: ({ children, onClick, disabled, className, variant, size }: any) => (
-		<button
-			onClick={onClick}
-			disabled={disabled}
-			className={className}
-			data-variant={variant}
-			data-size={size}
-			data-testid="button">
-			{children}
-		</button>
-	),
-}))
-
-// Mock SkillItem component
-vi.mock("../SkillItem", () => ({
-	SkillItem: ({ skill, onEdit, onDelete }: any) => (
-		<div data-testid={`skill-item-${skill.name}`}>
-			<span>{skill.name}</span>
-			{skill.description && <span>{skill.description}</span>}
-			{skill.mode && <span data-testid={`skill-mode-${skill.name}`}>{skill.mode}</span>}
-			<button onClick={onEdit} data-testid={`edit-${skill.name}`}>
-				Edit
-			</button>
-			<button onClick={onDelete} data-testid={`delete-${skill.name}`}>
-				Delete
-			</button>
-		</div>
-	),
-}))
-
-// Mock CreateSkillDialog component
-vi.mock("../CreateSkillDialog", () => ({
-	CreateSkillDialog: ({ open, onOpenChange, onSkillCreated }: any) => (
-		<div data-testid="create-skill-dialog" data-open={open}>
-			{open && (
-				<>
-					<button onClick={() => onOpenChange(false)} data-testid="close-dialog">
-						Close
-					</button>
-					<button onClick={onSkillCreated} data-testid="create-skill-button">
-						Create
-					</button>
-				</>
-			)}
-		</div>
-	),
-}))
-
-// Mock SectionHeader and Section components
-vi.mock("../SectionHeader", () => ({
-	SectionHeader: ({ children }: any) => <div data-testid="section-header">{children}</div>,
-}))
-
-vi.mock("../Section", () => ({
-	Section: ({ children }: any) => <div data-testid="section">{children}</div>,
-}))
-
-// Mock SearchableSetting
-vi.mock("../SearchableSetting", () => ({
-	SearchableSetting: ({ children }: any) => <div data-testid="searchable-setting">{children}</div>,
-}))
-
-const mockSkills: SkillMetadata[] = [
-	{
-		name: "project-skill",
-		description: "A project skill",
-		path: "/workspace/.roo/skills/project-skill/SKILL.md",
-		source: "project",
-	},
-	{
-		name: "project-mode-skill",
-		description: "A project mode-specific skill",
-		path: "/workspace/.roo/skills-architect/project-mode-skill/SKILL.md",
-		source: "project",
-		mode: "architect",
-	},
-	{
-		name: "global-skill",
-		description: "A global skill",
-		path: "/home/.roo/skills/global-skill/SKILL.md",
-		source: "global",
-	},
-]
-
-// Create a variable to hold the mock state
-let mockExtensionState: any = {}
-
-// Mock the useExtensionState hook
-vi.mock("@/context/ExtensionStateContext", () => ({
-	ExtensionStateContextProvider: ({ children }: any) => children,
-	useExtensionState: () => mockExtensionState,
-}))
-
-const renderSkillsSettings = (skills: SkillMetadata[] = mockSkills, cwd?: string) => {
-	const queryClient = new QueryClient({
-		defaultOptions: {
-			queries: { retry: false },
-			mutations: { retry: false },
-		},
-	})
-
-	// Update the mock state before rendering
-	mockExtensionState = {
-		skills,
-		cwd: cwd !== undefined ? cwd : "/workspace",
-		customModes: [],
-	}
-
-	return render(
-		<QueryClientProvider client={queryClient}>
-			<ExtensionStateContextProvider>
-				<SkillsSettings />
-			</ExtensionStateContextProvider>
-		</QueryClientProvider>,
-	)
-}
-
-describe("SkillsSettings", () => {
-	beforeEach(() => {
-		vi.clearAllMocks()
-	})
-
-	it("renders section header", () => {
-		renderSkillsSettings()
-
-		expect(screen.getByTestId("section-header")).toBeInTheDocument()
-		expect(screen.getByText("settings:sections.skills")).toBeInTheDocument()
-	})
-
-	it("requests skills on mount", () => {
-		renderSkillsSettings()
-
-		expect(vscode.postMessage).toHaveBeenCalledWith({ type: "requestSkills" })
-	})
-
-	it("displays project skills section when in a workspace", () => {
-		renderSkillsSettings()
-
-		expect(screen.getByText("settings:skills.projectSkills")).toBeInTheDocument()
-		expect(screen.getByTestId("skill-item-project-skill")).toBeInTheDocument()
-	})
-
-	it("displays global skills section", () => {
-		renderSkillsSettings()
-
-		expect(screen.getByText("settings:skills.globalSkills")).toBeInTheDocument()
-		expect(screen.getByTestId("skill-item-global-skill")).toBeInTheDocument()
-	})
-
-	it("does not display project skills section when not in a workspace", () => {
-		const globalOnlySkills = mockSkills.filter((s) => s.source === "global")
-		renderSkillsSettings(globalOnlySkills, "")
-
-		expect(screen.queryByText("settings:skills.projectSkills")).not.toBeInTheDocument()
-	})
-
-	it("shows empty state for project skills when none exist", () => {
-		const globalOnlySkills = mockSkills.filter((s) => s.source === "global")
-		renderSkillsSettings(globalOnlySkills)
-
-		expect(screen.getByText("settings:skills.noProjectSkills")).toBeInTheDocument()
-	})
-
-	it("shows empty state for global skills when none exist", () => {
-		const projectOnlySkills = mockSkills.filter((s) => s.source === "project")
-		renderSkillsSettings(projectOnlySkills)
-
-		expect(screen.getByText("settings:skills.noGlobalSkills")).toBeInTheDocument()
-	})
-
-	it("groups skills by source correctly", () => {
-		renderSkillsSettings()
-
-		// Project skills
-		expect(screen.getByTestId("skill-item-project-skill")).toBeInTheDocument()
-		expect(screen.getByTestId("skill-item-project-mode-skill")).toBeInTheDocument()
-
-		// Global skills
-		expect(screen.getByTestId("skill-item-global-skill")).toBeInTheDocument()
-	})
-
-	it("displays mode badge for mode-specific skills", () => {
-		renderSkillsSettings()
-
-		expect(screen.getByTestId("skill-mode-project-mode-skill")).toBeInTheDocument()
-		expect(screen.getByText("architect")).toBeInTheDocument()
-	})
-
-	it("opens create skill dialog when add button is clicked", () => {
-		renderSkillsSettings()
-
-		const addButtons = screen.getAllByTestId("button")
-		fireEvent.click(addButtons[0])
-
-		expect(screen.getByTestId("create-skill-dialog")).toHaveAttribute("data-open", "true")
-	})
-
-	it("opens delete confirmation dialog when delete button is clicked", () => {
-		renderSkillsSettings()
-
-		const deleteButton = screen.getByTestId("delete-project-skill")
-		fireEvent.click(deleteButton)
-
-		expect(screen.getByTestId("alert-dialog")).toHaveAttribute("data-open", "true")
-		expect(screen.getByText("settings:skills.deleteDialog.title")).toBeInTheDocument()
-	})
-
-	it("deletes skill when confirmation is clicked", async () => {
-		renderSkillsSettings()
-
-		const deleteButton = screen.getByTestId("delete-project-skill")
-		fireEvent.click(deleteButton)
-
-		const confirmButton = screen.getByTestId("alert-dialog-action")
-		fireEvent.click(confirmButton)
-
-		await waitFor(() => {
-			expect(vscode.postMessage).toHaveBeenCalledWith({
-				type: "deleteSkill",
-				skillName: "project-skill",
-				source: "project",
-				skillMode: undefined,
-			})
-		})
-	})
-
-	it("cancels deletion when cancel is clicked", () => {
-		renderSkillsSettings()
-
-		const deleteButton = screen.getByTestId("delete-project-skill")
-		fireEvent.click(deleteButton)
-
-		const cancelButton = screen.getByTestId("alert-dialog-cancel")
-		fireEvent.click(cancelButton)
-
-		expect(screen.getByTestId("alert-dialog")).toHaveAttribute("data-open", "false")
-	})
-
-	it("opens skill file when edit button is clicked", () => {
-		renderSkillsSettings()
-
-		const editButton = screen.getByTestId("edit-project-skill")
-		fireEvent.click(editButton)
-
-		expect(vscode.postMessage).toHaveBeenCalledWith({
-			type: "openSkillFile",
-			skillName: "project-skill",
-			source: "project",
-			skillMode: undefined,
-		})
-	})
-
-	it("sends mode when editing mode-specific skill", () => {
-		renderSkillsSettings()
-
-		const editButton = screen.getByTestId("edit-project-mode-skill")
-		fireEvent.click(editButton)
-
-		expect(vscode.postMessage).toHaveBeenCalledWith({
-			type: "openSkillFile",
-			skillName: "project-mode-skill",
-			source: "project",
-			skillMode: "architect",
-		})
-	})
-
-	it("sends mode when deleting mode-specific skill", async () => {
-		renderSkillsSettings()
-
-		const deleteButton = screen.getByTestId("delete-project-mode-skill")
-		fireEvent.click(deleteButton)
-
-		const confirmButton = screen.getByTestId("alert-dialog-action")
-		fireEvent.click(confirmButton)
-
-		await waitFor(() => {
-			expect(vscode.postMessage).toHaveBeenCalledWith({
-				type: "deleteSkill",
-				skillName: "project-mode-skill",
-				source: "project",
-				skillMode: "architect",
-			})
-		})
-	})
-
-	it("does not manually refresh after deletion (backend sends updated skills via context)", async () => {
-		renderSkillsSettings()
-
-		// Clear mock calls after initial mount
-		;(vscode.postMessage as any).mockClear()
-
-		const deleteButton = screen.getByTestId("delete-project-skill")
-		fireEvent.click(deleteButton)
-
-		const confirmButton = screen.getByTestId("alert-dialog-action")
-		fireEvent.click(confirmButton)
-
-		// Verify deleteSkill message was sent
-		await waitFor(() => {
-			expect(vscode.postMessage).toHaveBeenCalledWith({
-				type: "deleteSkill",
-				skillName: "project-skill",
-				source: "project",
-				skillMode: undefined,
-			})
-		})
-
-		// Verify that requestSkills was NOT called after deletion
-		// (the backend sends updated skills via ExtensionStateContext automatically)
-		const calls = (vscode.postMessage as any).mock.calls
-		const refreshCalls = calls.filter((call: any[]) => call[0].type === "requestSkills")
-		expect(refreshCalls.length).toBe(0)
-	})
-
-	it("does not manually refresh after creating new skill (backend sends updated skills via context)", async () => {
-		renderSkillsSettings()
-
-		// Clear mock calls after initial mount
-		;(vscode.postMessage as any).mockClear()
-
-		// Open create dialog
-		const addButtons = screen.getAllByTestId("button")
-		fireEvent.click(addButtons[0])
-
-		// Simulate skill creation
-		const createButton = screen.getByTestId("create-skill-button")
-		fireEvent.click(createButton)
-
-		// Verify that requestSkills was NOT called after creation
-		// (the backend sends updated skills via ExtensionStateContext automatically)
-		const calls = (vscode.postMessage as any).mock.calls
-		const refreshCalls = calls.filter((call: any[]) => call[0].type === "requestSkills")
-		expect(refreshCalls.length).toBe(0)
-	})
-
-	it("renders empty state when no skills exist", () => {
-		renderSkillsSettings([])
-
-		expect(screen.getByText("settings:skills.noProjectSkills")).toBeInTheDocument()
-		expect(screen.getByText("settings:skills.noGlobalSkills")).toBeInTheDocument()
-	})
-
-	it("handles multiple skills of the same source", () => {
-		const multipleSkills: SkillMetadata[] = [
-			{
-				name: "skill-1",
-				description: "First skill",
-				path: "/path/1",
-				source: "global",
-			},
-			{
-				name: "skill-2",
-				description: "Second skill",
-				path: "/path/2",
-				source: "global",
-			},
-			{
-				name: "skill-3",
-				description: "Third skill",
-				path: "/path/3",
-				source: "global",
-			},
-		]
-
-		renderSkillsSettings(multipleSkills)
-
-		expect(screen.getByTestId("skill-item-skill-1")).toBeInTheDocument()
-		expect(screen.getByTestId("skill-item-skill-2")).toBeInTheDocument()
-		expect(screen.getByTestId("skill-item-skill-3")).toBeInTheDocument()
-	})
-
-	it("renders add skill button in each section", () => {
-		renderSkillsSettings()
-
-		// Should have two "Add Skill" buttons - one for project, one for global
-		const buttons = screen.getAllByTestId("button")
-		const addButtons = buttons.filter((btn) => btn.textContent?.includes("settings:skills.addSkill"))
-		expect(addButtons.length).toBe(2)
-	})
-})

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

@@ -15,7 +15,6 @@ import {
 	type MarketplaceInstalledMetadata,
 	type Command,
 	type McpServer,
-	type SkillMetadata,
 	RouterModels,
 	ORGANIZATION_ALLOW_ALL,
 	DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
@@ -43,7 +42,6 @@ export interface ExtensionStateContextType extends ExtensionState {
 	filePaths: string[]
 	openedTabs: Array<{ label: string; isActive: boolean; path?: string }>
 	commands: Command[]
-	skills: SkillMetadata[]
 	organizationAllowList: OrganizationAllowList
 	organizationSettingsVersion: number
 	cloudIsAuthenticated: boolean
@@ -102,8 +100,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 +209,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		screenshotQuality: 75,
 		terminalShellIntegrationTimeout: 4000,
 		mcpEnabled: true,
-		enableMcpServerCreation: false,
 		remoteControlEnabled: false,
 		taskSyncEnabled: false,
 		featureRoomoteControlEnabled: false,
@@ -280,7 +275,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 	const [filePaths, setFilePaths] = useState<string[]>([])
 	const [openedTabs, setOpenedTabs] = useState<Array<{ label: string; isActive: boolean; path?: string }>>([])
 	const [commands, setCommands] = useState<Command[]>([])
-	const [skills, setSkills] = useState<SkillMetadata[]>([])
 	const [mcpServers, setMcpServers] = useState<McpServer[]>([])
 	const [currentCheckpoint, setCurrentCheckpoint] = useState<string>()
 	const [extensionRouterModels, setExtensionRouterModels] = useState<RouterModels | undefined>(undefined)
@@ -379,10 +373,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 					setCommands(message.commands ?? [])
 					break
 				}
-				case "skills": {
-					setSkills(message.skills ?? [])
-					break
-				}
 				case "messageUpdated": {
 					const clineMessage = message.clineMessage!
 					setState((prevState) => {
@@ -495,7 +485,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		filePaths,
 		openedTabs,
 		commands,
-		skills,
 		soundVolume: state.soundVolume,
 		ttsSpeed: state.ttsSpeed,
 		writeDelayMs: state.writeDelayMs,
@@ -553,8 +542,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…"

+ 1 - 47
webview-ui/src/i18n/locales/ca/settings.json

@@ -41,8 +41,7 @@
 		"ui": "UI",
 		"experimental": "Experimental",
 		"language": "Idioma",
-		"about": "Sobre Roo Code",
-		"skills": "Skills"
+		"about": "Sobre Roo Code"
 	},
 	"about": {
 		"bugReport": {
@@ -993,50 +992,5 @@
 			"label": "Requereix {{primaryMod}}+Intro per enviar missatges",
 			"description": "Quan estigui activat, has de prémer {{primaryMod}}+Intro per enviar missatges en lloc de només Intro"
 		}
-	},
-	"skills": {
-		"description": "Gestiona les skills que proporcionen instruccions contextuals a l'agent. Les skills s'apliquen automàticament quan són rellevants per a les teves tasques. <DocsLink>Més informació</DocsLink>",
-		"projectSkills": "Skills del Projecte",
-		"globalSkills": "Skills Globals",
-		"noProjectSkills": "No hi ha skills de projecte configurades. Crea'n una per afegir capacitats específiques del projecte a l'agent.",
-		"noGlobalSkills": "No hi ha skills globals configurades. Crea'n una per afegir capacitats a l'agent disponibles en tots els projectes.",
-		"addSkill": "Afegir Skill",
-		"editSkill": "Editar skill",
-		"deleteSkill": "Eliminar skill",
-		"deleteDialog": {
-			"title": "Eliminar Skill",
-			"description": "Estàs segur que vols eliminar la skill \"{{name}}\"? Aquesta acció no es pot desfer.",
-			"confirm": "Eliminar",
-			"cancel": "Cancel·lar"
-		},
-		"createDialog": {
-			"title": "Crear Nova Skill",
-			"description": "Defineix una nova plantilla de skill que proporcioni instruccions contextuals a l'agent.",
-			"nameLabel": "Nom",
-			"namePlaceholder": "el-meu-nom-de-skill",
-			"nameHint": "Només lletres minúscules, números i guions (1-64 caràcters)",
-			"descriptionLabel": "Descripció",
-			"descriptionPlaceholder": "Descriu quan s'hauria d'utilitzar aquesta skill...",
-			"descriptionHint": "Explica què fa aquesta skill i quan l'agent hauria d'aplicar-la (1-1024 caràcters)",
-			"sourceLabel": "Ubicació",
-			"sourceHint": "Tria si aquesta skill està disponible globalment o només en aquest projecte",
-			"modeLabel": "Mode (opcional)",
-			"modePlaceholder": "Qualsevol mode",
-			"modeHint": "Restringeix aquesta skill a un mode específic",
-			"modeAny": "Qualsevol mode",
-			"create": "Crear",
-			"cancel": "Cancel·lar"
-		},
-		"source": {
-			"global": "Global (disponible en tots els projectes)",
-			"project": "Projecte (només aquest espai de treball)"
-		},
-		"validation": {
-			"nameRequired": "El nom és obligatori",
-			"nameTooLong": "El nom ha de tenir com a màxim 64 caràcters",
-			"nameInvalid": "El nom ha de tenir entre 1 i 64 caràcters, només lletres minúscules, números o guions",
-			"descriptionRequired": "La descripció és obligatòria",
-			"descriptionTooLong": "La descripció ha de tenir com a màxim 1024 caràcters"
-		}
 	}
 }

+ 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…"

+ 1 - 47
webview-ui/src/i18n/locales/de/settings.json

@@ -41,8 +41,7 @@
 		"ui": "UI",
 		"experimental": "Experimentell",
 		"language": "Sprache",
-		"about": "Über Roo Code",
-		"skills": "Skills"
+		"about": "Über Roo Code"
 	},
 	"about": {
 		"bugReport": {
@@ -993,50 +992,5 @@
 			"label": "{{primaryMod}}+Enter zum Senden erfordern",
 			"description": "Wenn aktiviert, musst du {{primaryMod}}+Enter drücken, um Nachrichten zu senden, anstatt nur Enter"
 		}
-	},
-	"skills": {
-		"description": "Verwalten Sie Skills, die dem Agenten kontextbezogene Anweisungen bereitstellen. Skills werden automatisch angewendet, wenn sie für Ihre Aufgaben relevant sind. <DocsLink>Mehr erfahren</DocsLink>",
-		"projectSkills": "Projekt-Skills",
-		"globalSkills": "Globale Skills",
-		"noProjectSkills": "Keine Projekt-Skills konfiguriert. Erstellen Sie eine, um projektspezifische Agentenfähigkeiten hinzuzufügen.",
-		"noGlobalSkills": "Keine globalen Skills konfiguriert. Erstellen Sie eine, um Agentenfähigkeiten hinzuzufügen, die in allen Projekten verfügbar sind.",
-		"addSkill": "Skill hinzufügen",
-		"editSkill": "Skill bearbeiten",
-		"deleteSkill": "Skill löschen",
-		"deleteDialog": {
-			"title": "Skill löschen",
-			"description": "Sind Sie sicher, dass Sie die Skill \"{{name}}\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
-			"confirm": "Löschen",
-			"cancel": "Abbrechen"
-		},
-		"createDialog": {
-			"title": "Neue Skill erstellen",
-			"description": "Definieren Sie eine neue Skill-Vorlage, die dem Agenten kontextbezogene Anweisungen bereitstellt.",
-			"nameLabel": "Name",
-			"namePlaceholder": "mein-skill-name",
-			"nameHint": "Nur Kleinbuchstaben, Zahlen und Bindestriche (1-64 Zeichen)",
-			"descriptionLabel": "Beschreibung",
-			"descriptionPlaceholder": "Beschreiben Sie, wann diese Skill verwendet werden sollte...",
-			"descriptionHint": "Erklären Sie, was diese Skill tut und wann der Agent sie anwenden sollte (1-1024 Zeichen)",
-			"sourceLabel": "Standort",
-			"sourceHint": "Wählen Sie, ob diese Skill global oder nur in diesem Projekt verfügbar ist",
-			"modeLabel": "Modus (optional)",
-			"modePlaceholder": "Beliebiger Modus",
-			"modeHint": "Beschränken Sie diese Skill auf einen bestimmten Modus",
-			"modeAny": "Beliebiger Modus",
-			"create": "Erstellen",
-			"cancel": "Abbrechen"
-		},
-		"source": {
-			"global": "Global (in allen Projekten verfügbar)",
-			"project": "Projekt (nur dieser Arbeitsbereich)"
-		},
-		"validation": {
-			"nameRequired": "Name ist erforderlich",
-			"nameTooLong": "Name darf höchstens 64 Zeichen lang sein",
-			"nameInvalid": "Name muss 1-64 Kleinbuchstaben, Zahlen oder Bindestriche enthalten",
-			"descriptionRequired": "Beschreibung ist erforderlich",
-			"descriptionTooLong": "Beschreibung darf höchstens 1024 Zeichen lang sein"
-		}
 	}
 }

+ 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",

+ 0 - 46
webview-ui/src/i18n/locales/en/settings.json

@@ -30,7 +30,6 @@
 		"modes": "Modes",
 		"mcp": "MCP Servers",
 		"worktrees": "Worktrees",
-		"skills": "Skills",
 		"autoApprove": "Auto-Approve",
 		"browser": "Browser",
 		"checkpoints": "Checkpoints",
@@ -71,51 +70,6 @@
 	"slashCommands": {
 		"description": "Manage your slash commands to quickly execute custom workflows and actions. <DocsLink>Learn more</DocsLink>"
 	},
-	"skills": {
-		"description": "Manage skills that provide contextual instructions to the agent. Skills are automatically applied when relevant to your tasks. <DocsLink>Learn more</DocsLink>",
-		"projectSkills": "Project Skills",
-		"globalSkills": "Global Skills",
-		"noProjectSkills": "No project skills configured. Create one to add project-specific agent capabilities.",
-		"noGlobalSkills": "No global skills configured. Create one to add agent capabilities available across all projects.",
-		"addSkill": "Add Skill",
-		"editSkill": "Edit skill",
-		"deleteSkill": "Delete skill",
-		"deleteDialog": {
-			"title": "Delete Skill",
-			"description": "Are you sure you want to delete the skill \"{{name}}\"? This action cannot be undone.",
-			"confirm": "Delete",
-			"cancel": "Cancel"
-		},
-		"createDialog": {
-			"title": "Create New Skill",
-			"description": "Define a new skill template that provides contextual instructions to the agent.",
-			"nameLabel": "Name",
-			"namePlaceholder": "my-skill-name",
-			"nameHint": "Lowercase letters, numbers, and hyphens only (1-64 characters)",
-			"descriptionLabel": "Description",
-			"descriptionPlaceholder": "Describe when this skill should be used...",
-			"descriptionHint": "Explain what this skill does and when the agent should apply it (1-1024 characters)",
-			"sourceLabel": "Location",
-			"sourceHint": "Choose whether this skill is available globally or only in this project",
-			"modeLabel": "Mode (optional)",
-			"modePlaceholder": "Any mode",
-			"modeHint": "Restrict this skill to a specific mode",
-			"modeAny": "Any mode",
-			"create": "Create",
-			"cancel": "Cancel"
-		},
-		"source": {
-			"global": "Global (available in all projects)",
-			"project": "Project (this workspace only)"
-		},
-		"validation": {
-			"nameRequired": "Name is required",
-			"nameTooLong": "Name must be 64 characters or less",
-			"nameInvalid": "Name must be 1-64 lowercase letters, numbers, or hyphens",
-			"descriptionRequired": "Description is required",
-			"descriptionTooLong": "Description must be 1024 characters or less"
-		}
-	},
 	"ui": {
 		"collapseThinking": {
 			"label": "Collapse Thinking messages by default",

+ 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…"

+ 1 - 47
webview-ui/src/i18n/locales/es/settings.json

@@ -41,8 +41,7 @@
 		"ui": "UI",
 		"experimental": "Experimental",
 		"language": "Idioma",
-		"about": "Acerca de Roo Code",
-		"skills": "Skills"
+		"about": "Acerca de Roo Code"
 	},
 	"about": {
 		"bugReport": {
@@ -993,50 +992,5 @@
 			"label": "Requerir {{primaryMod}}+Enter para enviar mensajes",
 			"description": "Cuando está activado, debes presionar {{primaryMod}}+Enter para enviar mensajes en lugar de solo Enter"
 		}
-	},
-	"skills": {
-		"description": "Gestiona skills que proporcionan instrucciones contextuales al agente. Las skills se aplican automáticamente cuando son relevantes para tus tareas. <DocsLink>Más información</DocsLink>",
-		"projectSkills": "Skills del Proyecto",
-		"globalSkills": "Skills Globales",
-		"noProjectSkills": "No hay skills de proyecto configuradas. Crea una para añadir capacidades específicas del proyecto al agente.",
-		"noGlobalSkills": "No hay skills globales configuradas. Crea una para añadir capacidades al agente disponibles en todos los proyectos.",
-		"addSkill": "Añadir Skill",
-		"editSkill": "Editar skill",
-		"deleteSkill": "Eliminar skill",
-		"deleteDialog": {
-			"title": "Eliminar Skill",
-			"description": "¿Estás seguro de que quieres eliminar la skill \"{{name}}\"? Esta acción no se puede deshacer.",
-			"confirm": "Eliminar",
-			"cancel": "Cancelar"
-		},
-		"createDialog": {
-			"title": "Crear Nueva Skill",
-			"description": "Define una nueva plantilla de skill que proporcione instrucciones contextuales al agente.",
-			"nameLabel": "Nombre",
-			"namePlaceholder": "mi-nombre-de-skill",
-			"nameHint": "Solo letras minúsculas, números y guiones (1-64 caracteres)",
-			"descriptionLabel": "Descripción",
-			"descriptionPlaceholder": "Describe cuándo debería usarse esta skill...",
-			"descriptionHint": "Explica qué hace esta skill y cuándo el agente debería aplicarla (1-1024 caracteres)",
-			"sourceLabel": "Ubicación",
-			"sourceHint": "Elige si esta skill está disponible globalmente o solo en este proyecto",
-			"modeLabel": "Modo (opcional)",
-			"modePlaceholder": "Cualquier modo",
-			"modeHint": "Restringe esta skill a un modo específico",
-			"modeAny": "Cualquier modo",
-			"create": "Crear",
-			"cancel": "Cancelar"
-		},
-		"source": {
-			"global": "Global (disponible en todos los proyectos)",
-			"project": "Proyecto (solo este espacio de trabajo)"
-		},
-		"validation": {
-			"nameRequired": "El nombre es obligatorio",
-			"nameTooLong": "El nombre debe tener como máximo 64 caracteres",
-			"nameInvalid": "El nombre debe tener entre 1 y 64 caracteres, solo letras minúsculas, números o guiones",
-			"descriptionRequired": "La descripción es obligatoria",
-			"descriptionTooLong": "La descripción debe tener como máximo 1024 caracteres"
-		}
 	}
 }

+ 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…"

+ 1 - 47
webview-ui/src/i18n/locales/fr/settings.json

@@ -41,8 +41,7 @@
 		"ui": "UI",
 		"experimental": "Expérimental",
 		"language": "Langue",
-		"about": "À propos de Roo Code",
-		"skills": "Skills"
+		"about": "À propos de Roo Code"
 	},
 	"about": {
 		"bugReport": {
@@ -993,50 +992,5 @@
 			"label": "Exiger {{primaryMod}}+Entrée pour envoyer les messages",
 			"description": "Lorsqu'activé, tu dois appuyer sur {{primaryMod}}+Entrée pour envoyer des messages au lieu de simplement Entrée"
 		}
-	},
-	"skills": {
-		"description": "Gérez les skills qui fournissent des instructions contextuelles à l'agent. Les skills sont automatiquement appliquées lorsqu'elles sont pertinentes pour vos tâches. <DocsLink>En savoir plus</DocsLink>",
-		"projectSkills": "Skills du Projet",
-		"globalSkills": "Skills Globales",
-		"noProjectSkills": "Aucune skill de projet configurée. Créez-en une pour ajouter des capacités spécifiques au projet à l'agent.",
-		"noGlobalSkills": "Aucune skill globale configurée. Créez-en une pour ajouter des capacités à l'agent disponibles dans tous les projets.",
-		"addSkill": "Ajouter une Skill",
-		"editSkill": "Modifier la skill",
-		"deleteSkill": "Supprimer la skill",
-		"deleteDialog": {
-			"title": "Supprimer la Skill",
-			"description": "Êtes-vous sûr de vouloir supprimer la skill \"{{name}}\" ? Cette action ne peut pas être annulée.",
-			"confirm": "Supprimer",
-			"cancel": "Annuler"
-		},
-		"createDialog": {
-			"title": "Créer une Nouvelle Skill",
-			"description": "Définissez un nouveau modèle de skill qui fournit des instructions contextuelles à l'agent.",
-			"nameLabel": "Nom",
-			"namePlaceholder": "mon-nom-de-skill",
-			"nameHint": "Lettres minuscules, chiffres et tirets uniquement (1-64 caractères)",
-			"descriptionLabel": "Description",
-			"descriptionPlaceholder": "Décrivez quand cette skill devrait être utilisée...",
-			"descriptionHint": "Expliquez ce que fait cette skill et quand l'agent devrait l'appliquer (1-1024 caractères)",
-			"sourceLabel": "Emplacement",
-			"sourceHint": "Choisissez si cette skill est disponible globalement ou uniquement dans ce projet",
-			"modeLabel": "Mode (optionnel)",
-			"modePlaceholder": "N'importe quel mode",
-			"modeHint": "Restreindre cette skill à un mode spécifique",
-			"modeAny": "N'importe quel mode",
-			"create": "Créer",
-			"cancel": "Annuler"
-		},
-		"source": {
-			"global": "Globale (disponible dans tous les projets)",
-			"project": "Projet (cet espace de travail uniquement)"
-		},
-		"validation": {
-			"nameRequired": "Le nom est obligatoire",
-			"nameTooLong": "Le nom doit contenir au maximum 64 caractères",
-			"nameInvalid": "Le nom doit contenir entre 1 et 64 lettres minuscules, chiffres ou tirets",
-			"descriptionRequired": "La description est obligatoire",
-			"descriptionTooLong": "La description doit contenir au maximum 1024 caractères"
-		}
 	}
 }

+ 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 में चयन किया जा रहा है…"

+ 1 - 47
webview-ui/src/i18n/locales/hi/settings.json

@@ -41,8 +41,7 @@
 		"ui": "UI",
 		"experimental": "प्रायोगिक",
 		"language": "भाषा",
-		"about": "परिचय",
-		"skills": "Skills"
+		"about": "परिचय"
 	},
 	"about": {
 		"bugReport": {
@@ -994,50 +993,5 @@
 			"label": "संदेश भेजने के लिए {{primaryMod}}+Enter की आवश्यकता है",
 			"description": "जब सक्षम हो, तो आपको केवल Enter के बजाय संदेश भेजने के लिए {{primaryMod}}+Enter दबाना होगा"
 		}
-	},
-	"skills": {
-		"description": "Skills का प्रबंधन करें जो एजेंट को संदर्भात्मक निर्देश प्रदान करते हैं। जब आपके कार्यों के लिए प्रासंगिक हों तो Skills स्वचालित रूप से लागू होते हैं। <DocsLink>और जानें</DocsLink>",
-		"projectSkills": "प्रोजेक्ट Skills",
-		"globalSkills": "ग्लोबल Skills",
-		"noProjectSkills": "कोई प्रोजेक्ट skills कॉन्फ़िगर नहीं किया गया। प्रोजेक्ट-विशिष्ट एजेंट क्षमताएं जोड़ने के लिए एक बनाएं।",
-		"noGlobalSkills": "कोई ग्लोबल skills कॉन्फ़िगर नहीं किया गया। सभी प्रोजेक्ट्स में उपलब्ध एजेंट क्षमताएं जोड़ने के लिए एक बनाएं।",
-		"addSkill": "Skill जोड़ें",
-		"editSkill": "Skill संपादित करें",
-		"deleteSkill": "Skill हटाएं",
-		"deleteDialog": {
-			"title": "Skill हटाएं",
-			"description": "क्या आप वाकई skill \"{{name}}\" को हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।",
-			"confirm": "हटाएं",
-			"cancel": "रद्द करें"
-		},
-		"createDialog": {
-			"title": "नया Skill बनाएं",
-			"description": "एक नया skill टेम्पलेट परिभाषित करें जो एजेंट को संदर्भात्मक निर्देश प्रदान करता है।",
-			"nameLabel": "नाम",
-			"namePlaceholder": "my-skill-name",
-			"nameHint": "केवल छोटे अक्षर, संख्याएं और हाइफ़न (1-64 वर्ण)",
-			"descriptionLabel": "विवरण",
-			"descriptionPlaceholder": "वर्णन करें कि इस skill का उपयोग कब किया जाना चाहिए...",
-			"descriptionHint": "समझाएं कि यह skill क्या करता है और एजेंट को इसे कब लागू करना चाहिए (1-1024 वर्ण)",
-			"sourceLabel": "स्थान",
-			"sourceHint": "चुनें कि यह skill ग्लोबल रूप से उपलब्ध है या केवल इस प्रोजेक्ट में",
-			"modeLabel": "मोड (वैकल्पिक)",
-			"modePlaceholder": "कोई भी मोड",
-			"modeHint": "इस skill को किसी विशिष्ट मोड तक सीमित करें",
-			"modeAny": "कोई भी मोड",
-			"create": "बनाएं",
-			"cancel": "रद्द करें"
-		},
-		"source": {
-			"global": "ग्लोबल (सभी प्रोजेक्ट्स में उपलब्ध)",
-			"project": "प्रोजेक्ट (केवल यह वर्कस्पेस)"
-		},
-		"validation": {
-			"nameRequired": "नाम आवश्यक है",
-			"nameTooLong": "नाम 64 वर्णों से अधिक नहीं होना चाहिए",
-			"nameInvalid": "नाम 1-64 छोटे अक्षर, संख्याएं या हाइफ़न होना चाहिए",
-			"descriptionRequired": "विवरण आवश्यक है",
-			"descriptionTooLong": "विवरण 1024 वर्णों से अधिक नहीं होना चाहिए"
-		}
 	}
 }

+ 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"
 	},

+ 1 - 47
webview-ui/src/i18n/locales/id/settings.json

@@ -41,8 +41,7 @@
 		"ui": "UI",
 		"experimental": "Eksperimental",
 		"language": "Bahasa",
-		"about": "Tentang Roo Code",
-		"skills": "Skills"
+		"about": "Tentang Roo Code"
 	},
 	"about": {
 		"bugReport": {
@@ -1023,50 +1022,5 @@
 			"label": "Memerlukan {{primaryMod}}+Enter untuk mengirim pesan",
 			"description": "Ketika diaktifkan, kamu harus menekan {{primaryMod}}+Enter untuk mengirim pesan alih-alih hanya Enter"
 		}
-	},
-	"skills": {
-		"description": "Kelola skills yang memberikan instruksi kontekstual kepada agen. Skills diterapkan secara otomatis saat relevan dengan tugas Anda. <DocsLink>Pelajari lebih lanjut</DocsLink>",
-		"projectSkills": "Skills Proyek",
-		"globalSkills": "Skills Global",
-		"noProjectSkills": "Tidak ada skills proyek yang dikonfigurasi. Buat satu untuk menambahkan kemampuan agen khusus proyek.",
-		"noGlobalSkills": "Tidak ada skills global yang dikonfigurasi. Buat satu untuk menambahkan kemampuan agen yang tersedia di semua proyek.",
-		"addSkill": "Tambahkan Skill",
-		"editSkill": "Edit skill",
-		"deleteSkill": "Hapus skill",
-		"deleteDialog": {
-			"title": "Hapus Skill",
-			"description": "Apakah Anda yakin ingin menghapus skill \"{{name}}\"? Tindakan ini tidak dapat dibatalkan.",
-			"confirm": "Hapus",
-			"cancel": "Batal"
-		},
-		"createDialog": {
-			"title": "Buat Skill Baru",
-			"description": "Tentukan template skill baru yang memberikan instruksi kontekstual kepada agen.",
-			"nameLabel": "Nama",
-			"namePlaceholder": "nama-skill-saya",
-			"nameHint": "Hanya huruf kecil, angka, dan tanda hubung (1-64 karakter)",
-			"descriptionLabel": "Deskripsi",
-			"descriptionPlaceholder": "Jelaskan kapan skill ini harus digunakan...",
-			"descriptionHint": "Jelaskan apa yang dilakukan skill ini dan kapan agen harus menerapkannya (1-1024 karakter)",
-			"sourceLabel": "Lokasi",
-			"sourceHint": "Pilih apakah skill ini tersedia secara global atau hanya di proyek ini",
-			"modeLabel": "Mode (opsional)",
-			"modePlaceholder": "Mode apa saja",
-			"modeHint": "Batasi skill ini ke mode tertentu",
-			"modeAny": "Mode apa saja",
-			"create": "Buat",
-			"cancel": "Batal"
-		},
-		"source": {
-			"global": "Global (tersedia di semua proyek)",
-			"project": "Proyek (workspace ini saja)"
-		},
-		"validation": {
-			"nameRequired": "Nama diperlukan",
-			"nameTooLong": "Nama harus 64 karakter atau kurang",
-			"nameInvalid": "Nama harus 1-64 huruf kecil, angka, atau tanda hubung",
-			"descriptionRequired": "Deskripsi diperlukan",
-			"descriptionTooLong": "Deskripsi harus 1024 karakter atau kurang"
-		}
 	}
 }

+ 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…"

+ 1 - 47
webview-ui/src/i18n/locales/it/settings.json

@@ -41,8 +41,7 @@
 		"ui": "UI",
 		"experimental": "Sperimentale",
 		"language": "Lingua",
-		"about": "Informazioni su Roo Code",
-		"skills": "Skills"
+		"about": "Informazioni su Roo Code"
 	},
 	"about": {
 		"bugReport": {
@@ -994,50 +993,5 @@
 			"label": "Richiedi {{primaryMod}}+Invio per inviare messaggi",
 			"description": "Quando abilitato, devi premere {{primaryMod}}+Invio per inviare messaggi invece di solo Invio"
 		}
-	},
-	"skills": {
-		"description": "Gestisci le skills che forniscono istruzioni contestuali all'agente. Le skills vengono applicate automaticamente quando rilevanti per le tue attività. <DocsLink>Scopri di più</DocsLink>",
-		"projectSkills": "Skills del Progetto",
-		"globalSkills": "Skills Globali",
-		"noProjectSkills": "Nessuna skill di progetto configurata. Creane una per aggiungere capacità specifiche del progetto all'agente.",
-		"noGlobalSkills": "Nessuna skill globale configurata. Creane una per aggiungere capacità all'agente disponibili in tutti i progetti.",
-		"addSkill": "Aggiungi Skill",
-		"editSkill": "Modifica skill",
-		"deleteSkill": "Elimina skill",
-		"deleteDialog": {
-			"title": "Elimina Skill",
-			"description": "Sei sicuro di voler eliminare la skill \"{{name}}\"? Questa azione non può essere annullata.",
-			"confirm": "Elimina",
-			"cancel": "Annulla"
-		},
-		"createDialog": {
-			"title": "Crea Nuova Skill",
-			"description": "Definisci un nuovo modello di skill che fornisce istruzioni contestuali all'agente.",
-			"nameLabel": "Nome",
-			"namePlaceholder": "il-mio-nome-skill",
-			"nameHint": "Solo lettere minuscole, numeri e trattini (1-64 caratteri)",
-			"descriptionLabel": "Descrizione",
-			"descriptionPlaceholder": "Descrivi quando questa skill dovrebbe essere utilizzata...",
-			"descriptionHint": "Spiega cosa fa questa skill e quando l'agente dovrebbe applicarla (1-1024 caratteri)",
-			"sourceLabel": "Posizione",
-			"sourceHint": "Scegli se questa skill è disponibile globalmente o solo in questo progetto",
-			"modeLabel": "Modalità (opzionale)",
-			"modePlaceholder": "Qualsiasi modalità",
-			"modeHint": "Limita questa skill a una modalità specifica",
-			"modeAny": "Qualsiasi modalità",
-			"create": "Crea",
-			"cancel": "Annulla"
-		},
-		"source": {
-			"global": "Globale (disponibile in tutti i progetti)",
-			"project": "Progetto (solo questo workspace)"
-		},
-		"validation": {
-			"nameRequired": "Il nome è obbligatorio",
-			"nameTooLong": "Il nome deve essere di massimo 64 caratteri",
-			"nameInvalid": "Il nome deve contenere 1-64 lettere minuscole, numeri o trattini",
-			"descriptionRequired": "La descrizione è obbligatoria",
-			"descriptionTooLong": "La descrizione deve essere di massimo 1024 caratteri"
-		}
 	}
 }

+ 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}}秒後に選択中…"

+ 1 - 47
webview-ui/src/i18n/locales/ja/settings.json

@@ -41,8 +41,7 @@
 		"ui": "UI",
 		"experimental": "実験的",
 		"language": "言語",
-		"about": "Roo Codeについて",
-		"skills": "Skills"
+		"about": "Roo Codeについて"
 	},
 	"about": {
 		"bugReport": {
@@ -994,50 +993,5 @@
 			"label": "メッセージを送信するには{{primaryMod}}+Enterが必要",
 			"description": "有効にすると、Enterだけでなく{{primaryMod}}+Enterを押してメッセージを送信する必要があります"
 		}
-	},
-	"skills": {
-		"description": "エージェントにコンテキスト指示を提供するスキルを管理します。スキルはタスクに関連する場合に自動的に適用されます。<DocsLink>詳細を見る</DocsLink>",
-		"projectSkills": "プロジェクトスキル",
-		"globalSkills": "グローバルスキル",
-		"noProjectSkills": "プロジェクトスキルが設定されていません。プロジェクト固有のエージェント機能を追加するには、作成してください。",
-		"noGlobalSkills": "グローバルスキルが設定されていません。すべてのプロジェクトで利用可能なエージェント機能を追加するには、作成してください。",
-		"addSkill": "スキルを追加",
-		"editSkill": "スキルを編集",
-		"deleteSkill": "スキルを削除",
-		"deleteDialog": {
-			"title": "スキルを削除",
-			"description": "スキル「{{name}}」を削除してもよろしいですか?この操作は元に戻せません。",
-			"confirm": "削除",
-			"cancel": "キャンセル"
-		},
-		"createDialog": {
-			"title": "新しいスキルを作成",
-			"description": "エージェントにコンテキスト指示を提供する新しいスキルテンプレートを定義します。",
-			"nameLabel": "名前",
-			"namePlaceholder": "my-skill-name",
-			"nameHint": "小文字、数字、ハイフンのみ(1〜64文字)",
-			"descriptionLabel": "説明",
-			"descriptionPlaceholder": "このスキルをいつ使用するか説明してください...",
-			"descriptionHint": "このスキルが何をするか、エージェントがいつ適用すべきかを説明してください(1〜1024文字)",
-			"sourceLabel": "場所",
-			"sourceHint": "このスキルがグローバルに利用可能か、このプロジェクトのみかを選択してください",
-			"modeLabel": "モード(オプション)",
-			"modePlaceholder": "全てのモード",
-			"modeHint": "このスキルを特定のモードに制限する",
-			"modeAny": "全てのモード",
-			"create": "作成",
-			"cancel": "キャンセル"
-		},
-		"source": {
-			"global": "グローバル(すべてのプロジェクトで利用可能)",
-			"project": "プロジェクト(このワークスペースのみ)"
-		},
-		"validation": {
-			"nameRequired": "名前は必須です",
-			"nameTooLong": "名前は64文字以内である必要があります",
-			"nameInvalid": "名前は1〜64文字の小文字、数字、またはハイフンである必要があります",
-			"descriptionRequired": "説明は必須です",
-			"descriptionTooLong": "説明は1024文字以内である必要があります"
-		}
 	}
 }

+ 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}}초 후 선택 중…"

+ 1 - 47
webview-ui/src/i18n/locales/ko/settings.json

@@ -41,8 +41,7 @@
 		"ui": "UI",
 		"experimental": "실험적",
 		"language": "언어",
-		"about": "Roo Code 정보",
-		"skills": "Skills"
+		"about": "Roo Code 정보"
 	},
 	"about": {
 		"bugReport": {
@@ -994,50 +993,5 @@
 			"label": "메시지를 보내려면 {{primaryMod}}+Enter가 필요",
 			"description": "활성화하면 Enter만으로는 안 되고 {{primaryMod}}+Enter를 눌러야 메시지를 보낼 수 있습니다"
 		}
-	},
-	"skills": {
-		"description": "에이전트에 컨텍스트 지침을 제공하는 스킬을 관리합니다. 스킬은 작업과 관련이 있을 때 자동으로 적용됩니다. <DocsLink>자세히 알아보기</DocsLink>",
-		"projectSkills": "프로젝트 스킬",
-		"globalSkills": "전역 스킬",
-		"noProjectSkills": "구성된 프로젝트 스킬이 없습니다. 프로젝트별 에이전트 기능을 추가하려면 하나를 만드세요.",
-		"noGlobalSkills": "구성된 전역 스킬이 없습니다. 모든 프로젝트에서 사용할 수 있는 에이전트 기능을 추가하려면 하나를 만드세요.",
-		"addSkill": "스킬 추가",
-		"editSkill": "스킬 편집",
-		"deleteSkill": "스킬 삭제",
-		"deleteDialog": {
-			"title": "스킬 삭제",
-			"description": "스킬 \"{{name}}\"을(를) 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
-			"confirm": "삭제",
-			"cancel": "취소"
-		},
-		"createDialog": {
-			"title": "새 스킬 만들기",
-			"description": "에이전트에 컨텍스트 지침을 제공하는 새 스킬 템플릿을 정의합니다.",
-			"nameLabel": "이름",
-			"namePlaceholder": "my-skill-name",
-			"nameHint": "소문자, 숫자 및 하이픈만 사용(1-64자)",
-			"descriptionLabel": "설명",
-			"descriptionPlaceholder": "이 스킬을 언제 사용해야 하는지 설명하세요...",
-			"descriptionHint": "이 스킬이 무엇을 하는지, 에이전트가 언제 적용해야 하는지 설명하세요(1-1024자)",
-			"sourceLabel": "위치",
-			"sourceHint": "이 스킬을 전역으로 사용할지 이 프로젝트에만 사용할지 선택하세요",
-			"modeLabel": "모드 (선택사항)",
-			"modePlaceholder": "모든 모드",
-			"modeHint": "이 스킬을 특정 모드로 제한",
-			"modeAny": "모든 모드",
-			"create": "만들기",
-			"cancel": "취소"
-		},
-		"source": {
-			"global": "전역 (모든 프로젝트에서 사용 가능)",
-			"project": "프로젝트 (이 작업공간만)"
-		},
-		"validation": {
-			"nameRequired": "이름은 필수입니다",
-			"nameTooLong": "이름은 64자 이하여야 합니다",
-			"nameInvalid": "이름은 1-64자의 소문자, 숫자 또는 하이픈이어야 합니다",
-			"descriptionRequired": "설명은 필수입니다",
-			"descriptionTooLong": "설명은 1024자 이하여야 합니다"
-		}
 	}
 }

+ 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…"

+ 1 - 47
webview-ui/src/i18n/locales/nl/settings.json

@@ -41,8 +41,7 @@
 		"ui": "UI",
 		"experimental": "Experimenteel",
 		"language": "Taal",
-		"about": "Over Roo Code",
-		"skills": "Skills"
+		"about": "Over Roo Code"
 	},
 	"about": {
 		"bugReport": {
@@ -994,50 +993,5 @@
 			"label": "Vereist {{primaryMod}}+Enter om berichten te versturen",
 			"description": "Wanneer ingeschakeld, moet je {{primaryMod}}+Enter indrukken om berichten te versturen in plaats van alleen Enter"
 		}
-	},
-	"skills": {
-		"description": "Beheer skills die contextuele instructies aan de agent verstrekken. Skills worden automatisch toegepast wanneer ze relevant zijn voor uw taken. <DocsLink>Meer informatie</DocsLink>",
-		"projectSkills": "Projectskills",
-		"globalSkills": "Globale Skills",
-		"noProjectSkills": "Geen projectskills geconfigureerd. Maak er een om projectspecifieke agentmogelijkheden toe te voegen.",
-		"noGlobalSkills": "Geen globale skills geconfigureerd. Maak er een om agentmogelijkheden toe te voegen die beschikbaar zijn in alle projecten.",
-		"addSkill": "Skill toevoegen",
-		"editSkill": "Skill bewerken",
-		"deleteSkill": "Skill verwijderen",
-		"deleteDialog": {
-			"title": "Skill verwijderen",
-			"description": "Weet u zeker dat u de skill \"{{name}}\" wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.",
-			"confirm": "Verwijderen",
-			"cancel": "Annuleren"
-		},
-		"createDialog": {
-			"title": "Nieuwe Skill maken",
-			"description": "Definieer een nieuwe skillsjabloon die contextuele instructies aan de agent verstrekt.",
-			"nameLabel": "Naam",
-			"namePlaceholder": "mijn-skill-naam",
-			"nameHint": "Alleen kleine letters, cijfers en streepjes (1-64 tekens)",
-			"descriptionLabel": "Beschrijving",
-			"descriptionPlaceholder": "Beschrijf wanneer deze skill moet worden gebruikt...",
-			"descriptionHint": "Leg uit wat deze skill doet en wanneer de agent deze moet toepassen (1-1024 tekens)",
-			"sourceLabel": "Locatie",
-			"sourceHint": "Kies of deze skill globaal beschikbaar is of alleen in dit project",
-			"modeLabel": "Modus (optioneel)",
-			"modePlaceholder": "Elke modus",
-			"modeHint": "Beperk deze skill tot een specifieke modus",
-			"modeAny": "Elke modus",
-			"create": "Maken",
-			"cancel": "Annuleren"
-		},
-		"source": {
-			"global": "Globaal (beschikbaar in alle projecten)",
-			"project": "Project (alleen deze workspace)"
-		},
-		"validation": {
-			"nameRequired": "Naam is verplicht",
-			"nameTooLong": "Naam moet maximaal 64 tekens zijn",
-			"nameInvalid": "Naam moet 1-64 kleine letters, cijfers of streepjes zijn",
-			"descriptionRequired": "Beschrijving is verplicht",
-			"descriptionTooLong": "Beschrijving moet maximaal 1024 tekens zijn"
-		}
 	}
 }

Некоторые файлы не были показаны из-за большого количества измененных файлов