From 7775d2ab65f2b344a70e306c82a30ef064412979 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 20 Jan 2026 18:49:58 +0000 Subject: [PATCH] feat: add code-simplify skill and /simplify slash command - Add built-in code-simplify skill based on Claude code-simplifier plugin - Add /simplify slash command that triggers the skill - Update SkillMetadata to support built-in skills alongside global and project skills - Implement built-in skills system in SkillsManager with proper override priority (project > global > built-in) - Add comprehensive test coverage for new functionality --- .../__tests__/built-in-commands.spec.ts | 12 +-- src/services/command/built-in-commands.ts | 17 ++++ src/services/skills/SkillsManager.ts | 29 +++++- .../skills/__tests__/SkillsManager.spec.ts | 24 ++--- src/services/skills/built-in-skills.ts | 96 +++++++++++++++++++ src/shared/skills.ts | 4 +- 6 files changed, 157 insertions(+), 25 deletions(-) create mode 100644 src/services/skills/built-in-skills.ts diff --git a/src/services/command/__tests__/built-in-commands.spec.ts b/src/services/command/__tests__/built-in-commands.spec.ts index ecb2bdb0fb8..5c503488e76 100644 --- a/src/services/command/__tests__/built-in-commands.spec.ts +++ b/src/services/command/__tests__/built-in-commands.spec.ts @@ -5,8 +5,8 @@ describe("Built-in Commands", () => { it("should return all built-in commands", async () => { const commands = await getBuiltInCommands() - expect(commands).toHaveLength(1) - expect(commands.map((cmd) => cmd.name)).toEqual(expect.arrayContaining(["init"])) + expect(commands).toHaveLength(2) + expect(commands.map((cmd) => cmd.name)).toEqual(expect.arrayContaining(["init", "simplify"])) // Verify all commands have required properties commands.forEach((command) => { @@ -63,10 +63,10 @@ describe("Built-in Commands", () => { it("should return all built-in command names", async () => { const names = await getBuiltInCommandNames() - expect(names).toHaveLength(1) - expect(names).toEqual(expect.arrayContaining(["init"])) - // Order doesn't matter since it's based on filesystem order - expect(names.sort()).toEqual(["init"]) + expect(names).toHaveLength(2) + expect(names).toEqual(expect.arrayContaining(["init", "simplify"])) + // Order doesn't matter since it's based on object key order + expect(names.sort()).toEqual(["init", "simplify"]) }) it("should return array of strings", async () => { diff --git a/src/services/command/built-in-commands.ts b/src/services/command/built-in-commands.ts index db113c48959..0c9a8e9e7d8 100644 --- a/src/services/command/built-in-commands.ts +++ b/src/services/command/built-in-commands.ts @@ -8,6 +8,23 @@ interface BuiltInCommandDefinition { } const BUILT_IN_COMMANDS: Record = { + simplify: { + name: "simplify", + description: "Simplify and refine code for clarity, consistency, and maintainability", + argumentHint: "[optional-context]", + content: `You are now using the code-simplify skill to analyze and refine code. + +Your task is to simplify recently modified code while preserving all functionality. Follow the code-simplify skill instructions to enhance code clarity, consistency, and maintainability. + +Focus on: +- Reducing unnecessary complexity +- Applying project-specific best practices +- Improving readability through clear naming +- Avoiding nested ternaries and overly compact code +- Maintaining explicit, understandable code + +Remember: Only refine code that has been recently modified unless explicitly instructed otherwise. Never change functionality - only improve how the code accomplishes its goals.`, + }, init: { name: "init", description: "Analyze codebase and create concise AGENTS.md files for AI assistants", diff --git a/src/services/skills/SkillsManager.ts b/src/services/skills/SkillsManager.ts index 59b50cf1713..989ee43d2ed 100644 --- a/src/services/skills/SkillsManager.ts +++ b/src/services/skills/SkillsManager.ts @@ -8,6 +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 { getBuiltInSkills, getBuiltInSkill } from "./built-in-skills" // Re-export for convenience export type { SkillMetadata, SkillContent } @@ -28,7 +29,7 @@ export class SkillsManager { } /** - * Discover all skills from global and project directories. + * Discover all skills from built-in, global and project directories. * Supports both generic skills (skills/) and mode-specific skills (skills-{mode}/). * Also supports symlinks: * - .roo/skills can be a symlink to a directory containing skill subdirectories @@ -36,6 +37,14 @@ export class SkillsManager { */ async discoverSkills(): Promise { this.skills.clear() + + // Add built-in skills first (lowest priority) + const builtInSkills = getBuiltInSkills() + for (const skill of builtInSkills) { + const skillKey = this.getSkillKey(skill.name, skill.source, skill.mode) + this.skills.set(skillKey, skill) + } + const skillsDirs = await this.getSkillsDirectories() for (const { dir, source, mode } of skillsDirs) { @@ -194,12 +203,16 @@ 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 + // Project always overrides global and built-in + if (newSkill.source === "project" && existing.source !== "project") return true + if (newSkill.source !== "project" && existing.source === "project") return false + + // Global always overrides built-in + if (newSkill.source === "global" && existing.source === "built-in") return true + if (newSkill.source === "built-in" && existing.source === "global") return false // Same source: mode-specific overrides generic if (newSkill.mode && !existing.mode) return true @@ -230,6 +243,12 @@ export class SkillsManager { if (!skill) return null + // Handle built-in skills + if (skill.source === "built-in") { + const builtInSkillContent = getBuiltInSkill(skill.name) + return builtInSkillContent || null + } + const fileContent = await fs.readFile(skill.path, "utf-8") const { content: body } = matter(fileContent) diff --git a/src/services/skills/__tests__/SkillsManager.spec.ts b/src/services/skills/__tests__/SkillsManager.spec.ts index 4b6549108bb..a5699b99e37 100644 --- a/src/services/skills/__tests__/SkillsManager.spec.ts +++ b/src/services/skills/__tests__/SkillsManager.spec.ts @@ -142,7 +142,7 @@ Instructions here...` await skillsManager.discoverSkills() - const skills = skillsManager.getAllSkills() + const skills = skillsManager.getAllSkills().filter((s) => s.source !== "built-in") expect(skills).toHaveLength(1) expect(skills[0].name).toBe("pdf-processing") expect(skills[0].description).toBe("Extract text and tables from PDF files") @@ -193,7 +193,7 @@ Instructions here...` await skillsManager.discoverSkills() - const skills = skillsManager.getAllSkills() + const skills = skillsManager.getAllSkills().filter((s) => s.source !== "built-in") expect(skills).toHaveLength(1) expect(skills[0].name).toBe("code-review") expect(skills[0].source).toBe("project") @@ -243,7 +243,7 @@ Instructions here...` await skillsManager.discoverSkills() - const skills = skillsManager.getAllSkills() + const skills = skillsManager.getAllSkills().filter((s) => s.source !== "built-in") expect(skills).toHaveLength(1) expect(skills[0].name).toBe("refactoring") expect(skills[0].mode).toBe("code") @@ -290,7 +290,7 @@ name: invalid-skill await skillsManager.discoverSkills() - const skills = skillsManager.getAllSkills() + const skills = skillsManager.getAllSkills().filter((s) => s.source !== "built-in") expect(skills).toHaveLength(0) }) @@ -336,7 +336,7 @@ description: Name doesn't match directory await skillsManager.discoverSkills() - const skills = skillsManager.getAllSkills() + const skills = skillsManager.getAllSkills().filter((s) => s.source !== "built-in") expect(skills).toHaveLength(0) }) @@ -375,7 +375,7 @@ description: Invalid name format }) await skillsManager.discoverSkills() - const skills = skillsManager.getAllSkills() + const skills = skillsManager.getAllSkills().filter((s) => s.source !== "built-in") expect(skills).toHaveLength(0) }) @@ -402,7 +402,7 @@ description: Too long name # Long Name Skill`) await skillsManager.discoverSkills() - const skills = skillsManager.getAllSkills() + const skills = skillsManager.getAllSkills().filter((s) => s.source !== "built-in") expect(skills).toHaveLength(0) }) @@ -426,7 +426,7 @@ description: " " # Empty Description`) await skillsManager.discoverSkills() - const skills = skillsManager.getAllSkills() + const skills = skillsManager.getAllSkills().filter((s) => s.source !== "built-in") expect(skills).toHaveLength(0) }) @@ -451,7 +451,7 @@ description: ${longDescription} # Too Long Description`) await skillsManager.discoverSkills() - const skills = skillsManager.getAllSkills() + const skills = skillsManager.getAllSkills().filter((s) => s.source !== "built-in") expect(skills).toHaveLength(0) }) @@ -506,7 +506,7 @@ Instructions here...` await skillsManager.discoverSkills() - const skills = skillsManager.getAllSkills() + const skills = skillsManager.getAllSkills().filter((s) => s.source !== "built-in") expect(skills).toHaveLength(1) expect(skills[0].name).toBe("shared-skill") expect(skills[0].source).toBe("global") @@ -559,7 +559,7 @@ Instructions here...` await skillsManager.discoverSkills() - const skills = skillsManager.getAllSkills() + const skills = skillsManager.getAllSkills().filter((s) => s.source !== "built-in") expect(skills).toHaveLength(1) expect(skills[0].name).toBe("my-alias") expect(skills[0].source).toBe("global") @@ -617,7 +617,7 @@ Instructions` await skillsManager.discoverSkills() - const codeSkills = skillsManager.getSkillsForMode("code") + const codeSkills = skillsManager.getSkillsForMode("code").filter((s) => s.source !== "built-in") // Should include both generic and code-specific skills expect(codeSkills.length).toBe(2) diff --git a/src/services/skills/built-in-skills.ts b/src/services/skills/built-in-skills.ts new file mode 100644 index 00000000000..6f7c78f56a4 --- /dev/null +++ b/src/services/skills/built-in-skills.ts @@ -0,0 +1,96 @@ +import { SkillMetadata, SkillContent } from "./SkillsManager" + +interface BuiltInSkillDefinition { + name: string + description: string + instructions: string +} + +const BUILT_IN_SKILLS: Record = { + "code-simplify": { + name: "code-simplify", + description: + "Simplifies and refines code for clarity, consistency, and maintainability while preserving all functionality. Focuses on recently modified code unless instructed otherwise.", + instructions: `You are an expert code simplification specialist focused on enhancing code clarity, consistency, and maintainability while preserving exact functionality. Your expertise lies in applying project-specific best practices to simplify and improve code without altering its behavior. You prioritize readable, explicit code over overly compact solutions. This is a balance that you have mastered as a result your years as an expert software engineer. + +You will analyze recently modified code and apply refinements that: + +1. **Preserve Functionality**: Never change what the code does - only how it does it. All original features, outputs, and behaviors must remain intact. + +2. **Apply Project Standards**: Follow the established coding standards from AGENTS.md and related documentation including: + + - Use ES modules with proper import sorting and extensions + - Prefer \`function\` keyword over arrow functions + - Use explicit return type annotations for top-level functions + - Follow proper React component patterns with explicit Props types + - Use proper error handling patterns (avoid try/catch when possible) + - Maintain consistent naming conventions + +3. **Enhance Clarity**: Simplify code structure by: + + - Reducing unnecessary complexity and nesting + - Eliminating redundant code and abstractions + - Improving readability through clear variable and function names + - Consolidating related logic + - Removing unnecessary comments that describe obvious code + - IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions + - Choose clarity over brevity - explicit code is often better than overly compact code + +4. **Maintain Balance**: Avoid over-simplification that could: + + - Reduce code clarity or maintainability + - Create overly clever solutions that are hard to understand + - Combine too many concerns into single functions or components + - Remove helpful abstractions that improve code organization + - Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners) + - Make the code harder to debug or extend + +5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope. + +Your refinement process: + +1. Identify the recently modified code sections +2. Analyze for opportunities to improve elegance and consistency +3. Apply project-specific best practices and coding standards +4. Ensure all functionality remains unchanged +5. Verify the refined code is simpler and more maintainable +6. Document only significant changes that affect understanding + +You operate autonomously and proactively, refining code immediately after it's written or modified without requiring explicit requests. Your goal is to ensure all code meets the highest standards of elegance and maintainability while preserving its complete functionality.`, + }, +} + +/** + * 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: ``, + source: "built-in" as const, + })) +} + +/** + * Get a specific built-in skill by name + */ +export function getBuiltInSkill(name: string): SkillContent | undefined { + const skill = BUILT_IN_SKILLS[name] + if (!skill) return undefined + + return { + name: skill.name, + description: skill.description, + path: ``, + source: "built-in" as const, + instructions: skill.instructions, + } +} + +/** + * Get names of all built-in skills + */ +export function getBuiltInSkillNames(): string[] { + return Object.keys(BUILT_IN_SKILLS) +} diff --git a/src/shared/skills.ts b/src/shared/skills.ts index 7ed85816aa8..e490ca8a190 100644 --- a/src/shared/skills.ts +++ b/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 identifier + source: "global" | "project" | "built-in" // Where the skill was discovered mode?: string // If set, skill is only available in this mode }