src — skills
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:
- Human-readable: Skills are primarily written in Markdown, making them easy to read, understand, and contribute to by developers and non-developers alike.
- Richer Metadata: Supports a wider range of metadata fields, including
version,author,tags,requires(for tools, environment variables), andNative Enginespecific fields likepriorityandtriggers. - Structured Content: Allows for defining
examples,steps,tool invocations, andcode blocksdirectly within the Markdown body, enabling more complex and explicit skill definitions. - Three-Tier Loading: Skills are loaded from a defined hierarchy:
workspace(project-specific) >managed(user-installed) >bundled(built-in), with higher tiers overriding lower ones. - Registry Integration: Designed to integrate with a remote skill registry (the "Hub") for discovery, installation, and updates.
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, }; }
/**
- Find the best matching skill for a request
*/ 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 // ==========================================================================
/**
- Register a legacy JSON-based skill by converting it to SKILL.md format.
- The skill is stored internally as a SKILL.md Skill after conversion
- from the legacy adapter.
- *
- @param legacySkill - A legacy skill object (from SkillManager or SkillLoader)
- @param tier - The tier to register under (defaults to 'workspace')
*/ 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); }
/**
- Get all skills as UnifiedSkill format.
- Converts all registered SKILL.md skills to the unified format.
- *
- @returns An array of UnifiedSkill objects
*/ getAllUnified(): UnifiedSkill[] { const unified: UnifiedSkill[] = [];
for (const skill of this.skills.values()) { unified.push(skillMdToUnified(skill)); }
return unified; }
// ========================================================================== // Management // ==========================================================================
/**
- Enable a skill
*/ enable(name: string): boolean { const skill = this.skills.get(name); if (skill) { skill.enabled = true; return true; } return false; }
/**
- Disable a skill
*/ disable(name: string): boolean { const skill = this.skills.get(name); if (skill) { skill.enabled = false; return true; } return false; }
/**
- Reload a specific skill
*/
async reload(name: string): Promise
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; } }
/**
- Reload all skills
*/
async reloadAll(): Promise
/**
- Unload a skill
*/ 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 // ==========================================================================
/**
- Start watching skill directories
*/ 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 } } }
/**
- Handle file change event
*/
private async handleFileChange(
tier: SkillTier,
filePath: string,
event: string
): Promise
// 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 } } } }
/**
- Stop watching
*/ stopWatching(): void { for (const watcher of this.watchers.values()) { watcher.close(); } this.watchers.clear(); }
// ========================================================================== // Utilities // ==========================================================================
/**
- Resolve path with home directory expansion
*/ private resolvePath(p: string): string { if (p.startsWith('~')) { return path.join(os.homedir(), p.slice(1)); } return path.resolve(p); }
/**
- Format skills as a list for display
*/ formatList(): string { const lines: string[] = ['Skills Registry:', ''];
const byTier: Record
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 registry
*/ shutdown(): void { this.stopWatching(); this.skills.clear(); this.skillsByTier.clear(); this.loaded = false; } }
// ============================================================================ // Singleton // ============================================================================
let registryInstance: SkillRegistry | null = null;
export function getSkillRegistry(config?: Partial
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.
- Execution Flow: Prioritizes execution:
steps>toolInvocations>codeBlocks. If none are present, it formats the skill's description as guidance. ToolExecutorFn&CodeExecutorFn: These are dependency injection points, allowing external modules (e.g., the agent's tool handler or code interpreter) to provide the actual implementation for running tools and code.- Requirements Check: Before execution, it verifies if the skill's
requires(tools, environment variables) are met within theSkillExecutionContext. - Dynamic Content Resolution: Integrates with
SkillVariableResolverto replace placeholders like$ARGUMENTSand$WORKING_DIR, andresolveBashInjectionsto execute!command` patterns.
3. SkillParser (src/skills/parser.ts)
The SkillParser module is dedicated to parsing SKILL.md file content into a structured Skill object.
parseSkillFile(content, sourcePath, tier): The main function that extracts YAML frontmatter and Markdown body.parseMetadata(): ExtractsSkillMetadatafrom the YAML frontmatter (name, description, version, tags, requirements, Native Engine specific fields).parseContent(): Analyzes the Markdown body to extractSkillContent, including the main description, usage instructions, examples, ordered steps, tool invocations, and code blocks.validateSkill(skill): Ensures the parsed skill adheres to basic structural and naming conventions.serializeSkill(skill): Converts aSkillobject back into itsSKILL.mdstring representation.
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.
search(query, options): Queries the remote registry for skills, with caching and filtering capabilities.install(skillName, version): Downloads a skill from the hub, validates its content, writes it to themanagedskills directory, and updates a locallockfile.json.uninstall(skillName): Removes an installed skill from the file system and the lockfile.update(skillName?): Checks for and installs newer versions of skills.publish(skillPath): Validates a localSKILL.mdfile and prepares its metadata for submission to the remote registry.sync(): Reconciles the locallockfile.jsonwith the actual state of installed skills on disk, detecting missing files or checksum mismatches.- Integrity Management: Uses SHA-256 checksums and a lockfile to ensure the integrity and versioning of installed skills.
5. Eligibility (src/skills/eligibility.ts)
This module determines if a skill can run on the current system by checking its declared requirements.
SkillRequirements: Defines what a skill needs (e.g.,bins(binaries),env(environment variables),configs(config files),platform,nodeVersion).checkEligibility(requirements): The main function that evaluates all specified requirements.- Utility Functions: Includes helpers like
isBinaryAvailable(),isEnvSet(),isConfigPresent(),isPlatformSupported(), andisNodeVersionSufficient().
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.
UnifiedSkill: A common interface that represents a skill, abstracting away the underlying format.legacyToUnified(legacySkill): Converts aLegacySkillobject to aUnifiedSkill.skillMdToUnified(skillMd): Converts aSKILL.mdSkillobject to aUnifiedSkill.unifiedToLegacy(unifiedSkill)/unifiedToSkillMd(unifiedSkill): Provides backward conversion capabilities, often preferring to return the original format if available.
7. Skill Enhancements (src/skills/skill-enhancements.ts)
This module provides utilities for dynamic content generation within skills.
SkillVariableResolver: Replaces template variables like$ARGUMENTS,$WORKING_DIR,$CLAUDE_SESSION_ID, and$GIT_BRANCHwithin skill content.SkillBudgetCalculator: Helps manage the size of injected skill content to fit within context window limits.
8. Bash Injection (src/skills/bash-injection.ts)
This module enables dynamic execution of shell commands within skill content using the !command`` syntax.
resolveBashInjections(content, cwd): Scans skill content for!commandof the executed command.patterns and replaces them with thestdout- Security Note: Commands are executed via
child_process.execSyncwith a timeout. This feature is intended for skill templates and not user-facing content due to security implications.
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.
isStarterPack(name): Checks if a given name corresponds to a known starter pack.getStarterPacks(): Lists all available starter packs.findStarterPack(keyword)/findStarterByKeyword(keyword): Helps locate starter packs based on keywords or aliases.
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.
initializeAllSkills(projectRoot?): This crucial function initializes both the newSKILL.mdSkillRegistryand the deprecatedSkillManager. It then registers all legacy skills into theSkillRegistry, making them discoverable and usable through the unified interface. This ensures a smooth transition and allows older skills to coexist with new ones.getAllUnifiedSkills(): Returns a combined list of all skills (bothSKILL.mdand legacy) asUnifiedSkillobjects, providing a single, consistent view of all available capabilities.findSkill(request): Uses theSkillRegistryto find the best matchingSKILL.mdskill for a given user request.executeSkill(skillName, context): Retrieves a skill from theSkillRegistryand passes it to theSkillExecutorfor execution.
Integration with the Codebase
The src/skills module is deeply integrated throughout the Code Buddy application:
- Agent Core (
src/agent/codebuddy-agent.ts): The agent usesfindSkill()to identify which skill best matches a user's intent, driving its decision-making process. - CLI Commands (
commands/cli/Native Engine-commands.ts,commands/handlers/*.ts): Native Engine-commands.tsusesSkillsHubforsearch,install,uninstall,update, andpublishoperations.starter-handlers.tsusesgetSkillRegistry()andfindStarterPack()to manage starter pack installations.core-handlers.tsstill interacts with the deprecatedgetSkillManager()for some legacy operations.- Server API (
src/server/index.ts): Exposes API endpoints forSkillsHuboperations (install, uninstall, search). - Tooling (
src/tools/skill-discovery-tool.ts,src/agent/tool-handler.ts): - The
skill-discovery-toolusesSkillsHub.search()to find skills. refreshToolsFromSkills()usesgetSkillRegistry()to dynamically update the available tools based on skill requirements.- Security (
src/security/skill-scanner.ts): TheSkillRegistrycallsscanSkillFile()to perform security checks onSKILL.mdfiles before loading them. - Sandbox (
src/sandbox/e2b-sandbox.ts): Legacy skill parsing (parseSkillFileinskill-manager.ts) might involve reading files, which could interact with the sandbox environment. - Desktop Automation (
src/desktop-automation/base-native-provider.ts):resolveBashInjectionsusesexecSync, which might be provided by native providers for command execution.
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.
SkillManager(src/skills/skill-manager.ts): This was the primary manager for legacy JSON skills. WhileinitializeAllSkills()integrates it, new code should preferSkillRegistry.SkillLoader(src/skills/skill-loader.ts): Responsible for loading legacy skills from various directories (builtin, global, project, agent). Its functionality is largely superseded bySkillRegistry's tiered loading.
These deprecated components are primarily maintained for backward compatibility and will eventually be phased out once the migration to SKILL.md is complete.