Development
Hook
Hooks
What skill-semver runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Install
> /plugin marketplace add cathy-kim/skill-semver > /plugin install skill-semver@skill-semver-marketplace
Ships with skill-semver. Installing the plugin gets these hooks.
What fires, and when
PostToolUse
- Matches
Write|Editnpx tsx hooks/skill-version-hook.ts
Where it lives
- hooks/skill-version-hook.tsRunsGitHub
Read the script
#!/usr/bin/env npx tsx /** * Skill Version Hook v1.1.0 * * SKILL.md 파일이 수정될 때 자동으로 버전 백업을 생성합니다. * * Hook Event: PostToolUse (Write, Edit tools) * * 동작: * 1. Write/Edit 도구가 SKILL.md를 수정했는지 확인 * 2. SKILL.md에서 버전 정보 추출 (pre-release 지원: 1.0.0-alpha, 1.0.0-beta.1) * 3. releases/ 폴더에 버전별 백업 생성 * 4. CHANGELOG.md 업데이트 * 5. Last Updated 날짜 자동 업데이트 * 6. 로그 출력 * * 파일 명명 규칙: v{VERSION}_{YYYY-MM-DD}_SKILL.md * * Changelog: * - v1.1.0: Pre-release 지원, Last Updated 자동화, 경로 버그 수정, 성능 최적화 * - v1.0.0: 초기 버전 */ import * as fs from "fs"; import * as path from "path"; // ============================================================================ // Types // ============================================================================ interface PostToolUseInput { session_id: string; tool_name: string; tool_input: { file_path?: string; content?: string; old_string?: string; new_string?: string; }; tool_output?: { success?: boolean; error?: string; }; transcript_path?: string; } interface HookResult { continue: boolean; message?: string; } // ============================================================================ // Constants // ============================================================================ const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd(); const LOG_DIR = path.join(PROJECT_DIR, ".claude", "hooks", "logs"); // ============================================================================ // Main Hook // ============================================================================ async function main(): Promise<void> { try { // Read input from stdin const inputData = fs.readFileSync(0, "utf-8"); const input: PostToolUseInput = JSON.parse(inputData); const result = await processHook(input); // Output result console.log(JSON.stringify(result)); } catch (error: any) { // Graceful degradation - don't block the session console.log( JSON.stringify({ continue: true, message: `[skill-version-hook] Error: ${error.message}`, }) ); } } async function processHook(input: PostToolUseInput): Promise<HookResult> { const { tool_name, tool_input, tool_output } = input; // 1. Check if this is a Write or Edit tool if (tool_name !== "Write" && tool_name !== "Edit") { return { continue: true }; } // 2. Check if the tool succeeded if (tool_output?.success === false) { return { continue: true }; } // 3. Check if the file is a SKILL.md const filePath = tool_input?.file_path; if (!filePath || !isSkillMdFile(filePath)) { return { continue: true }; } // 4. Extract skill name from path const skillName = extractSkillName(filePath); if (!skillName) { return { continue: true }; } // 5. Read the SKILL.md content once (optimization: single read) let content: string; try { content = fs.readFileSync(filePath, "utf-8"); } catch (error: any) { log(`[skill-version-hook] Failed to read ${filePath}: ${error.message}`); return { continue: true }; } // 6. Extract version from content (supports pre-release: 1.0.0-alpha, 1.0.0-beta.1) const version = extractVersionFromContent(content); if (!version) { log(`[skill-version-hook] No version found in ${filePath}, skipping backup`); return { continue: true, message: `[skill-version-hook] No version header found in ${skillName}/SKILL.md`, }; } // 7. Create releases directory if needed const releasesDir = path.join(path.dirname(filePath), "releases"); if (!fs.existsSync(releasesDir)) { fs.mkdirSync(releasesDir, { recursive: true }); } // 8. Generate backup filename (sanitize pre-release for filename) const today = new Date().toISOString().split("T")[0]; // YYYY-MM-DD const safeVersion = version.replace(/[^a-zA-Z0-9.-]/g, "-"); const backupFilename = `v${safeVersion}_${today}_SKILL.md`; const backupPath = path.join(releasesDir, backupFilename); // 9. Check if backup already exists for this version if (fs.existsSync(backupPath)) { log(`[skill-version-hook] Backup already exists: ${backupFilename}`); return { continue: true, message: `[skill-version-hook] Backup already exists for ${skillName} v${version}`, }; } // 10. Update Last Updated date in SKILL.md const updatedContent = updateLastUpdated(content, today); if (updatedContent !== content) { try { fs.writeFileSync(filePath, updatedContent, "utf-8"); content = updatedContent; log(`[skill-version-hook] Updated Last Updated to ${today}`); } catch (error: any) { log(`[skill-version-hook] Failed to update Last Updated: ${error.message}`); } } // 11. Create backup try { fs.writeFileSync(backupPath, content, "utf-8"); log(`[skill-version-hook] Created backup: ${backupFilename}`); // 12. Update CHANGELOG.md const changelogPath = path.join(path.dirname(filePath), "CHANGELOG.md"); updateChangelog(changelogPath, skillName, version, today); // 13. Add initial development notice for 0.x.x versions const versionNote = version.startsWith("0.") ? " (Initial Development)" : ""; return { continue: true, message: `[skill-version-hook] Backed up ${skillName}/SKILL.md to releases/${backupFilename}${versionNote}`, }; } catch (error: any) { log(`[skill-version-hook] Failed to create backup: ${error.message}`); return { continue: true, message: `[skill-version-hook] Failed to backup: ${error.message}`, }; } } // ============================================================================ // Helper Functions // ============================================================================ /** * Check if the file path is a SKILL.md file * Fixed: Use regex for cross-platform path separator compatibility */ function isSkillMdFile(filePath: string): boolean { // Normalize path and convert to forward slashes for co
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Ships withskill-semver
Automatic version control for Claude Code Skills with semantic versioning, auto-backup, and changelog generation.
Get the whole plugin
Stats
18
Stars
2
Forks
Quiet
Maintenance
TypeScript
Language
7mo ago
Last commit
7mo ago
Created
Repo: cathy-kim/skill-semver

