Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/services/command/__tests__/built-in-commands.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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 () => {
Expand Down
17 changes: 17 additions & 0 deletions src/services/command/built-in-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
29 changes: 24 additions & 5 deletions src/services/skills/SkillsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -28,14 +29,22 @@ 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
* - .roo/skills/[dirname] can be a symlink to a skill directory
*/
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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
24 changes: 12 additions & 12 deletions src/services/skills/__tests__/SkillsManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
})

Expand Down Expand Up @@ -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)
})

Expand Down Expand Up @@ -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)
})

Expand All @@ -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)
})

Expand All @@ -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)
})

Expand All @@ -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)
})

Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down
96 changes: 96 additions & 0 deletions src/services/skills/built-in-skills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { SkillMetadata, SkillContent } from "./SkillsManager"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import creates a circular dependency with SkillsManager.ts, which imports from this file. While it works at runtime because these are type-only imports (erased during compilation), it's a code smell that could cause issues if runtime values are ever added. Import directly from the shared types module instead.

Suggested change
import { SkillMetadata, SkillContent } from "./SkillsManager"
import { SkillMetadata, SkillContent } from "../../shared/skills"

Fix it with Roo Code or mention @roomote and request a fix.


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)
}
4 changes: 2 additions & 2 deletions src/shared/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
Loading