Derive a new skill from one or more existing skills. **Single parent** (``source_dirs`` is one Path): Copies parent directory → applies LLM output. Supports PATCH / DIFF / FULL. **Multiple parents** (``source_dirs`` is a list of Paths): Creates a brand-new directory and applie
(
source_dirs: Union[Path, List[Path]],
target_dir: Path,
content: str,
patch_type: PatchType = PatchType.AUTO,
)
| 179 | ) |
| 180 | |
| 181 | def derive_skill( |
| 182 | source_dirs: Union[Path, List[Path]], |
| 183 | target_dir: Path, |
| 184 | content: str, |
| 185 | patch_type: PatchType = PatchType.AUTO, |
| 186 | ) -> SkillEditResult: |
| 187 | """Derive a new skill from one or more existing skills. |
| 188 | |
| 189 | **Single parent** (``source_dirs`` is one Path): |
| 190 | Copies parent directory → applies LLM output. Supports PATCH / DIFF / FULL. |
| 191 | |
| 192 | **Multiple parents** (``source_dirs`` is a list of Paths): |
| 193 | Creates a brand-new directory and applies LLM output as FULL or PATCH |
| 194 | (DIFF not supported for multi-parent — no single base to search/replace). |
| 195 | ``content_diff`` is empty for multi-parent (no meaningful single-parent |
| 196 | diff); ``content_snapshot`` captures the full result for lineage tracking. |
| 197 | |
| 198 | Source directories stay unchanged in both cases. |
| 199 | |
| 200 | Args: |
| 201 | source_dirs: Parent skill directory (or list for multi-parent merge). |
| 202 | target_dir: New skill directory (must not exist). |
| 203 | content: LLM output. |
| 204 | patch_type: Output format (AUTO to auto-detect). |
| 205 | """ |
| 206 | # Normalise to list |
| 207 | if isinstance(source_dirs, Path): |
| 208 | sources = [source_dirs] |
| 209 | else: |
| 210 | sources = list(source_dirs) |
| 211 | |
| 212 | if not sources: |
| 213 | return SkillEditResult(error="derive_skill requires at least one source directory") |
| 214 | if target_dir.exists(): |
| 215 | return SkillEditResult(error=f"Target already exists: {target_dir}") |
| 216 | |
| 217 | # Validate all sources |
| 218 | for sd in sources: |
| 219 | if not sd.is_dir(): |
| 220 | return SkillEditResult(error=f"Source does not exist: {sd}") |
| 221 | if not (sd / SKILL_FILENAME).exists(): |
| 222 | return SkillEditResult(error=f"Source SKILL.md not found: {sd / SKILL_FILENAME}") |
| 223 | |
| 224 | first_source = sources[0] |
| 225 | is_multi_parent = len(sources) > 1 |
| 226 | |
| 227 | if is_multi_parent: |
| 228 | # Multi-parent merge: create new directory, apply content (FULL or PATCH) |
| 229 | if patch_type == PatchType.AUTO: |
| 230 | patch_type = detect_patch_type(content) |
| 231 | if patch_type == PatchType.DIFF: |
| 232 | # DIFF (SEARCH/REPLACE) is not meaningful for merge — no single base |
| 233 | patch_type = PatchType.FULL |
| 234 | |
| 235 | try: |
| 236 | target_dir.mkdir(parents=True, exist_ok=True) |
| 237 | if patch_type == PatchType.PATCH: |
| 238 | _apply_multi_file_patch(content, target_dir) |
no test coverage detected