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
This commit is contained in:
Roo Code 2026-01-20 18:49:58 +00:00
parent ead1658441
commit 7775d2ab65
6 changed files with 157 additions and 25 deletions

View file

@ -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 () => {

View file

@ -8,6 +8,23 @@ interface BuiltInCommandDefinition {
}
const BUILT_IN_COMMANDS: Record<string, BuiltInCommandDefinition> = {
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",

View file

@ -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<void> {
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)

View file

@ -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)

View file

@ -0,0 +1,96 @@
import { SkillMetadata, SkillContent } from "./SkillsManager"
interface BuiltInSkillDefinition {
name: string
description: string
instructions: string
}
const BUILT_IN_SKILLS: Record<string, BuiltInSkillDefinition> = {
"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: `<built-in:${skill.name}>`,
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: `<built-in:${skill.name}>`,
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)
}

View file

@ -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
}