Decision Records
ADR-0006: Plugin host for detectors & actions
Architecture Decision Record
- Status: accepted
- Date: 2026-06-15
- Deciders: project owner
Context
The original app mixed generic clipboard features with personal-workflow ones (Claude-session detection, Jira/GitHub actions, terminal/repo helpers) full of hardcoded user paths. The public rewrite must stay generic, yet still let those personal features exist — without shipping them or their paths in the public repo.
Decision
- Extend through two contracts (
IClipDetector,IClipActioninClipwell.Protocol.Plugins) rather than baking features in. Built-ins ship in the core; everything else is a plugin. PluginLoader.Load<T>(dir)Assembly.LoadFroms each DLL in the plugins folder and instantiates concreteTs with a parameterless ctor. Failures are skipped.- Two-process loading. Detectors load in the daemon (it classifies); actions load in the picker (it shows the Ctrl+K palette). Both scan the same folder.
- Single-DLL plugins. A plugin references
Clipwell.ProtocolwithPrivate="false"so it binds to the host's loaded protocol assembly — preserving interface type identity (a second copy would not match). - Actions get a context (
IClipActionContext) for side effects, so they never depend on the UI and can run from a plugin unchanged. - Plugins dir:
<data dir>/plugins, overrideCLIPWELL_PLUGINS_DIR.
Consequences
- The public repo ships only the contracts + a
plugins/sampleexample; personal features move to a separate private plugin with paths in plugin config, not the public tree. - Verified end-to-end: the sample's
TodoDetector(loaded by the daemon) classifies "TODO …" as a customtodokind, and itsShoutActionappears in the palette — with no core changes. - Trade-offs:
Assembly.LoadFromruns plugin code in-process (trusted-plugin model, no sandbox); plugins are not hot-reloaded (loaded at startup); and a plugin must be rebuilt against a compatibleClipwell.Protocolwhen the contract changes.