@@ -68,7 +68,11 @@ def execute_repository(config: ScanConfig) -> ScanExecution:
6868 state_store = StateStore (output_dir )
6969 previous_state = state_store .load ()
7070 changed_diffs = _detect_changed_diffs (config , previous_state , git_client )
71- changed_files = _changed_paths_from_diffs (changed_diffs )
71+ changed_files = _changed_paths_from_diffs (
72+ changed_diffs = changed_diffs ,
73+ repo_path = repo_path ,
74+ output_dir = output_dir ,
75+ )
7276 effective_mode , notes = _choose_effective_mode (
7377 config = config ,
7478 previous_state = previous_state ,
@@ -155,8 +159,14 @@ def execute_repository(config: ScanConfig) -> ScanExecution:
155159 patch_cost = cost_estimator .estimate_incremental_patch (rewrite_cost )
156160
157161 skill_artifacts : dict [str , str ] = {}
162+ generated_skill_names : list [str ] = []
163+ planned_skill_names : list [str ] = []
158164 if _should_run_skill_pipeline (config ):
159- skill_artifacts = _build_skill_artifacts (
165+ (
166+ skill_artifacts ,
167+ generated_skill_names ,
168+ planned_skill_names ,
169+ ) = _build_skill_artifacts (
160170 config = config ,
161171 effective_mode = effective_mode ,
162172 repo_path = repo_path ,
@@ -177,6 +187,13 @@ def execute_repository(config: ScanConfig) -> ScanExecution:
177187 output_dir = output_dir ,
178188 rendered_artifacts = rendered_artifacts ,
179189 )
190+ if planned_skill_names :
191+ updated_files .extend (
192+ _prune_stale_skill_files (
193+ output_dir = output_dir ,
194+ planned_skill_names = planned_skill_names ,
195+ )
196+ )
180197
181198 report = _build_report (
182199 config = config ,
@@ -188,6 +205,7 @@ def execute_repository(config: ScanConfig) -> ScanExecution:
188205 changed_files = changed_files ,
189206 affected_files = affected_files ,
190207 affected_skills = affected_skills ,
208+ generated_skills = generated_skill_names ,
191209 written_files = written_files ,
192210 updated_files = updated_files ,
193211 head_commit = git_client .current_head () if git_client .is_repository () else None ,
@@ -232,6 +250,7 @@ def execute_repository(config: ScanConfig) -> ScanExecution:
232250 changed_files = changed_files ,
233251 affected_files = affected_files ,
234252 affected_skills = affected_skills ,
253+ generated_skills = generated_skill_names ,
235254 report_path = report_path ,
236255 report = report ,
237256 )
@@ -615,8 +634,50 @@ def _detect_changed_diffs(
615634 return []
616635
617636
618- def _changed_paths_from_diffs (changed_diffs : list [FileDiffPatch ]) -> list [str ]:
619- return sorted ({item .path for item in changed_diffs })
637+ def _changed_paths_from_diffs (
638+ changed_diffs : list [FileDiffPatch ],
639+ repo_path : Path ,
640+ output_dir : Path ,
641+ ) -> list [str ]:
642+ return sorted (
643+ {
644+ item .path
645+ for item in changed_diffs
646+ if item .path
647+ and not _is_generated_artifact_path (
648+ path = item .path ,
649+ repo_path = repo_path ,
650+ output_dir = output_dir ,
651+ )
652+ }
653+ )
654+
655+
656+ def _is_generated_artifact_path (
657+ path : str ,
658+ repo_path : Path ,
659+ output_dir : Path ,
660+ ) -> bool :
661+ normalized = Path (path ).as_posix ()
662+ first_part = normalized .split ("/" , 1 )[0 ]
663+ if first_part .startswith (".code2skill" ):
664+ return True
665+ if normalized in {"AGENTS.md" , "CLAUDE.md" , ".windsurfrules" }:
666+ return True
667+ if normalized == ".github/copilot-instructions.md" :
668+ return True
669+ if normalized == ".cursor/rules" or normalized .startswith (".cursor/rules/" ):
670+ return True
671+
672+ try :
673+ relative_output_dir = output_dir .relative_to (repo_path ).as_posix ()
674+ except ValueError :
675+ return False
676+
677+ return (
678+ normalized == relative_output_dir
679+ or normalized .startswith (f"{ relative_output_dir } /" )
680+ )
620681
621682
622683def _choose_effective_mode (
@@ -764,7 +825,7 @@ def _build_skill_artifacts(
764825 changed_diffs : list [FileDiffPatch ],
765826 affected_files : list [str ],
766827 affected_skill_names : list [str ],
767- ) -> dict [str , str ]:
828+ ) -> tuple [ dict [str , str ], list [ str ], list [ str ] ]:
768829 from .skill_generator import SkillGenerator , match_planned_skills
769830 from .skill_planner import SkillPlanner , load_skill_plan , render_skill_plan
770831
@@ -795,7 +856,8 @@ def _build_skill_artifacts(
795856 "skill-plan.json" : render_skill_plan (plan ),
796857 }
797858 artifacts .update (generator .generate_all (blueprint = blueprint , plan = plan ))
798- return artifacts
859+ planned_names = [skill .name for skill in plan .skills ]
860+ return artifacts , planned_names , planned_names
799861
800862 try :
801863 plan = load_skill_plan (plan_path )
@@ -805,10 +867,12 @@ def _build_skill_artifacts(
805867 "skill-plan.json" : render_skill_plan (plan ),
806868 }
807869 artifacts .update (generator .generate_all (blueprint = blueprint , plan = plan ))
808- return artifacts
870+ planned_names = [skill .name for skill in plan .skills ]
871+ return artifacts , planned_names , planned_names
809872
810873 artifacts : dict [str , str ] = {}
811874 plan_skill_names = {skill .name for skill in plan .skills }
875+ planned_names = [skill .name for skill in plan .skills ]
812876 present_skills = [
813877 name for name in affected_skill_names
814878 if name in plan_skill_names
@@ -823,15 +887,13 @@ def _build_skill_artifacts(
823887 plan = planner .plan (blueprint = blueprint , repo_path = repo_path )
824888 artifacts ["skill-plan.json" ] = render_skill_plan (plan )
825889 plan_skill_names = {skill .name for skill in plan .skills }
826- affected_skill_names = [
827- name for name in affected_skill_names
828- if name in plan_skill_names
829- ]
830- if not affected_skill_names :
831- affected_skill_names = match_planned_skills (affected_files , plan )
890+ planned_names = [skill .name for skill in plan .skills ]
891+ affected_skill_names = match_planned_skills (affected_files , plan )
892+ artifacts .update (generator .generate_all (blueprint = blueprint , plan = plan ))
893+ return artifacts , planned_names , planned_names
832894
833895 if not affected_skill_names :
834- return artifacts
896+ return artifacts , [], planned_names
835897
836898 artifacts .update (
837899 generator .generate_incremental (
@@ -843,7 +905,7 @@ def _build_skill_artifacts(
843905 previous_state = previous_state ,
844906 )
845907 )
846- return artifacts
908+ return artifacts , affected_skill_names , planned_names
847909
848910
849911def build_llm_backend (provider : str , model : str | None = None ):
@@ -871,6 +933,25 @@ def _write_outputs(
871933 return written_files , updated_files
872934
873935
936+ def _prune_stale_skill_files (
937+ output_dir : Path ,
938+ planned_skill_names : list [str ],
939+ ) -> list [Path ]:
940+ skills_dir = output_dir / "skills"
941+ if not skills_dir .exists ():
942+ return []
943+
944+ keep = {f"{ name } .md" for name in planned_skill_names }
945+ keep .add ("index.md" )
946+ removed : list [Path ] = []
947+ for path in skills_dir .glob ("*.md" ):
948+ if path .name in keep :
949+ continue
950+ path .unlink (missing_ok = True )
951+ removed .append (path )
952+ return removed
953+
954+
874955def _build_report (
875956 config : ScanConfig ,
876957 effective_mode : str ,
@@ -881,6 +962,7 @@ def _build_report(
881962 changed_files : list [str ],
882963 affected_files : list [str ],
883964 affected_skills : list [str ],
965+ generated_skills : list [str ],
884966 written_files : list [Path ],
885967 updated_files : list [Path ],
886968 head_commit : str | None ,
@@ -914,6 +996,7 @@ def _build_report(
914996 changed_files = changed_files ,
915997 affected_files = affected_files ,
916998 affected_skills = affected_skills ,
999+ generated_skills = generated_skills ,
9171000 ),
9181001 first_generation_cost = first_generation_cost ,
9191002 incremental_rewrite_cost = rewrite_cost ,
0 commit comments