In-place repair of an existing skill directory. Applies the LLM output to the skill directory, overwrites files on disk, returns a combined diff and snapshot for lineage recording. Args: skill_dir: Existing skill directory. content: LLM output (FULL, DIFF, or PATCH form
(
skill_dir: Path,
content: str,
patch_type: PatchType = PatchType.AUTO,
)
| 124 | |
| 125 | |
| 126 | def fix_skill( |
| 127 | skill_dir: Path, |
| 128 | content: str, |
| 129 | patch_type: PatchType = PatchType.AUTO, |
| 130 | ) -> SkillEditResult: |
| 131 | """In-place repair of an existing skill directory. |
| 132 | |
| 133 | Applies the LLM output to the skill directory, overwrites files on disk, |
| 134 | returns a combined diff and snapshot for lineage recording. |
| 135 | |
| 136 | Args: |
| 137 | skill_dir: Existing skill directory. |
| 138 | content: LLM output (FULL, DIFF, or PATCH format). |
| 139 | patch_type: Output format (AUTO to auto-detect). |
| 140 | """ |
| 141 | if not skill_dir.is_dir(): |
| 142 | return SkillEditResult(error=f"Skill directory not found: {skill_dir}") |
| 143 | skill_file = skill_dir / SKILL_FILENAME |
| 144 | if not skill_file.exists(): |
| 145 | return SkillEditResult(error=f"SKILL.md not found: {skill_file}") |
| 146 | |
| 147 | # Snapshot before edit |
| 148 | old_files = _collect_files(skill_dir) |
| 149 | |
| 150 | # Resolve patch type |
| 151 | if patch_type == PatchType.AUTO: |
| 152 | patch_type = detect_patch_type(content) |
| 153 | |
| 154 | try: |
| 155 | if patch_type == PatchType.PATCH: |
| 156 | _apply_multi_file_patch(content, skill_dir) |
| 157 | elif patch_type == PatchType.FULL: |
| 158 | _apply_multi_file_full(content, skill_dir) |
| 159 | elif patch_type == PatchType.DIFF: |
| 160 | _apply_search_replace_to_file(content, skill_file) |
| 161 | else: |
| 162 | return SkillEditResult(error=f"Unknown patch type: {patch_type}") |
| 163 | except PatchError as e: |
| 164 | return SkillEditResult(error=str(e)) |
| 165 | except Exception as e: |
| 166 | return SkillEditResult(error=f"Unexpected error: {e}") |
| 167 | |
| 168 | _normalize_skill_frontmatter(skill_dir) |
| 169 | |
| 170 | # Snapshot after edit |
| 171 | new_files = _collect_files(skill_dir) |
| 172 | diff = _compute_files_diff(old_files, new_files) |
| 173 | |
| 174 | logger.info(f"fix_skill: {skill_dir.name} ({patch_type.value})") |
| 175 | return SkillEditResult( |
| 176 | skill_dir=skill_dir, |
| 177 | content_diff=diff, |
| 178 | content_snapshot=new_files, |
| 179 | ) |
| 180 | |
| 181 | def derive_skill( |
| 182 | source_dirs: Union[Path, List[Path]], |
no test coverage detected