Commit 2bf655e
feat(presets): add enable/disable toggle and update semantics (#1891)
* 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>1 parent f679468 commit 2bf655e
File tree
5 files changed
+783
-54
lines changed- src/specify_cli
- tests
5 files changed
+783
-54
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2419 | 2419 | | |
2420 | 2420 | | |
2421 | 2421 | | |
| 2422 | + | |
| 2423 | + | |
| 2424 | + | |
| 2425 | + | |
| 2426 | + | |
| 2427 | + | |
| 2428 | + | |
| 2429 | + | |
| 2430 | + | |
| 2431 | + | |
| 2432 | + | |
| 2433 | + | |
| 2434 | + | |
| 2435 | + | |
| 2436 | + | |
| 2437 | + | |
| 2438 | + | |
| 2439 | + | |
| 2440 | + | |
| 2441 | + | |
| 2442 | + | |
| 2443 | + | |
| 2444 | + | |
| 2445 | + | |
| 2446 | + | |
| 2447 | + | |
| 2448 | + | |
| 2449 | + | |
| 2450 | + | |
| 2451 | + | |
| 2452 | + | |
| 2453 | + | |
| 2454 | + | |
| 2455 | + | |
| 2456 | + | |
| 2457 | + | |
| 2458 | + | |
| 2459 | + | |
| 2460 | + | |
| 2461 | + | |
| 2462 | + | |
| 2463 | + | |
| 2464 | + | |
| 2465 | + | |
| 2466 | + | |
| 2467 | + | |
| 2468 | + | |
| 2469 | + | |
| 2470 | + | |
| 2471 | + | |
| 2472 | + | |
| 2473 | + | |
| 2474 | + | |
| 2475 | + | |
| 2476 | + | |
| 2477 | + | |
| 2478 | + | |
| 2479 | + | |
| 2480 | + | |
| 2481 | + | |
| 2482 | + | |
| 2483 | + | |
| 2484 | + | |
| 2485 | + | |
| 2486 | + | |
| 2487 | + | |
| 2488 | + | |
| 2489 | + | |
| 2490 | + | |
| 2491 | + | |
| 2492 | + | |
| 2493 | + | |
| 2494 | + | |
| 2495 | + | |
| 2496 | + | |
| 2497 | + | |
| 2498 | + | |
| 2499 | + | |
| 2500 | + | |
| 2501 | + | |
| 2502 | + | |
| 2503 | + | |
| 2504 | + | |
2422 | 2505 | | |
2423 | 2506 | | |
2424 | 2507 | | |
| |||
3855 | 3938 | | |
3856 | 3939 | | |
3857 | 3940 | | |
3858 | | - | |
3859 | | - | |
| 3941 | + | |
3860 | 3942 | | |
3861 | 3943 | | |
3862 | 3944 | | |
| |||
3903 | 3985 | | |
3904 | 3986 | | |
3905 | 3987 | | |
3906 | | - | |
3907 | | - | |
| 3988 | + | |
3908 | 3989 | | |
3909 | 3990 | | |
3910 | 3991 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
222 | 222 | | |
223 | 223 | | |
224 | 224 | | |
225 | | - | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
226 | 236 | | |
227 | 237 | | |
228 | 238 | | |
| |||
244 | 254 | | |
245 | 255 | | |
246 | 256 | | |
247 | | - | |
| 257 | + | |
248 | 258 | | |
249 | 259 | | |
250 | 260 | | |
| |||
267 | 277 | | |
268 | 278 | | |
269 | 279 | | |
270 | | - | |
| 280 | + | |
| 281 | + | |
271 | 282 | | |
272 | 283 | | |
273 | | - | |
| 284 | + | |
274 | 285 | | |
275 | 286 | | |
276 | 287 | | |
277 | | - | |
278 | | - | |
| 288 | + | |
| 289 | + | |
279 | 290 | | |
280 | 291 | | |
281 | 292 | | |
282 | 293 | | |
283 | 294 | | |
284 | 295 | | |
285 | 296 | | |
286 | | - | |
| 297 | + | |
287 | 298 | | |
288 | 299 | | |
289 | 300 | | |
| |||
296 | 307 | | |
297 | 308 | | |
298 | 309 | | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
299 | 313 | | |
300 | | - | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
301 | 320 | | |
302 | 321 | | |
303 | 322 | | |
| |||
306 | 325 | | |
307 | 326 | | |
308 | 327 | | |
309 | | - | |
310 | | - | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
311 | 333 | | |
312 | 334 | | |
313 | 335 | | |
| |||
320 | 342 | | |
321 | 343 | | |
322 | 344 | | |
323 | | - | |
| 345 | + | |
324 | 346 | | |
325 | | - | |
326 | | - | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
327 | 355 | | |
328 | 356 | | |
329 | | - | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
330 | 377 | | |
331 | | - | |
332 | | - | |
| 378 | + | |
| 379 | + | |
333 | 380 | | |
334 | 381 | | |
335 | | - | |
| 382 | + | |
336 | 383 | | |
337 | | - | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
338 | 388 | | |
339 | 389 | | |
340 | 390 | | |
| |||
343 | 393 | | |
344 | 394 | | |
345 | 395 | | |
346 | | - | |
| 396 | + | |
347 | 397 | | |
348 | | - | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
349 | 402 | | |
350 | | - | |
| 403 | + | |
351 | 404 | | |
352 | 405 | | |
353 | 406 | | |
354 | 407 | | |
355 | 408 | | |
356 | 409 | | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
357 | 413 | | |
358 | 414 | | |
359 | 415 | | |
| |||
365 | 421 | | |
366 | 422 | | |
367 | 423 | | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
368 | 427 | | |
369 | 428 | | |
370 | 429 | | |
| |||
633 | 692 | | |
634 | 693 | | |
635 | 694 | | |
636 | | - | |
| 695 | + | |
637 | 696 | | |
638 | 697 | | |
639 | 698 | | |
| |||
0 commit comments