Install preset from a local directory. Args: source_dir: Path to preset directory speckit_version: Current spec-kit version priority: Resolution priority (lower = higher precedence, default 10) Returns: Installed preset manifest
(
self,
source_dir: Path,
speckit_version: str,
priority: int = 10,
)
| 1499 | shutil.rmtree(skill_subdir) |
| 1500 | |
| 1501 | def install_from_directory( |
| 1502 | self, |
| 1503 | source_dir: Path, |
| 1504 | speckit_version: str, |
| 1505 | priority: int = 10, |
| 1506 | ) -> PresetManifest: |
| 1507 | """Install preset from a local directory. |
| 1508 | |
| 1509 | Args: |
| 1510 | source_dir: Path to preset directory |
| 1511 | speckit_version: Current spec-kit version |
| 1512 | priority: Resolution priority (lower = higher precedence, default 10) |
| 1513 | |
| 1514 | Returns: |
| 1515 | Installed preset manifest |
| 1516 | |
| 1517 | Raises: |
| 1518 | PresetValidationError: If manifest is invalid or priority is invalid |
| 1519 | PresetCompatibilityError: If pack is incompatible |
| 1520 | """ |
| 1521 | # Validate priority |
| 1522 | if priority < 1: |
| 1523 | raise PresetValidationError("Priority must be a positive integer (1 or higher)") |
| 1524 | |
| 1525 | manifest_path = source_dir / "preset.yml" |
| 1526 | manifest = PresetManifest(manifest_path) |
| 1527 | |
| 1528 | self.check_compatibility(manifest, speckit_version) |
| 1529 | |
| 1530 | if self.registry.is_installed(manifest.id): |
| 1531 | raise PresetError( |
| 1532 | f"Preset '{manifest.id}' is already installed. " |
| 1533 | f"Use 'specify preset remove {manifest.id}' first." |
| 1534 | ) |
| 1535 | |
| 1536 | dest_dir = self.presets_dir / manifest.id |
| 1537 | if dest_dir.exists(): |
| 1538 | shutil.rmtree(dest_dir) |
| 1539 | |
| 1540 | shutil.copytree(source_dir, dest_dir) |
| 1541 | |
| 1542 | # Pre-register the preset so that composition resolution can see it |
| 1543 | # in the priority stack when resolving composed command content. |
| 1544 | self.registry.add(manifest.id, { |
| 1545 | "version": manifest.version, |
| 1546 | "source": "local", |
| 1547 | "manifest_hash": manifest.get_hash(), |
| 1548 | "enabled": True, |
| 1549 | "priority": priority, |
| 1550 | "registered_commands": {}, |
| 1551 | "registered_skills": [], |
| 1552 | }) |
| 1553 | |
| 1554 | registered_commands: Dict[str, List[str]] = {} |
| 1555 | registered_skills: List[str] = [] |
| 1556 | try: |
| 1557 | # Register command overrides with AI agents and persist the result |
| 1558 | # immediately so cleanup can recover even if installation stops |