{"record":{"id":"85db41bbd21cc10d","repo":"farion1231/cc-switch","slug":"no-skills-in-zip","errorCode":"NO_SKILLS_IN_ZIP","errorMessage":"NO_SKILLS_IN_ZIP","messagePattern":"NO_SKILLS_IN_ZIP","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":3653,"sourceCode":"    /// 流程：\n    /// 1. 解压 ZIP 到临时目录\n    /// 2. 扫描目录查找包含 SKILL.md 的技能\n    /// 3. 复制到 SSOT 并保存到数据库\n    /// 4. 同步到当前应用目录\n    pub fn install_from_zip(\n        db: &Arc<Database>,\n        zip_path: &Path,\n        current_app: &AppType,\n    ) -> Result<Vec<InstalledSkill>> {\n        // 解压到临时目录\n        let temp_guard = Self::extract_local_zip(zip_path)?;\n        let temp_dir = temp_guard.path();\n\n        // 扫描所有包含 SKILL.md 的目录\n        let skill_dirs = Self::scan_skills_in_dir(temp_dir)?;\n\n        if skill_dirs.is_empty() {\n            return Err(anyhow!(format_skill_error(\n                \"NO_SKILLS_IN_ZIP\",\n                &[],\n                Some(\"checkZipContent\"),\n            )));\n        }\n\n        let _state_guard = skill_state_write_guard();\n        let ssot_dir = Self::get_ssot_dir()?;\n        let mut installed = Vec::new();\n        let existing_skills = db.get_all_installed_skills()?;\n        let zip_stem = zip_path\n            .file_stem()\n            .and_then(|s| s.to_str())\n            .map(|s| s.to_string());\n\n        for skill_dir in skill_dirs {\n            // 解析元数据（提前解析，用于确定安装名）\n            let skill_md = skill_dir.join(\"SKILL.md\");","sourceCodeStart":3635,"sourceCodeEnd":3671,"githubUrl":"https://github.com/farion1231/cc-switch/blob/a2e22f330273a5b6ffa87cb8b82b624601bac562/src-tauri/src/services/skill.rs#L3635-L3671","documentation":"Structured error from install_from_zip: the local ZIP was extracted successfully, but scan_skills_in_dir found no subdirectory containing a SKILL.md. The installer only knows how to install directories that look like skills, so an archive with no manifest anywhere is rejected before anything is written to SSOT.","triggerScenarios":"Importing a ZIP that is not a skill package: documentation archives, a ZIP of loose files with SKILL.md missing, a folder where the manifest is misspelled (skill.md / SKILL.MD — the scan is for the exact name SKILL.md), or the skill folder was excluded when the ZIP was created.","commonSituations":"Users right-click 'Compress' on the wrong folder; macOS/Windows tools skipping dotfiles or specific files; manifest renamed while editing; zipping the repo root of a project whose skills live deeper than expected but with different naming.","solutions":["Inspect the ZIP: unzip -l skills.zip — confirm some directory contains a file named exactly SKILL.md","Re-create the ZIP so it contains <skill-name>/SKILL.md at or below the root","Fix the manifest filename case/spelling in the source folder and re-zip"],"exampleFix":"# before\nzip -r skills.zip . -x '*.git*'   # may exclude or misplace SKILL.md\n\n# after — zip the skill folder itself so <name>/SKILL.md is present\nzip -r skills.zip my-skill/","handlingStrategy":"validation","validationCode":"// Rust — pre-scan a ZIP for a SKILL.md before invoking install_from_zip\nfn zip_has_skill_manifest(zip_path: &Path) -> Result<bool> {\n    let file = fs::File::open(zip_path)?;\n    let mut zip = zip::ZipArchive::new(file)?;\n    Ok((0..zip.len()).any(|i| {\n        zip.by_index(i).map(|e| e.name().ends_with(\"SKILL.md\")).unwrap_or(false)\n    }))\n}","typeGuard":"export function isNoSkillsInZip(e: unknown): boolean {\n  return typeof e === \"string\" && e.includes('\"code\":\"NO_SKILLS_IN_ZIP\"');\n}","tryCatchPattern":"match install_from_zip(&db, &zip, &app).await {\n    Err(e) if e.to_string().contains(\"NO_SKILLS_IN_ZIP\") => {\n        // show 'the ZIP contains no skill (missing SKILL.md)' and keep the file picker open\n    }\n    other => other,\n}","preventionTips":["Always zip the skill folder itself so <name>/SKILL.md is inside","Check the manifest filename casing — the scanner matches SKILL.md exactly","Run unzip -l on exported ZIPs as a packaging sanity check"],"tags":["zip","import","skill","validation"],"backgroundTag":null,"analyzedSha":"a2e22f330273a5b6ffa87cb8b82b624601bac562","analyzedAt":"2026-08-16T03:46:07.889Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}