Welcome to the Skills project! In this first chapter, we will explore the engine that makes the system feel "alive": Skill Change Detection.
Imagine you are a developer building a new feature. You write a script, save the file, and want to test it immediately.
The Old Way:
The Skill Change Detection Way:
This concept acts like a "Hot Reloading" engine. It allows users to add, edit, or delete skills (custom commands) at runtime without ever needing to restart the application.
To achieve this, we rely on three main ideas:
As a consumer of this module, your interaction is very simple. You generally care about two things: starting the watcher and listening for updates.
Before we start watching files, let's set up a listener. This is how the rest of the application knows to refresh its menu or clear its memory.
import { skillChangeDetector } from './skillChangeDetector'
// Subscribe to the signal
// This function runs every time a file changes
skillChangeDetector.subscribe(() => {
console.log("Something changed! Reloading skills...")
})
Explanation: We register a callback function. Whenever the detector triggers, this function runs.
Once we are listening, we turn the machine on.
// Start watching the filesystem
await skillChangeDetector.initialize()
Explanation: This kicks off the process. It finds the correct folders and attaches the "eyes" (file watchers) to them.
What happens inside skillChangeDetector.ts when you modify a file? Let's visualize the flow.
First, the system needs to know which folders to watch. It checks user settings, project settings, and additional flags. This logic is handled by a concept we will cover in depth in Path Discovery.
// Simplified logic from getWatchablePaths
async function getWatchablePaths() {
const paths = []
// Check user home directory
if (await exists('~/.claude/skills')) {
paths.push('~/.claude/skills')
}
// Check current project directory
if (await exists('./.claude/skills')) {
paths.push('./.claude/skills')
}
return paths
}
We use a library called chokidar to handle the heavy lifting of filesystem events. It's more reliable than the native Node.js fs.watch.
// Inside initialize()
watcher = chokidar.watch(paths, {
persistent: true,
ignoreInitial: true, // Don't trigger for existing files on startup
depth: 2, // Only look 2 folders deep
})
// Listen for specific events
watcher.on('add', handleChange)
watcher.on('change', handleChange)
watcher.on('unlink', handleChange) // 'unlink' means delete
Explanation: We configure the watcher to ignore the files that are already there (we loaded those on startup) and only report new actions.
This is the most critical part for performance. If you switch git branches, 100 files might change instantly. We use Debouncing to handle this.
The logic is: "Wait 300ms. If another change happens, reset the timer. Only run when silence lasts for 300ms."
let reloadTimer = null
const RELOAD_DEBOUNCE_MS = 300
function scheduleReload(path) {
// If a timer is already running, stop it!
if (reloadTimer) clearTimeout(reloadTimer)
// Start a new timer
reloadTimer = setTimeout(async () => {
performReload() // The actual heavy lifting
}, RELOAD_DEBOUNCE_MS)
}
Explanation: This ensures that performReload only runs once per batch of file changes, saving the computer from freezing. This is detailed further in Reload Debouncing.
When the timer finally fires, we need to ensure the application forgets the old version of the files.
function performReload() {
// 1. Clear the internal memory of skills
clearSkillCaches()
// 2. Clear command definitions
clearCommandsCache()
// 3. Tell everyone updates are ready
skillsChanged.emit()
}
Explanation: We clear the caches (covered in Cache Invalidation) and then trigger the reactive signal (covered in Reactive Signaling).
In this chapter, you learned how Skill Change Detection brings the application to life by:
However, a watcher is useless if it doesn't know where to look. In the next chapter, we will learn how the application intelligently hunts down configuration folders across your system.
Generated by Code IQ