@@ -1039,7 +1039,8 @@ def install_ai_skills(project_path: Path, selected_ai: str, tracker: StepTracker
10391039 tracker: Optional progress tracker.
10401040
10411041 Returns:
1042- ``True`` if at least one skill was installed, ``False`` otherwise.
1042+ ``True`` if at least one skill was installed or all skills were
1043+ already present (idempotent re-run), ``False`` otherwise.
10431044 """
10441045 # Locate command templates in the agent's extracted commands directory.
10451046 # download_and_extract_template() already placed the .md files here.
@@ -1059,7 +1060,7 @@ def install_ai_skills(project_path: Path, selected_ai: str, tracker: StepTracker
10591060 if fallback_dir .exists () and any (fallback_dir .glob ("*.md" )):
10601061 templates_dir = fallback_dir
10611062
1062- if not templates_dir .exists ():
1063+ if not templates_dir .exists () or not any ( templates_dir . glob ( "*.md" )) :
10631064 if tracker :
10641065 tracker .error ("ai-skills" , "command templates not found" )
10651066 else :
@@ -1082,6 +1083,7 @@ def install_ai_skills(project_path: Path, selected_ai: str, tracker: StepTracker
10821083 tracker .start ("ai-skills" )
10831084
10841085 installed_count = 0
1086+ skipped_count = 0
10851087 for command_file in command_files :
10861088 try :
10871089 content = command_file .read_text (encoding = "utf-8" )
@@ -1116,20 +1118,26 @@ def install_ai_skills(project_path: Path, selected_ai: str, tracker: StepTracker
11161118 skill_dir .mkdir (parents = True , exist_ok = True )
11171119
11181120 # Select the best description available
1119- original_desc = frontmatter .get ("description" , "" ) if frontmatter else ""
1121+ original_desc = frontmatter .get ("description" , "" )
11201122 enhanced_desc = SKILL_DESCRIPTIONS .get (command_name , original_desc or f"Spec-kit workflow command: { command_name } " )
11211123
11221124 # Build SKILL.md following agentskills.io spec
11231125 # Use yaml.safe_dump to safely serialise the frontmatter and
11241126 # avoid YAML injection from descriptions containing colons,
11251127 # quotes, or newlines.
1128+ # Normalize source filename for metadata — strip speckit. prefix
1129+ # so it matches the canonical templates/commands/<cmd>.md path.
1130+ source_name = command_file .name
1131+ if source_name .startswith ("speckit." ):
1132+ source_name = source_name [len ("speckit." ):]
1133+
11261134 frontmatter_data = {
11271135 "name" : skill_name ,
11281136 "description" : enhanced_desc ,
11291137 "compatibility" : "Requires spec-kit project structure with .specify/ directory" ,
11301138 "metadata" : {
11311139 "author" : "github-spec-kit" ,
1132- "source" : f"templates/commands/{ command_file . name } " ,
1140+ "source" : f"templates/commands/{ source_name } " ,
11331141 },
11341142 }
11351143 frontmatter_text = yaml .safe_dump (frontmatter_data , sort_keys = False ).strip ()
@@ -1144,6 +1152,7 @@ def install_ai_skills(project_path: Path, selected_ai: str, tracker: StepTracker
11441152 skill_file = skill_dir / "SKILL.md"
11451153 if skill_file .exists ():
11461154 # Do not overwrite user-customized skills on re-runs
1155+ skipped_count += 1
11471156 continue
11481157 skill_file .write_text (skill_content , encoding = "utf-8" )
11491158 installed_count += 1
@@ -1153,17 +1162,23 @@ def install_ai_skills(project_path: Path, selected_ai: str, tracker: StepTracker
11531162 continue
11541163
11551164 if tracker :
1156- if installed_count > 0 :
1165+ if installed_count > 0 and skipped_count > 0 :
1166+ tracker .complete ("ai-skills" , f"{ installed_count } new + { skipped_count } existing skills in { skills_dir .relative_to (project_path )} " )
1167+ elif installed_count > 0 :
11571168 tracker .complete ("ai-skills" , f"{ installed_count } skills → { skills_dir .relative_to (project_path )} " )
1169+ elif skipped_count > 0 :
1170+ tracker .complete ("ai-skills" , f"{ skipped_count } skills already present" )
11581171 else :
11591172 tracker .error ("ai-skills" , "no skills installed" )
11601173 else :
11611174 if installed_count > 0 :
11621175 console .print (f"[green]✓[/green] Installed { installed_count } agent skills to { skills_dir .relative_to (project_path )} /" )
1176+ elif skipped_count > 0 :
1177+ console .print (f"[green]✓[/green] { skipped_count } agent skills already present in { skills_dir .relative_to (project_path )} /" )
11631178 else :
11641179 console .print ("[yellow]No skills were installed[/yellow]" )
11651180
1166- return installed_count > 0
1181+ return installed_count > 0 or skipped_count > 0
11671182
11681183
11691184@app .command ()
0 commit comments