{"record":{"id":"6db282a3c0179f2a","repo":"farion1231/cc-switch","slug":"skill-skill-md","errorCode":null,"errorMessage":"Skill 源目录缺少 SKILL.md，拒绝同步以避免覆盖目标目录: {}","messagePattern":"Skill 源目录缺少 SKILL\\.md，拒绝同步以避免覆盖目标目录: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":2342,"sourceCode":"            fs::remove_dir(path)?; // Windows 的目录 symlink 需要用 remove_dir\n        } else if path.is_dir() {\n            // 真实目录：递归删除\n            fs::remove_dir_all(path)?;\n        } else if path.exists() {\n            // 普通文件\n            fs::remove_file(path)?;\n        }\n        Ok(())\n    }\n\n    fn validate_sync_source_dir(source: &Path, directory: &str) -> Result<()> {\n        if !source.is_dir() {\n            return Err(anyhow!(\"Skill 不存在于 SSOT: {directory}\"));\n        }\n\n        let manifest = source.join(\"SKILL.md\");\n        if !manifest.is_file() {\n            return Err(anyhow!(\n                \"Skill 源目录缺少 SKILL.md，拒绝同步以避免覆盖目标目录: {}\",\n                source.display()\n            ));\n        }\n\n        Ok(())\n    }\n\n    fn replace_dest_with_copy(source: &Path, dest: &Path, directory: &str) -> Result<()> {\n        Self::validate_sync_source_dir(source, directory)?;\n\n        let parent = dest\n            .parent()\n            .ok_or_else(|| anyhow!(\"Invalid skill destination: {}\", dest.display()))?;\n        fs::create_dir_all(parent)?;\n\n        let nonce = SystemTime::now()\n            .duration_since(UNIX_EPOCH)","sourceCodeStart":2324,"sourceCodeEnd":2360,"githubUrl":"https://github.com/farion1231/cc-switch/blob/a2e22f330273a5b6ffa87cb8b82b624601bac562/src-tauri/src/services/skill.rs#L2324-L2360","documentation":"Thrown by validate_sync_source_dir when the SSOT source directory exists but contains no SKILL.md manifest. The check is deliberate: copying an empty or half-written source over the app destination would silently wipe a working skill, so sync is refused until the source is a valid skill directory again.","triggerScenarios":"An interrupted download/import left the SSOT directory half-populated; the user or another tool deleted SKILL.md from the SSOT folder; a directory was created manually inside SSOT that is not actually a skill.","commonSituations":"Disk-full during install so SKILL.md was never written; sync tools that skip the manifest file; users editing skills and accidentally renaming SKILL.md.","solutions":["Inspect the printed source path and confirm SKILL.md is missing or misnamed (e.g. skill.md, SKILL.MD)","Restore SKILL.md (restore from backup or reinstall/re-import the skill to rewrite the whole SSOT copy)","If the directory is junk, delete it and remove the DB row so sync stops attempting it"],"exampleFix":"// before\nservice.sync_to_app_dir(&directory, &app)?; // Skill 源目录缺少 SKILL.md\n\n// after\nlet source = ssot_dir.join(&directory);\nif !source.join(\"SKILL.md\").is_file() {\n    return reinstall_skill(&directory); // repopulate manifest before syncing\n}\nservice.sync_to_app_dir(&directory, &app)?;","handlingStrategy":"validation","validationCode":"// Rust caller — require the manifest before sync\nlet source = ssot_dir.join(&directory);\nif !source.join(\"SKILL.md\").is_file() {\n    return Err(anyhow!(\"skill source incomplete (no SKILL.md): {}\", source.display()));\n}","typeGuard":null,"tryCatchPattern":"match service.sync_to_app_dir(&dir, &app) {\n    Err(e) if e.to_string().contains(\"缺少 SKILL.md\") => repair_or_reinstall(&dir).await,\n    other => other,\n}","preventionTips":["Keep SKILL.md as the first file written during any custom packaging step","Don't rename SKILL.md when editing skills; frontmatter edits belong inside the file","Treat a source dir without SKILL.md as corrupt and rebuild it from the original install"],"tags":["skill","sync","filesystem","manifest"],"backgroundTag":null,"analyzedSha":"a2e22f330273a5b6ffa87cb8b82b624601bac562","analyzedAt":"2026-08-16T03:46:07.889Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}