(bundle: SkillGenerateOutput)
| 176 | } |
| 177 | |
| 178 | function validateBundle(bundle: SkillGenerateOutput): void { |
| 179 | const allowedExtensions = new Set([".md", ".ts", ".js", ".sh", ".json", ".yaml", ".yml", ".txt"]); |
| 180 | const files = [...bundle.scripts, ...bundle.references]; |
| 181 | if (Buffer.byteLength(bundle.skill_md, "utf8") > 100 * 1024) throw new Error("SKILL.md exceeds size limit"); |
| 182 | if (files.length > 50) throw new Error("bundle contains too many files"); |
| 183 | |
| 184 | let totalBytes = Buffer.byteLength(bundle.skill_md, "utf8"); |
| 185 | for (const file of files) { |
| 186 | const name = file.filename; |
| 187 | if (!name || path.isAbsolute(name) || name.startsWith("/") || name.includes("..")) throw new Error(`unsafe filename: ${name}`); |
| 188 | if (!/^[A-Za-z0-9._/-]+$/.test(name)) throw new Error(`invalid filename: ${name}`); |
| 189 | const ext = path.extname(name).toLowerCase(); |
| 190 | if (!allowedExtensions.has(ext)) throw new Error(`unsupported file type: ${name}`); |
| 191 | const fileSize = Buffer.byteLength(file.content, "utf8"); |
| 192 | if (fileSize > 512 * 1024) throw new Error(`file exceeds size limit: ${name}`); |
| 193 | totalBytes += fileSize; |
| 194 | } |
| 195 | if (totalBytes > 5 * 1024 * 1024) throw new Error("bundle exceeds size limit"); |
| 196 | } |
| 197 | |
| 198 | function writeCompanionFiles(dirPath: string, root: "scripts" | "references", files: Array<{ filename: string; content: string }>): void { |
| 199 | if (files.length === 0) return; |
no test coverage detected