Forráskód Böngészése

refactor: remove built-in skills and built-in skills mechanism (#11414)

Hannes Rudolph 2 napja
szülő
commit
b759b92f01
38 módosított fájl, 91 hozzáadás és 1495 törlés
  1. 2 2
      packages/types/src/skills.ts
  2. 1 1
      packages/types/src/vscode-extension-host.ts
  3. 1 1
      src/core/auto-approval/index.ts
  4. 1 4
      src/core/prompts/sections/skills.ts
  5. 8 8
      src/core/tools/__tests__/skillTool.spec.ts
  6. 0 20
      src/core/webview/__tests__/skillsMessageHandler.spec.ts
  7. 7 30
      src/core/webview/skillsMessageHandler.ts
  8. 1 3
      src/i18n/locales/ca/skills.json
  9. 1 3
      src/i18n/locales/de/skills.json
  10. 1 3
      src/i18n/locales/en/skills.json
  11. 1 3
      src/i18n/locales/es/skills.json
  12. 1 3
      src/i18n/locales/fr/skills.json
  13. 1 3
      src/i18n/locales/hi/skills.json
  14. 1 3
      src/i18n/locales/id/skills.json
  15. 1 3
      src/i18n/locales/it/skills.json
  16. 1 3
      src/i18n/locales/ja/skills.json
  17. 1 3
      src/i18n/locales/ko/skills.json
  18. 1 3
      src/i18n/locales/nl/skills.json
  19. 1 3
      src/i18n/locales/pl/skills.json
  20. 1 3
      src/i18n/locales/pt-BR/skills.json
  21. 1 3
      src/i18n/locales/ru/skills.json
  22. 1 3
      src/i18n/locales/tr/skills.json
  23. 1 3
      src/i18n/locales/vi/skills.json
  24. 1 3
      src/i18n/locales/zh-CN/skills.json
  25. 1 3
      src/i18n/locales/zh-TW/skills.json
  26. 0 2
      src/package.json
  27. 8 24
      src/services/skills/SkillsManager.ts
  28. 0 8
      src/services/skills/__tests__/SkillsManager.spec.ts
  29. 0 175
      src/services/skills/__tests__/generate-built-in-skills.spec.ts
  30. 0 421
      src/services/skills/built-in-skills.ts
  31. 0 304
      src/services/skills/built-in/create-mcp-server/SKILL.md
  32. 0 57
      src/services/skills/built-in/create-mode/SKILL.md
  33. 0 300
      src/services/skills/generate-built-in-skills.ts
  34. 2 2
      src/shared/skills.ts
  35. 25 36
      webview-ui/src/components/settings/SkillItem.tsx
  36. 11 17
      webview-ui/src/components/settings/SkillsSettings.tsx
  37. 4 28
      webview-ui/src/components/settings/__tests__/SkillItem.spec.tsx
  38. 3 1
      webview-ui/src/components/ui/hooks/useRouterModels.ts

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

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

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

@@ -631,7 +631,7 @@ export interface WebviewMessage {
 	modeConfig?: ModeConfig
 	timeout?: number
 	payload?: WebViewMessagePayload
-	source?: "global" | "project" | "built-in"
+	source?: "global" | "project"
 	skillName?: string // For skill operations (createSkill, deleteSkill, moveSkill, openSkillFile)
 	/** @deprecated Use skillModeSlugs instead */
 	skillMode?: string // For skill operations (current mode restriction)

+ 1 - 1
src/core/auto-approval/index.ts

@@ -151,7 +151,7 @@ export async function checkAutoApproval({
 			return { decision: "approve" }
 		}
 
-		// The skill tool only loads pre-defined instructions from built-in, global, or project skills.
+		// The skill tool only loads pre-defined instructions from 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") {

+ 1 - 4
src/core/prompts/sections/skills.ts

@@ -33,10 +33,7 @@ export async function getSkillsSection(
 		.map((skill) => {
 			const name = escapeXml(skill.name)
 			const description = escapeXml(skill.description)
-			// 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>` : ""
+			const locationLine = `\n    <location>${escapeXml(skill.path)}</location>`
 			return `  <skill>\n    <name>${name}</name>\n    <description>${description}</description>${locationLine}\n  </skill>`
 		})
 		.join("\n")

+ 8 - 8
src/core/tools/__tests__/skillTool.spec.ts

@@ -99,7 +99,7 @@ describe("skillTool", () => {
 		)
 	})
 
-	it("should successfully load built-in skill", async () => {
+	it("should successfully load a global skill", async () => {
 		const block: ToolUse<"skill"> = {
 			type: "tool_use" as const,
 			name: "skill" as const,
@@ -113,7 +113,7 @@ describe("skillTool", () => {
 		const mockSkillContent = {
 			name: "create-mcp-server",
 			description: "Instructions for creating MCP servers",
-			source: "built-in",
+			source: "global",
 			instructions: "Step 1: Create the server...",
 		}
 
@@ -127,7 +127,7 @@ describe("skillTool", () => {
 				tool: "skill",
 				skill: "create-mcp-server",
 				args: undefined,
-				source: "built-in",
+				source: "global",
 				description: "Instructions for creating MCP servers",
 			}),
 		)
@@ -135,7 +135,7 @@ describe("skillTool", () => {
 		expect(mockCallbacks.pushToolResult).toHaveBeenCalledWith(
 			`Skill: create-mcp-server
 Description: Instructions for creating MCP servers
-Source: built-in
+Source: global
 
 --- Skill Instructions ---
 
@@ -158,7 +158,7 @@ Step 1: Create the server...`,
 		const mockSkillContent = {
 			name: "create-mcp-server",
 			description: "Instructions for creating MCP servers",
-			source: "built-in",
+			source: "global",
 			instructions: "Step 1: Create the server...",
 		}
 
@@ -170,7 +170,7 @@ Step 1: Create the server...`,
 			`Skill: create-mcp-server
 Description: Instructions for creating MCP servers
 Provided arguments: weather API server
-Source: built-in
+Source: global
 
 --- Skill Instructions ---
 
@@ -192,7 +192,7 @@ Step 1: Create the server...`,
 		mockSkillsManager.getSkillContent.mockResolvedValue({
 			name: "create-mcp-server",
 			description: "Test",
-			source: "built-in",
+			source: "global",
 			instructions: "Test instructions",
 		})
 
@@ -264,7 +264,7 @@ Step 1: Create the server...`,
 		const mockSkillContent = {
 			name: "create-mcp-server",
 			description: "Test",
-			source: "built-in",
+			source: "global",
 			instructions: "Test instructions",
 		}
 

+ 0 - 20
src/core/webview/__tests__/skillsMessageHandler.spec.ts

@@ -28,7 +28,6 @@ vi.mock("../../../i18n", () => ({
 			"skills:errors.missing_delete_fields": "Missing required fields: skillName or source",
 			"skills:errors.missing_move_fields": "Missing required fields: skillName or source",
 			"skills:errors.skill_not_found": `Skill "${params?.name}" not found`,
-			"skills:errors.cannot_modify_builtin": "Built-in skills cannot be created, deleted, or moved",
 		}
 		return translations[key] || key
 	},
@@ -333,25 +332,6 @@ describe("skillsMessageHandler", () => {
 				"Failed to move skill: Skills manager not available",
 			)
 		})
-
-		it("returns undefined when trying to move a built-in skill", async () => {
-			const provider = createMockProvider(true)
-
-			const result = await handleMoveSkill(provider, {
-				type: "moveSkill",
-				skillName: "test-skill",
-				source: "built-in",
-				newSkillMode: "code",
-			} as WebviewMessage)
-
-			expect(result).toBeUndefined()
-			expect(mockLog).toHaveBeenCalledWith(
-				"Error moving skill: Built-in skills cannot be created, deleted, or moved",
-			)
-			expect(vscode.window.showErrorMessage).toHaveBeenCalledWith(
-				"Failed to move skill: Built-in skills cannot be created, deleted, or moved",
-			)
-		})
 	})
 
 	describe("handleOpenSkillFile", () => {

+ 7 - 30
src/core/webview/skillsMessageHandler.ts

@@ -6,6 +6,8 @@ import type { ClineProvider } from "./ClineProvider"
 import { openFile } from "../../integrations/misc/open-file"
 import { t } from "../../i18n"
 
+type SkillSource = SkillMetadata["source"]
+
 /**
  * Handles the requestSkills message - returns all skills metadata
  */
@@ -36,7 +38,7 @@ export async function handleCreateSkill(
 ): Promise<SkillMetadata[] | undefined> {
 	try {
 		const skillName = message.skillName
-		const source = message.source
+		const source = message.source as SkillSource
 		const skillDescription = message.skillDescription
 		// Support new modeSlugs array or fall back to legacy skillMode
 		const modeSlugs = message.skillModeSlugs ?? (message.skillMode ? [message.skillMode] : undefined)
@@ -45,11 +47,6 @@ export async function handleCreateSkill(
 			throw new Error(t("skills:errors.missing_create_fields"))
 		}
 
-		// Built-in skills cannot be created
-		if (source === "built-in") {
-			throw new Error(t("skills:errors.cannot_modify_builtin"))
-		}
-
 		const skillsManager = provider.getSkillsManager()
 		if (!skillsManager) {
 			throw new Error(t("skills:errors.manager_unavailable"))
@@ -81,7 +78,7 @@ export async function handleDeleteSkill(
 ): Promise<SkillMetadata[] | undefined> {
 	try {
 		const skillName = message.skillName
-		const source = message.source
+		const source = message.source as SkillSource
 		// Support new skillModeSlugs array or fall back to legacy skillMode
 		const skillMode = message.skillModeSlugs?.[0] ?? message.skillMode
 
@@ -89,11 +86,6 @@ export async function handleDeleteSkill(
 			throw new Error(t("skills:errors.missing_delete_fields"))
 		}
 
-		// Built-in skills cannot be deleted
-		if (source === "built-in") {
-			throw new Error(t("skills:errors.cannot_modify_builtin"))
-		}
-
 		const skillsManager = provider.getSkillsManager()
 		if (!skillsManager) {
 			throw new Error(t("skills:errors.manager_unavailable"))
@@ -122,7 +114,7 @@ export async function handleMoveSkill(
 ): Promise<SkillMetadata[] | undefined> {
 	try {
 		const skillName = message.skillName
-		const source = message.source
+		const source = message.source as SkillSource
 		const currentMode = message.skillMode
 		const newMode = message.newSkillMode
 
@@ -130,11 +122,6 @@ export async function handleMoveSkill(
 			throw new Error(t("skills:errors.missing_move_fields"))
 		}
 
-		// Built-in skills cannot be moved
-		if (source === "built-in") {
-			throw new Error(t("skills:errors.cannot_modify_builtin"))
-		}
-
 		const skillsManager = provider.getSkillsManager()
 		if (!skillsManager) {
 			throw new Error(t("skills:errors.manager_unavailable"))
@@ -163,18 +150,13 @@ export async function handleUpdateSkillModes(
 ): Promise<SkillMetadata[] | undefined> {
 	try {
 		const skillName = message.skillName
-		const source = message.source
+		const source = message.source as SkillSource
 		const newModeSlugs = message.newSkillModeSlugs
 
 		if (!skillName || !source) {
 			throw new Error(t("skills:errors.missing_update_modes_fields"))
 		}
 
-		// Built-in skills cannot be modified
-		if (source === "built-in") {
-			throw new Error(t("skills:errors.cannot_modify_builtin"))
-		}
-
 		const skillsManager = provider.getSkillsManager()
 		if (!skillsManager) {
 			throw new Error(t("skills:errors.manager_unavailable"))
@@ -200,17 +182,12 @@ export async function handleUpdateSkillModes(
 export async function handleOpenSkillFile(provider: ClineProvider, message: WebviewMessage): Promise<void> {
 	try {
 		const skillName = message.skillName
-		const source = message.source
+		const source = message.source as SkillSource
 
 		if (!skillName || !source) {
 			throw new Error(t("skills:errors.missing_delete_fields"))
 		}
 
-		// Built-in skills cannot be opened as files (they have no file path)
-		if (source === "built-in") {
-			throw new Error(t("skills:errors.cannot_open_builtin"))
-		}
-
 		const skillsManager = provider.getSkillsManager()
 		if (!skillsManager) {
 			throw new Error(t("skills:errors.manager_unavailable"))

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

@@ -11,8 +11,6 @@
 		"missing_update_modes_fields": "Falten camps obligatoris: skillName o source",
 		"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}}\"",
-		"cannot_modify_builtin": "Les habilitats integrades no es poden crear ni eliminar",
-		"cannot_open_builtin": "Les habilitats integrades no es poden obrir com a fitxers"
+		"skill_not_found": "No s'ha trobat l'habilitat \"{{name}}\""
 	}
 }

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

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

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

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

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

@@ -11,8 +11,6 @@
 		"missing_update_modes_fields": "Faltan campos obligatorios: skillName o source",
 		"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}}\"",
-		"cannot_modify_builtin": "Las habilidades integradas no se pueden crear ni eliminar",
-		"cannot_open_builtin": "Las habilidades integradas no se pueden abrir como archivos"
+		"skill_not_found": "No se encontró la habilidad \"{{name}}\""
 	}
 }

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

@@ -11,8 +11,6 @@
 		"missing_update_modes_fields": "Champs obligatoires manquants : skillName ou source",
 		"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",
-		"cannot_modify_builtin": "Les compétences intégrées ne peuvent pas être créées ou supprimées",
-		"cannot_open_builtin": "Les compétences intégrées ne peuvent pas être ouvertes en tant que fichiers"
+		"skill_not_found": "Compétence \"{{name}}\" introuvable"
 	}
 }

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -11,8 +11,6 @@
 		"missing_update_modes_fields": "Brakuje wymaganych pól: skillName lub source",
 		"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}}\"",
-		"cannot_modify_builtin": "Wbudowane umiejętności nie mogą być tworzone ani usuwane",
-		"cannot_open_builtin": "Wbudowane umiejętności nie mogą być otwierane jako pliki"
+		"skill_not_found": "Nie znaleziono umiejętności \"{{name}}\""
 	}
 }

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

@@ -11,8 +11,6 @@
 		"missing_update_modes_fields": "Campos obrigatórios ausentes: skillName ou source",
 		"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",
-		"cannot_modify_builtin": "Habilidades integradas não podem ser criadas ou excluídas",
-		"cannot_open_builtin": "Habilidades integradas não podem ser abertas como arquivos"
+		"skill_not_found": "Habilidade \"{{name}}\" não encontrada"
 	}
 }

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

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

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

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

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

@@ -11,8 +11,6 @@
 		"missing_update_modes_fields": "Thiếu các trường bắt buộc: skillName hoặc source",
 		"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}}\"",
-		"cannot_modify_builtin": "Không thể tạo hoặc xóa kỹ năng tích hợp sẵn",
-		"cannot_open_builtin": "Không thể mở kỹ năng tích hợp sẵn dưới dạng tệp"
+		"skill_not_found": "Không tìm thấy kỹ năng \"{{name}}\""
 	}
 }

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

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

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

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

+ 0 - 2
src/package.json

@@ -439,8 +439,6 @@
 		"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",

+ 8 - 24
src/services/skills/SkillsManager.ts

@@ -15,7 +15,6 @@ import {
 	SKILL_NAME_MAX_LENGTH,
 } from "@roo-code/types"
 import { t } from "../../i18n"
-import { getBuiltInSkills, getBuiltInSkillContent } from "./built-in-skills"
 
 // Re-export for convenience
 export type { SkillMetadata, SkillContent }
@@ -179,19 +178,13 @@ export class SkillsManager {
 
 	/**
 	 * Get skills available for the current mode.
-	 * Resolves overrides: project > global > built-in, mode-specific > generic.
+	 * Resolves overrides: project > global, 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()) {
 			// Check if skill is available in current mode:
 			// - modeSlugs undefined or empty = available in all modes ("Any mode")
@@ -232,14 +225,13 @@ export class SkillsManager {
 
 	/**
 	 * Determine if newSkill should override existingSkill based on priority rules.
-	 * Priority: project > global > built-in, mode-specific > generic
+	 * Priority: project > global, mode-specific > generic
 	 */
 	private shouldOverrideSkill(existing: SkillMetadata, newSkill: SkillMetadata): boolean {
-		// Define source priority: project > global > built-in
+		// Define source priority: project > global
 		const sourcePriority: Record<string, number> = {
-			project: 3,
-			global: 2,
-			"built-in": 1,
+			project: 2,
+			global: 1,
 		}
 
 		const existingPriority = sourcePriority[existing.source] ?? 0
@@ -275,21 +267,13 @@ export class SkillsManager {
 			const modeSkills = this.getSkillsForMode(currentMode)
 			skill = modeSkills.find((s) => s.name === name)
 		} else {
-			// Fall back to any skill with this name (check discovered skills first, then built-in)
+			// Fall back to any skill with this name
 			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
+		// Read skill content from disk
 		const fileContent = await fs.readFile(skill.path, "utf-8")
 		const { content: body } = matter(fileContent)
 
@@ -599,7 +583,7 @@ Add your skill instructions here.
 		const modesList = await this.getAvailableModes()
 
 		// Priority rules for skills with the same name:
-		// 1. Source level: project > global > built-in (handled by shouldOverrideSkill in getSkillsForMode)
+		// 1. Source level: project > global (handled by shouldOverrideSkill in getSkillsForMode)
 		// 2. Within the same source level: later-processed directories override earlier ones
 		//    (via Map.set replacement during discovery - same source+mode+name key gets replaced)
 		//

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

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

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

@@ -1,175 +0,0 @@
-/**
- * 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()
-	})
-})

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

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

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

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

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

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

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

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

+ 2 - 2
src/shared/skills.ts

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

+ 25 - 36
webview-ui/src/components/settings/SkillItem.tsx

@@ -53,9 +53,6 @@ export const SkillItem: React.FC<SkillItemProps> = ({ skill, onEdit, onDelete })
 		[skill.name, skill.source, skill.mode],
 	)
 
-	// Built-in skills cannot change mode
-	const isBuiltIn = skill.source === "built-in"
-
 	return (
 		<div className="px-4 py-2 text-sm flex items-center group hover:bg-vscode-list-hoverBackground">
 			{/* Skill name and description */}
@@ -70,27 +67,21 @@ export const SkillItem: React.FC<SkillItemProps> = ({ skill, onEdit, onDelete })
 
 			{/* Mode dropdown */}
 			<div className="ml-2 shrink-0">
-				{isBuiltIn ? (
-					<span className="px-1.5 py-0.5 text-xs rounded bg-vscode-badge-background text-vscode-badge-foreground">
-						{skill.mode || t("settings:skills.modeAny")}
-					</span>
-				) : (
-					<StandardTooltip content={t("settings:skills.changeMode")}>
-						<Select value={currentModeValue} onValueChange={handleModeChange}>
-							<SelectTrigger className="h-6 w-auto min-w-[80px] max-w-[120px] text-xs px-2 py-0.5 border-none bg-vscode-badge-background text-vscode-badge-foreground hover:bg-vscode-button-hoverBackground">
-								<SelectValue />
-							</SelectTrigger>
-							<SelectContent>
-								<SelectItem value={MODE_ANY}>{t("settings:skills.modeAny")}</SelectItem>
-								{availableModes.map((m) => (
-									<SelectItem key={m.slug} value={m.slug}>
-										{m.name}
-									</SelectItem>
-								))}
-							</SelectContent>
-						</Select>
-					</StandardTooltip>
-				)}
+				<StandardTooltip content={t("settings:skills.changeMode")}>
+					<Select value={currentModeValue} onValueChange={handleModeChange}>
+						<SelectTrigger className="h-6 w-auto min-w-[80px] max-w-[120px] text-xs px-2 py-0.5 border-none bg-vscode-badge-background text-vscode-badge-foreground hover:bg-vscode-button-hoverBackground">
+							<SelectValue />
+						</SelectTrigger>
+						<SelectContent>
+							<SelectItem value={MODE_ANY}>{t("settings:skills.modeAny")}</SelectItem>
+							{availableModes.map((m) => (
+								<SelectItem key={m.slug} value={m.slug}>
+									{m.name}
+								</SelectItem>
+							))}
+						</SelectContent>
+					</Select>
+				</StandardTooltip>
 			</div>
 
 			{/* Action buttons */}
@@ -106,18 +97,16 @@ export const SkillItem: React.FC<SkillItemProps> = ({ skill, onEdit, onDelete })
 					</Button>
 				</StandardTooltip>
 
-				{!isBuiltIn && (
-					<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>
-				)}
+				<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>
 	)

+ 11 - 17
webview-ui/src/components/settings/SkillsSettings.tsx

@@ -164,8 +164,6 @@ export const SkillsSettings: React.FC = () => {
 	// Render a single skill item
 	const renderSkillItem = useCallback(
 		(skill: SkillMetadata) => {
-			const isBuiltIn = skill.source === "built-in"
-
 			return (
 				<div
 					key={`${skill.source}-${skill.name}-${skill.modeSlugs?.join(",") || "any"}`}
@@ -186,14 +184,12 @@ export const SkillsSettings: React.FC = () => {
 
 						{/* Actions */}
 						<div className="flex items-center gap-1 px-0 ml-0 min-[400px]:ml-0 min-[400px]:mt-4 flex-shrink-0">
-							{/* Mode settings button (gear icon) - only for non-built-in skills */}
-							{!isBuiltIn && (
-								<StandardTooltip content={t("settings:skills.configureModes")}>
-									<Button variant="ghost" size="icon" onClick={() => handleOpenModeDialog(skill)}>
-										<Settings className="size-4" />
-									</Button>
-								</StandardTooltip>
-							)}
+							{/* Mode settings button (gear icon) */}
+							<StandardTooltip content={t("settings:skills.configureModes")}>
+								<Button variant="ghost" size="icon" onClick={() => handleOpenModeDialog(skill)}>
+									<Settings className="size-4" />
+								</Button>
+							</StandardTooltip>
 
 							<StandardTooltip content={t("settings:skills.editSkill")}>
 								<Button variant="ghost" size="icon" onClick={() => handleEditClick(skill)}>
@@ -201,13 +197,11 @@ export const SkillsSettings: React.FC = () => {
 								</Button>
 							</StandardTooltip>
 
-							{!isBuiltIn && (
-								<StandardTooltip content={t("settings:skills.deleteSkill")}>
-									<Button variant="ghost" size="icon" onClick={() => handleDeleteClick(skill)}>
-										<Trash2 className="text-destructive" />
-									</Button>
-								</StandardTooltip>
-							)}
+							<StandardTooltip content={t("settings:skills.deleteSkill")}>
+								<Button variant="ghost" size="icon" onClick={() => handleDeleteClick(skill)}>
+									<Trash2 className="text-destructive" />
+								</Button>
+							</StandardTooltip>
 						</div>
 					</div>
 				</div>

+ 4 - 28
webview-ui/src/components/settings/__tests__/SkillItem.spec.tsx

@@ -92,13 +92,6 @@ const mockSkillWithMode: SkillMetadata = {
 	mode: "architect",
 }
 
-const mockBuiltInSkill: SkillMetadata = {
-	name: "built-in-skill",
-	description: "A built-in skill",
-	path: "<built-in:test>",
-	source: "built-in",
-}
-
 describe("SkillItem", () => {
 	const mockOnEdit = vi.fn()
 	const mockOnDelete = vi.fn()
@@ -119,7 +112,7 @@ describe("SkillItem", () => {
 		expect(screen.getByText("A test skill description")).toBeInTheDocument()
 	})
 
-	it("renders mode dropdown for non-built-in skills", () => {
+	it("renders mode dropdown", () => {
 		render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
 
 		expect(screen.getByTestId("select")).toBeInTheDocument()
@@ -139,15 +132,6 @@ describe("SkillItem", () => {
 		expect(select).toHaveAttribute("data-value", "__any__")
 	})
 
-	it("does not render mode dropdown for built-in skills", () => {
-		render(<SkillItem skill={mockBuiltInSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
-
-		// Should not have a select element
-		expect(screen.queryByTestId("select")).not.toBeInTheDocument()
-		// Should have a static badge instead
-		expect(screen.getByText("Any mode")).toBeInTheDocument()
-	})
-
 	it("calls onEdit when edit button is clicked", () => {
 		render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
 
@@ -158,24 +142,16 @@ describe("SkillItem", () => {
 		expect(mockOnEdit).toHaveBeenCalledTimes(1)
 	})
 
-	it("calls onDelete when delete button is clicked for non-built-in skills", () => {
+	it("calls onDelete when delete button is clicked", () => {
 		render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
 
 		const buttons = screen.getAllByTestId("button")
-		// Find the delete button (second one for non-built-in)
+		// Find the delete button (second one)
 		fireEvent.click(buttons[1])
 
 		expect(mockOnDelete).toHaveBeenCalledTimes(1)
 	})
 
-	it("does not render delete button for built-in skills", () => {
-		render(<SkillItem skill={mockBuiltInSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
-
-		// Should only have 1 button (edit) for built-in skills
-		const buttons = screen.getAllByTestId("button")
-		expect(buttons).toHaveLength(1)
-	})
-
 	it("calls onEdit when clicking on skill name area", () => {
 		render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
 
@@ -239,7 +215,7 @@ describe("SkillItem", () => {
 		expect(itemDiv).toHaveClass("hover:bg-vscode-list-hoverBackground")
 	})
 
-	it("renders edit and delete buttons for non-built-in skills", () => {
+	it("renders edit and delete buttons", () => {
 		render(<SkillItem skill={mockSkill} onEdit={mockOnEdit} onDelete={mockOnDelete} />)
 
 		const buttons = screen.getAllByTestId("button")

+ 3 - 1
webview-ui/src/components/ui/hooks/useRouterModels.ts

@@ -12,7 +12,9 @@ type UseRouterModelsOptions = {
 const getRouterModels = async (provider?: string) =>
 	new Promise<RouterModels>((resolve, reject) => {
 		const cleanup = () => {
-			window.removeEventListener("message", handler)
+			if (typeof window !== "undefined") {
+				window.removeEventListener("message", handler)
+			}
 		}
 
 		const timeout = setTimeout(() => {