MCPcopy Create free account
hub / github.com/Zoo-Code-Org/Zoo-Code / loadSkillMetadata

Method loadSkillMetadata

src/services/skills/SkillsManager.ts:92–176  ·  view source on GitHub ↗

* Load skill metadata from a skill directory. * @param skillDir - The resolved path to the skill directory (target of symlink if symlinked) * @param source - Whether this is a global or project skill * @param mode - The mode this skill is specific to (undefined for generic skills) * @param s

(
		skillDir: string,
		source: "global" | "project",
		mode?: string,
		skillName?: string,
	)

Source from the content-addressed store, hash-verified

90 * @param skillName - The skill name (from symlink name if symlinked, otherwise from directory name)
91 */
92 private async loadSkillMetadata(
93 skillDir: string,
94 source: "global" | "project",
95 mode?: string,
96 skillName?: string,
97 ): Promise<void> {
98 const skillMdPath = path.join(skillDir, "SKILL.md")
99 if (!(await fileExists(skillMdPath))) return
100
101 try {
102 const fileContent = await fs.readFile(skillMdPath, "utf-8")
103
104 // Use gray-matter to parse frontmatter
105 const { data: frontmatter, content: body } = matter(fileContent)
106
107 // Validate required fields (only name and description for now)
108 if (!frontmatter.name || typeof frontmatter.name !== "string") {
109 console.error(`Skill at ${skillDir} is missing required 'name' field`)
110 return
111 }
112 if (!frontmatter.description || typeof frontmatter.description !== "string") {
113 console.error(`Skill at ${skillDir} is missing required 'description' field`)
114 return
115 }
116
117 // Validate that frontmatter name matches the skill name (directory name or symlink name)
118 // Per the Agent Skills spec: "name field must match the parent directory name"
119 const effectiveSkillName = skillName || path.basename(skillDir)
120 if (frontmatter.name !== effectiveSkillName) {
121 console.error(`Skill name "${frontmatter.name}" doesn't match directory "${effectiveSkillName}"`)
122 return
123 }
124
125 // Validate skill name per agentskills.io spec using shared validation
126 const nameValidation = validateSkillNameShared(effectiveSkillName)
127 if (!nameValidation.valid) {
128 const errorMessage = this.getSkillNameErrorMessage(effectiveSkillName, nameValidation.error!)
129 console.error(`Skill name "${effectiveSkillName}" is invalid: ${errorMessage}`)
130 return
131 }
132
133 // Description constraints:
134 // - 1-1024 chars
135 // - non-empty (after trimming)
136 const description = frontmatter.description.trim()
137 if (description.length < 1 || description.length > 1024) {
138 console.error(
139 `Skill "${effectiveSkillName}" has an invalid description length: must be 1-1024 characters (got ${description.length})`,
140 )
141 return
142 }
143
144 // Parse modeSlugs from frontmatter (new format) or fall back to directory-based mode
145 // Priority: frontmatter.modeSlugs > frontmatter.mode > directory mode
146 let modeSlugs: string[] | undefined
147 if (Array.isArray(frontmatter.modeSlugs)) {
148 modeSlugs = frontmatter.modeSlugs.filter((s: unknown) => typeof s === "string" && s.length > 0)
149 if (modeSlugs.length === 0) {

Callers 1

scanSkillsDirectoryMethod · 0.95

Calls 6

getSkillKeyMethod · 0.95
fileExistsFunction · 0.90
errorMethod · 0.65
setMethod · 0.65
readFileMethod · 0.45

Tested by

no test coverage detected