fix: address Copilot PR review comments (round 2)

- Fix init --preset error masking: distinguish "not found" from real errors
- Fix bash resolve_template: skip hidden dirs in extensions (match Python/PS)
- Fix temp dir leaks in tests: use temp_dir fixture instead of mkdtemp
- Fix self-test catalog entry: add note that it's local-only (no download_url)
- Fix Windows path issue in resolve_with_source: use Path.relative_to()
- Fix skill restore path: use project's .specify/templates/commands/ not source tree
- Add encoding="utf-8" to all file read/write in agents.py
- Update test to set up core command templates for skill restoration
This commit is contained in:
Manfred Riem
2026-03-10 16:35:17 -05:00
parent 3ffef55954
commit da6e7d2283
6 changed files with 45 additions and 25 deletions

View File

@@ -1600,17 +1600,21 @@ def init(
preset_manager.install_from_directory(local_path, speckit_ver)
else:
preset_catalog = PresetCatalog(project_path)
try:
zip_path = preset_catalog.download_pack(preset)
preset_manager.install_from_zip(zip_path, speckit_ver)
# Clean up downloaded ZIP to avoid cache accumulation
try:
zip_path.unlink(missing_ok=True)
except OSError:
# Best-effort cleanup; failure to delete is non-fatal
pass
except PresetError:
pack_info = preset_catalog.get_pack_info(preset)
if not pack_info:
console.print(f"[yellow]Warning:[/yellow] Preset '{preset}' not found in catalog. Skipping.")
else:
try:
zip_path = preset_catalog.download_pack(preset)
preset_manager.install_from_zip(zip_path, speckit_ver)
# Clean up downloaded ZIP to avoid cache accumulation
try:
zip_path.unlink(missing_ok=True)
except OSError:
# Best-effort cleanup; failure to delete is non-fatal
pass
except PresetError as preset_err:
console.print(f"[yellow]Warning:[/yellow] Failed to install preset '{preset}': {preset_err}")
except Exception as preset_err:
console.print(f"[yellow]Warning:[/yellow] Failed to install preset: {preset_err}")

View File

@@ -301,7 +301,7 @@ class CommandRegistrar:
if not source_file.exists():
continue
content = source_file.read_text()
content = source_file.read_text(encoding="utf-8")
frontmatter, body = self.parse_frontmatter(content)
frontmatter = self._adjust_script_paths(frontmatter)
@@ -318,7 +318,7 @@ class CommandRegistrar:
raise ValueError(f"Unsupported format: {agent_config['format']}")
dest_file = commands_dir / f"{cmd_name}{agent_config['extension']}"
dest_file.write_text(output)
dest_file.write_text(output, encoding="utf-8")
if agent_name == "copilot":
self.write_copilot_prompt(project_root, cmd_name)
@@ -327,7 +327,7 @@ class CommandRegistrar:
for alias in cmd_info.get("aliases", []):
alias_file = commands_dir / f"{alias}{agent_config['extension']}"
alias_file.write_text(output)
alias_file.write_text(output, encoding="utf-8")
if agent_name == "copilot":
self.write_copilot_prompt(project_root, alias)
registered.append(alias)
@@ -345,7 +345,7 @@ class CommandRegistrar:
prompts_dir = project_root / ".github" / "prompts"
prompts_dir.mkdir(parents=True, exist_ok=True)
prompt_file = prompts_dir / f"{cmd_name}.prompt.md"
prompt_file.write_text(f"---\nagent: {cmd_name}\n---\n")
prompt_file.write_text(f"---\nagent: {cmd_name}\n---\n", encoding="utf-8")
def register_commands_for_all_agents(
self,

View File

@@ -565,9 +565,8 @@ class PresetManager:
from . import SKILL_DESCRIPTIONS
# Locate core command templates
script_dir = Path(__file__).parent.parent.parent # up from src/specify_cli/
core_templates_dir = script_dir / "templates" / "commands"
# Locate core command templates from the project's installed templates
core_templates_dir = self.project_root / ".specify" / "templates" / "commands"
for skill_name in skill_names:
# Derive command name from skill name (speckit-specify -> specify)
@@ -1458,20 +1457,29 @@ class PresetResolver:
if str(self.presets_dir) in resolved_str and self.presets_dir.exists():
registry = PresetRegistry(self.presets_dir)
for pack_id, _metadata in registry.list_by_priority():
if f"/{pack_id}/" in resolved_str:
pack_dir = self.presets_dir / pack_id
try:
resolved.relative_to(pack_dir)
meta = registry.get(pack_id)
version = meta.get("version", "?") if meta else "?"
return {
"path": resolved_str,
"source": f"{pack_id} v{version}",
}
except ValueError:
continue
if str(self.extensions_dir) in resolved_str and self.extensions_dir.exists():
if self.extensions_dir.exists():
for ext_dir in sorted(self.extensions_dir.iterdir()):
if ext_dir.is_dir() and f"/{ext_dir.name}/" in resolved_str:
if not ext_dir.is_dir() or ext_dir.name.startswith("."):
continue
try:
resolved.relative_to(ext_dir)
return {
"path": resolved_str,
"source": f"extension:{ext_dir.name}",
}
except ValueError:
continue
return {"path": resolved_str, "source": "core"}