mirror of
https://github.com/github/spec-kit.git
synced 2026-03-20 12:23:09 +00:00
* feat(presets): add enable/disable toggle and update semantics Add preset enable/disable CLI commands and update semantics to match the extension system capabilities. Changes: - Add `preset enable` and `preset disable` CLI commands - Add `restore()` method to PresetRegistry for rollback scenarios - Update `get()` and `list()` to return deep copies (prevents mutation) - Update `list_by_priority()` to filter disabled presets by default - Add input validation to `restore()` for defensive programming - Add 16 new tests covering all functionality and edge cases Closes #1851 Closes #1852 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review - deep copy and error message accuracy - Fix error message in restore() to match actual validation ("dict" not "non-empty dict") - Use copy.deepcopy() in restore() to prevent caller mutation - Apply same fixes to ExtensionRegistry for parity - Add /defensive-check command for pre-PR validation - Add tests for restore() validation and deep copy behavior Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * revert: remove defensive-check command from PR * fix: address PR review - clarify messaging and add parity - Add note to enable/disable output clarifying commands/skills remain active - Add include_disabled parameter to ExtensionRegistry.list_by_priority for parity - Add tests for extension disabled filtering Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review - disabled extension resolution and corrupted entries - Fix _get_all_extensions_by_priority to use include_disabled=True for tracking registered IDs, preventing disabled extensions from being picked up as unregistered directories - Add corrupted entry handling to get() - returns None for non-dict entries - Add integration tests for disabled extension template resolution - Add tests for get() corrupted entry handling in both registries Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: handle corrupted registry in list() methods - Add defensive handling to list() when presets/extensions is not a dict - Return empty dict instead of crashing on corrupted registry - Apply same fix to both PresetRegistry and ExtensionRegistry for parity - Add tests for corrupted registry handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: validate top-level registry structure in get() and restore() - get() now validates self.data["presets/extensions"] is a dict before accessing - restore() ensures presets/extensions dict exists before writing - Prevents crashes when registry JSON is parseable but has corrupted structure - Applied same fixes to both PresetRegistry and ExtensionRegistry for parity Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: validate root-level JSON structure in _load() and is_installed() - _load() now validates json.load() result is a dict before returning - is_installed() validates presets/extensions is a dict before checking membership - Prevents crashes when registry file is valid JSON but wrong type (e.g., array) - Applied same fixes to both registries for parity Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: normalize presets/extensions field in _load() - _load() now normalizes the presets/extensions field to {} if not a dict - Makes corrupted registries recoverable for add/update/remove operations - Applied same fix to both registries for parity Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use raw registry keys to track corrupted extensions - Use registry.list().keys() instead of list_by_priority() for tracking - Corrupted entries are now treated as tracked, not picked up as unregistered - Tighten test assertion for disabled preset resolution - Update test to match new expected behavior for corrupted entries Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: handle None metadata in ExtensionManager.remove() - Add defensive check for corrupted metadata in remove() - Match existing pattern in PresetManager.remove() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add keys() method and filter corrupted entries in list() - Add lightweight keys() method that returns IDs without deep copy - Update list() to filter out non-dict entries (match type contract) - Use keys() instead of list().keys() for performance - Fix comment to reflect actual behavior Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address defensive-check findings - deep copy, corruption guards, parity - Extension enable/disable: use delta pattern matching presets - add(): use copy.deepcopy(metadata) in both registries - remove(): guard outer field for corruption in both registries - update(): guard outer field for corruption in both registries Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: deep copy updates in update() to prevent caller mutation Both PresetRegistry.update() and ExtensionRegistry.update() now deep copy the input updates/metadata dict to prevent callers from mutating nested objects after the call. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: iamaeroplane <michal.bachorik@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>