* refactor(plugins): split PluginStoreManager into mixins
src/plugin_system/store_manager.py (2,977 lines) keeps the class, its
shared state, locks, the uninstall registry, directory lookup and
uninstall; its methods are split by area into:
- store_registry.py (_RegistryMixin): registry, GitHub metadata, search,
manifest validation
- store_install.py (_InstallMixin): install paths and dependencies
- store_update.py (_UpdateMixin): updates, rollback, local git state
Pure move: all 56 members are byte-identical (checked with ast) and the
assembled class has exactly the same attributes as before (checked at
runtime). PluginStoreManager is imported from store_manager.py as before.
Tests that patched shared modules (subprocess, requests, tempfile, shutil)
through store_manager now reach them through the module whose code they
exercise; a source-text contract test reads all store_*.py modules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: annotate findings the split moved into new store modules
subprocess imports and a list-form git clone (no shell), and the config
template's placeholder token string -- existing code that Codacy reported
as new because it moved. Annotated with the repo's nosec/nosemgrep style.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: annotate the default-branch git clone the split moved
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- load_plugin: an on_enable() that raises unregisters the instance, so the
next load retries instead of returning True "already loaded".
- get_plugin_info: guard plugin.get_info(); one plugin raising no longer
breaks /api/v3/plugins/installed.
- plugin_state.json and the operation history are written with
atomic_write_text under their lock.
- plugin_loader: module-level lock serialises pip installs across the
parallel startup loaders.
- store_manager._install_via_download: extract dir cleanup moved to finally.
- Test doubles: draw_image() warns (DeprecationWarning; the real
DisplayManager has none), MockDisplayManager.draw_text accepts the real
signature's optional params, VisualTestDisplayManager logs draw errors at
WARNING.
- Docs/comments: compatibility.py method name, PluginState.LOADED meaning,
brittle schema count, why _report_skip_once uses setdefault.
- Remove unused PluginOperationQueue.get_active_operations().
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>