Skip to content

Commit aa1df03

Browse files
iamaeroplaneclaude
andcommitted
fix: address second round of review feedback
- Make chmod best-effort (try/except OSError) so permission edge cases don't abort extension installation - Filter overrides by name pattern in list_available(): commands must contain dots, templates must not, preventing cross-contamination in the shared overrides directory Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d6e0773 commit aa1df03

File tree

2 files changed

+30
-9
lines changed

2 files changed

+30
-9
lines changed

src/specify_cli/extensions.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,12 +628,15 @@ def install_from_directory(
628628
ignore_fn = self._load_extensionignore(source_dir)
629629
shutil.copytree(source_dir, dest_dir, ignore=ignore_fn)
630630

631-
# Set execute permissions on extension scripts (POSIX only)
631+
# Set execute permissions on extension scripts (POSIX only, best-effort)
632632
if os.name == "posix":
633633
for script in manifest.scripts:
634634
script_path = dest_dir / script["file"]
635635
if script_path.exists() and script_path.suffix == ".sh":
636-
script_path.chmod(script_path.stat().st_mode | 0o111)
636+
try:
637+
script_path.chmod(script_path.stat().st_mode | 0o111)
638+
except OSError:
639+
pass
637640

638641
# Register commands with AI agents
639642
registered_commands = {}

src/specify_cli/presets.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,20 +1703,38 @@ def list_available(
17031703
else: # script
17041704
subdirs = ["scripts"]
17051705

1706+
def _name_matches_type(name: str) -> bool:
1707+
"""Check if a file name matches the expected pattern for the template type.
1708+
1709+
Commands use dot notation (e.g. speckit.specify), templates use
1710+
hyphens only (e.g. spec-template). This prevents the shared
1711+
overrides directory from leaking commands into template listings
1712+
or vice versa. Scripts live in their own subdirectory so no
1713+
filtering is needed.
1714+
"""
1715+
if template_type == "command":
1716+
return "." in name
1717+
if template_type == "template":
1718+
return "." not in name
1719+
return True
1720+
17061721
def _collect(directory: Path, source: str):
17071722
"""Collect template files from a directory."""
17081723
if not directory.is_dir():
17091724
return
17101725
for f in sorted(directory.iterdir()):
17111726
if f.is_file() and f.suffix == ext:
17121727
name = f.stem
1713-
if name not in seen:
1714-
seen.add(name)
1715-
results.append({
1716-
"name": name,
1717-
"path": str(f),
1718-
"source": source,
1719-
})
1728+
if name in seen:
1729+
continue
1730+
if not _name_matches_type(name):
1731+
continue
1732+
seen.add(name)
1733+
results.append({
1734+
"name": name,
1735+
"path": str(f),
1736+
"source": source,
1737+
})
17201738

17211739
# Priority 1: Project-local overrides
17221740
if template_type == "script":

0 commit comments

Comments
 (0)