(folderName)
| 132 | } |
| 133 | |
| 134 | function validatePlugin(folderName) { |
| 135 | const pluginDir = path.join(PLUGINS_DIR, folderName); |
| 136 | const errors = []; |
| 137 | |
| 138 | // Rule 1: Must have .github/plugin/plugin.json |
| 139 | const pluginJsonPath = path.join(pluginDir, ".github/plugin", "plugin.json"); |
| 140 | if (!fs.existsSync(pluginJsonPath)) { |
| 141 | errors.push("missing required file: .github/plugin/plugin.json"); |
| 142 | return errors; |
| 143 | } |
| 144 | |
| 145 | // Rule 2: Must have README.md |
| 146 | const readmePath = path.join(pluginDir, "README.md"); |
| 147 | if (!fs.existsSync(readmePath)) { |
| 148 | errors.push("missing required file: README.md"); |
| 149 | } |
| 150 | |
| 151 | // Parse plugin.json |
| 152 | let plugin; |
| 153 | try { |
| 154 | const raw = fs.readFileSync(pluginJsonPath, "utf-8"); |
| 155 | plugin = JSON.parse(raw); |
| 156 | } catch (err) { |
| 157 | errors.push(`failed to parse plugin.json: ${err.message}`); |
| 158 | return errors; |
| 159 | } |
| 160 | |
| 161 | // Rule 3 & 4: name, description, version |
| 162 | const nameErrors = validateName(plugin.name, folderName); |
| 163 | errors.push(...nameErrors); |
| 164 | |
| 165 | const descError = validateDescription(plugin.description); |
| 166 | if (descError) errors.push(descError); |
| 167 | |
| 168 | const versionError = validateVersion(plugin.version); |
| 169 | if (versionError) errors.push(versionError); |
| 170 | |
| 171 | // Rule 5: keywords (or tags for backward compat) |
| 172 | const keywordsError = validateKeywords(plugin.keywords ?? plugin.tags); |
| 173 | if (keywordsError) errors.push(keywordsError); |
| 174 | |
| 175 | // Rule 6: agents, commands, skills paths |
| 176 | const specErrors = validateSpecPaths(plugin); |
| 177 | errors.push(...specErrors); |
| 178 | |
| 179 | return errors; |
| 180 | } |
| 181 | |
| 182 | // Main validation function |
| 183 | function validatePlugins() { |
no test coverage detected