src — skills

Module: src-skills Cohesion: 0.80 Members: 0

src — skills

The src/skills module is the core of the agent's capabilities, responsible for defining, loading, managing, and executing the various "skills" it possesses. These skills represent discrete functionalities, ranging from simple file edits to complex multi-step workflows, and are crucial for the agent to understand and respond to user requests.

This module has undergone a significant evolution, transitioning from a legacy JSON-based format to a more expressive and powerful SKILL.md format. The current system aims to provide a unified interface over both formats during this transition.

SKILL.md: The New Standard for Skills

The SKILL.md system introduces a natural language format for defining skills, leveraging YAML frontmatter for metadata and Markdown for detailed instructions and content. This format is inspired by the Native Engine specification, offering enhanced readability, richer metadata, and a structured approach to skill definition.

Why SKILL.md?

The SKILL.md format offers several advantages over the legacy JSON format:

Quick Comparison: Legacy JSON vs. SKILL.md

| Feature | Legacy JSON (.json) | SKILL.md (.skill.md) if (skill.metadata.Native Engine?.triggers) { for (const trigger of skill.metadata.Native Engine.triggers) { const triggerLower = trigger.toLowerCase(); if (query.includes(triggerLower)) { score += 0.4; matchedTriggers.push(trigger); } else { // Check word overlap for multi-word triggers const triggerWords = triggerLower.split(/\s+/).filter(w => w.length >= 3); const overlap = triggerWords.filter(tw => significantWords.includes(tw)).length; if (triggerWords.length > 0 && overlap >= Math.ceil(triggerWords.length * 0.6)) { score += 0.25 * (overlap / triggerWords.length); matchedTriggers.push(trigger); } } } if (matchedTriggers.length > 0) { reasons.push('trigger match'); } }

// Check examples — query must contain example or high word overlap if (skill.content.examples) { for (const example of skill.content.examples) { const exampleLower = example.request.toLowerCase(); if (query.includes(exampleLower) || exampleLower.includes(query)) { score += 0.35; reasons.push('example match'); break; } // Word overlap with examples const exampleWords = exampleLower.split(/\s+/).filter(w => w.length >= 3); const overlap = exampleWords.filter(ew => significantWords.includes(ew)).length; if (exampleWords.length > 0 && overlap >= Math.ceil(exampleWords.length * 0.5)) { score += 0.2 * (overlap / exampleWords.length); reasons.push('example word match'); break; } } }

// Apply priority boost if (skill.metadata.Native Engine?.priority) { score *= 1 + (skill.metadata.Native Engine.priority / 100); }

// Normalize to 0-1 const confidence = Math.min(1, score);

return { skill, confidence, reason: reasons.join(', ') || 'no match', matchedTriggers, matchedTags, }; }

/**

*/ findBestMatch(request: string): SkillMatch | null { const matches = this.search({ query: request, limit: 1, minConfidence: 0.15, });

return matches.length > 0 ? matches[0] : null; }

// ========================================================================== // Unified Skill Access // ==========================================================================

/**

*/ registerLegacySkill(legacySkill: LegacySkill, tier: SkillTier = 'workspace'): void { // Convert to SKILL.md Skill format for internal storage const skill: Skill = { metadata: { name: legacySkill.name, description: legacySkill.description || '', tags: legacySkill.triggers ? legacySkill.triggers.slice(0, 10) : undefined, requires: legacySkill.tools ? { tools: legacySkill.tools } : undefined, Native Engine: { priority: legacySkill.priority, triggers: legacySkill.triggers ? [...legacySkill.triggers] : undefined, }, }, content: { description: legacySkill.systemPrompt || '', rawMarkdown: legacySkill.systemPrompt || '', }, sourcePath: 'legacy://' + legacySkill.name, tier, loadedAt: new Date(), enabled: true, };

this.registerSkill(skill); }

/**

*/ getAllUnified(): UnifiedSkill[] { const unified: UnifiedSkill[] = [];

for (const skill of this.skills.values()) { unified.push(skillMdToUnified(skill)); }

return unified; }

// ========================================================================== // Management // ==========================================================================

/**

*/ enable(name: string): boolean { const skill = this.skills.get(name); if (skill) { skill.enabled = true; return true; } return false; }

/**

*/ disable(name: string): boolean { const skill = this.skills.get(name); if (skill) { skill.enabled = false; return true; } return false; }

/**

*/ async reload(name: string): Promise { const skill = this.skills.get(name); if (!skill) { return false; }

try { const reloaded = await this.loadSkillFile(skill.sourcePath, skill.tier); this.registerSkill(reloaded); return true; } catch (error) { this.emit('skill:error', name, error instanceof Error ? error : new Error(String(error))); return false; } }

/**

*/ async reloadAll(): Promise { this.loaded = false; await this.load(); }

/**

*/ unload(name: string): boolean { const skill = this.skills.get(name); if (!skill) { return false; }

this.skills.delete(name); const tierMap = this.skillsByTier.get(skill.tier); if (tierMap) { tierMap.delete(name); }

this.emit('skill:unloaded', name); return true; }

// ========================================================================== // File Watching // ==========================================================================

/**

*/ private startWatching(): void { const paths = [ { tier: 'workspace' as SkillTier, path: this.config.workspacePath }, { tier: 'managed' as SkillTier, path: this.config.managedPath }, ];

for (const { tier, path: dirPath } of paths) { if (!dirPath) continue;

const resolved = this.resolvePath(dirPath); if (!fs.existsSync(resolved)) continue;

try { const watcher = fs.watch(resolved, { recursive: true }, (event, filename) => { if (filename && (filename.endsWith('.md') || filename.endsWith('.skill.md'))) { this.handleFileChange(tier, path.join(resolved, filename), event); } });

this.watchers.set(resolved, watcher); } catch { // Watching not supported or permission denied } } }

/**

*/ private async handleFileChange( tier: SkillTier, filePath: string, event: string ): Promise { if (event === 'rename') { // File deleted or renamed const skill = Array.from(this.skills.values()).find(s => s.sourcePath === filePath); if (skill) { this.unload(skill.metadata.name); }

// Check if file exists (renamed to) if (fs.existsSync(filePath)) { try { const newSkill = await this.loadSkillFile(filePath, tier); this.registerSkill(newSkill); } catch { // Invalid skill file } } } else if (event === 'change') { // File modified const skill = Array.from(this.skills.values()).find(s => s.sourcePath === filePath); if (skill) { await this.reload(skill.metadata.name); } else { // New file try { const newSkill = await this.loadSkillFile(filePath, tier); this.registerSkill(newSkill); } catch { // Invalid skill file } } } }

/**

*/ stopWatching(): void { for (const watcher of this.watchers.values()) { watcher.close(); } this.watchers.clear(); }

// ========================================================================== // Utilities // ==========================================================================

/**

*/ private resolvePath(p: string): string { if (p.startsWith('~')) { return path.join(os.homedir(), p.slice(1)); } return path.resolve(p); }

/**

*/ formatList(): string { const lines: string[] = ['Skills Registry:', ''];

const byTier: Record = { workspace: [], managed: [], bundled: [], team: [], };

for (const skill of this.skills.values()) { byTier[skill.tier].push(skill); }

for (const tier of ['workspace', 'managed', 'bundled', 'team'] as SkillTier[]) { const skills = byTier[tier]; if (skills.length === 0) continue;

lines.push([${tier.toUpperCase()}] (${skills.length})); for (const skill of skills) { const status = skill.enabled ? '✓' : '✗'; const tags = skill.metadata.tags?.join(', ') || ''; lines.push( ${status} ${skill.metadata.name} - ${skill.metadata.description}); if (tags) { lines.push( Tags: ${tags}); } } lines.push(''); }

return lines.join('\n'); }

/**

*/ shutdown(): void { this.stopWatching(); this.skills.clear(); this.skillsByTier.clear(); this.loaded = false; } }

// ============================================================================ // Singleton // ============================================================================

let registryInstance: SkillRegistry | null = null;

export function getSkillRegistry(config?: Partial): SkillRegistry { if (!registryInstance) { registryInstance = new SkillRegistry(config); } return registryInstance; }

export function resetSkillRegistry(): void { if (registryInstance) { registryInstance.shutdown(); } registryInstance = null; }

export default SkillRegistry;


### Migration Steps

The `MIGRATION.md` document outlines the process for converting legacy JSON skills to `SKILL.md`. Key steps include:

1.  **Create a `.skill.md` file**: Place it in `.codebuddy/skills/` (workspace), `~/.codebuddy/skills/` (managed), or `src/skills/bundled/` (bundled).
2.  **Convert Fields**: Map legacy fields like `name`, `description`, `triggers`, `systemPrompt`, and `tools` to their `SKILL.md` frontmatter or content equivalents.
3.  **Register**: Skills are automatically registered by `SkillRegistry` on load, or explicitly via `initializeAllSkills()`.

## Core Architecture & Components

The `src/skills` module is built around several key components that work together to provide a robust skill system.

### 1. `SkillRegistry` (`src/skills/registry.ts`)

The `SkillRegistry` is the central component for managing `SKILL.md` skills. It handles loading, storing, searching, and providing access to skills across different tiers.

*   **Tiered Loading**: Skills are loaded from `bundled`, `managed`, and `workspace` directories. This hierarchy ensures that project-specific skills (workspace) can override user-installed (managed) or built-in (bundled) skills.
    *   `bundledPath`: `src/skills/bundled/` (e.g., `file-edit.skill.md`, `git-commit.skill.md`).
    *   `managedPath`: `~/.codebuddy/skills/managed/` (user-installed via `SkillsHub`).
    *   `workspacePath`: `.codebuddy/skills/` (project-specific).
*   **Skill Matching**: Provides `search()` and `findBestMatch()` methods to identify relevant skills based on a query, tags, and triggers, using a confidence-based scoring mechanism.
*   **File Watching**: Can monitor skill directories for changes, automatically reloading or unloading skills as files are added, modified, or deleted.
*   **Security Integration**: Integrates with `src/security/skill-scanner.ts` to block skills with critical security findings during registration.
*   **Legacy Skill Integration**: The `registerLegacySkill()` method allows legacy JSON skills (from `SkillManager`) to be converted and registered within the `SkillRegistry`, providing a unified search and access interface.

graph TD A[SkillRegistry] --> B(loadTier: 'bundled') A --> C(loadTier: 'managed') A --> D(loadTier: 'workspace') B --> E[Skill Files: src/skills/bundled/] C --> F[Skill Files: ~/.codebuddy/skills/managed/] D --> G[Skill Files: .codebuddy/skills/] E -- parseSkillFile --> H(Skill Object) F -- parseSkillFile --> H G -- parseSkillFile --> H H -- registerSkill --> A A -- search/findBestMatch --> I(SkillMatch) A -- registerLegacySkill --> J(LegacySkill) J -- legacyToUnified --> H ```

2. SkillExecutor (src/skills/executor.ts)

The SkillExecutor is responsible for interpreting and executing the content of a Skill object. It orchestrates the execution of steps, tool invocations, and code blocks defined within a skill.

3. SkillParser (src/skills/parser.ts)

The SkillParser module is dedicated to parsing SKILL.md file content into a structured Skill object.

4. SkillsHub (src/skills/hub.ts)

The SkillsHub provides functionality for interacting with a remote skill registry (e.g., hub.codebuddy.dev). It enables discovery, installation, management, and publishing of skills.

5. Eligibility (src/skills/eligibility.ts)

This module determines if a skill can run on the current system by checking its declared requirements.

6. Adapters (src/skills/adapters/index.ts, legacy-skill-adapter.ts)

The adapters facilitate conversion between the legacy JSON skill format and the UnifiedSkill format, and between SKILL.md and UnifiedSkill.

7. Skill Enhancements (src/skills/skill-enhancements.ts)

This module provides utilities for dynamic content generation within skills.

8. Bash Injection (src/skills/bash-injection.ts)

This module enables dynamic execution of shell commands within skill content using the !command`` syntax.

9. Starter Packs (src/skills/starter-packs.ts)

Starter packs are pre-defined collections of skills or configurations that can be quickly initialized. This module provides functions to discover and resolve these packs.

10. Bundled Skills (src/skills/bundled/*.skill.md)

This directory contains examples of SKILL.md skills that are bundled with the application, such as file-edit.skill.md, git-commit.skill.md, typescript-expert.skill.md, and web-search.skill.md. These serve as foundational capabilities and reference implementations.

The Unified Skill System in Action

The src/skills/index.ts file acts as the entry point and orchestrator for the entire skill system, providing convenience functions and ensuring backward compatibility.

Integration with the Codebase

The src/skills module is deeply integrated throughout the Code Buddy application:

Deprecated Components

During the transition to SKILL.md, some legacy components are still present but are marked for deprecation. Developers should aim to use the SKILL.md system (SkillRegistry, SkillExecutor) for new features.

These deprecated components are primarily maintained for backward compatibility and will eventually be phased out once the migration to SKILL.md is complete.