* Loads a single skill from a SKILL.md file. * Returns null if the skill is invalid.
( skillDir: string, skillFilePath: string, verbose: boolean, )
| 44 | * Returns null if the skill is invalid. |
| 45 | */ |
| 46 | function loadSkillFromFile( |
| 47 | skillDir: string, |
| 48 | skillFilePath: string, |
| 49 | verbose: boolean, |
| 50 | ): SkillDefinition | null { |
| 51 | const dirName = path.basename(skillDir) |
| 52 | |
| 53 | // Read the file |
| 54 | let content: string |
| 55 | try { |
| 56 | content = fs.readFileSync(skillFilePath, 'utf8') |
| 57 | } catch { |
| 58 | if (verbose) { |
| 59 | console.error(`Failed to read skill file: ${skillFilePath}`) |
| 60 | } |
| 61 | return null |
| 62 | } |
| 63 | |
| 64 | // Parse frontmatter |
| 65 | const parsed = parseFrontmatter(content) |
| 66 | if (!parsed) { |
| 67 | if (verbose) { |
| 68 | console.error(`Invalid frontmatter in skill file: ${skillFilePath}`) |
| 69 | } |
| 70 | return null |
| 71 | } |
| 72 | |
| 73 | // Validate frontmatter |
| 74 | const result = SkillFrontmatterSchema.safeParse(parsed.frontmatter) |
| 75 | if (!result.success) { |
| 76 | if (verbose) { |
| 77 | console.error( |
| 78 | `Invalid skill frontmatter in ${skillFilePath}: ${result.error.message}`, |
| 79 | ) |
| 80 | } |
| 81 | return null |
| 82 | } |
| 83 | |
| 84 | const frontmatter = result.data |
| 85 | |
| 86 | // Verify name matches directory name |
| 87 | if (frontmatter.name !== dirName) { |
| 88 | if (verbose) { |
| 89 | console.error( |
| 90 | `Skill name '${frontmatter.name}' does not match directory name '${dirName}' in ${skillFilePath}`, |
| 91 | ) |
| 92 | } |
| 93 | return null |
| 94 | } |
| 95 | |
| 96 | return { |
| 97 | name: frontmatter.name, |
| 98 | description: frontmatter.description, |
| 99 | license: frontmatter.license, |
| 100 | metadata: frontmatter.metadata, |
| 101 | content, |
| 102 | filePath: skillFilePath, |
| 103 | } |
no test coverage detected