Plugin Dependencies
A plugin can declare ordered dependencies on other plugins. The loader resolves them topologically before falling back to the existing priority integer for tie-breaks within each layer.
See also: Plugin Distribution, Backend Hooks.
Why
Hook order is observable: an onContextReady handler that reads branch_overrides only sees a useful value if branch_switching has already populated it. Before this field, ordering was coupled to a single global priority: int shared by every plugin — collisions were silent and forced magic numbers.
Declaring dependencies: ["branch_switching"] makes the constraint explicit, self-documenting, and verified at load time.
Where to Declare
Two equivalent surfaces:
1. Distributable Plugin (plugin.yaml)
name: my_plugin
version: 1.0.0
has_backend: true
entry_module: my_plugin.plugin
entry_class: MyPlugin
priority: 50
dependencies:
- branch_switching
- permission_bridge
The dependencies field is part of PluginPackageManifest (src/codumentor/plugins/package.py). Default is [] — existing manifests need no change.
2. Built-in or Inline (meta)
class MyPlugin:
meta = {
"name": "my_plugin",
"version": "1.0.0",
"priority": 50,
"dependencies": ["branch_switching"],
}
The loader reads meta["dependencies"] for in-tree plugins. Same semantics as plugin.yaml.
3. Per-deployment Override (codumentor.yaml)
plugins:
- module: codumentor.plugins.my_plugin
class: MyPlugin
dependencies: ["branch_switching"]
A dependencies entry in codumentor.yaml overrides whatever the plugin's meta (or external manifest) declared. Use this sparingly — it lets a deployment fix a load-order bug without rebuilding the plugin, but it also drifts from upstream.
Resolution Rules
- Topological sort first. Each plugin appears after every plugin it depends on, transitively.
- Priority within a layer. Plugins whose dependencies are all satisfied at the same point sort by
priorityinteger (higher runs first), preserving the pre-existing behaviour for plugins that don't declaredependencies. - Names match
meta["name"]. A dependency string"branch_switching"must match another loaded plugin'smeta["name"](or external manifestname).
Failure Modes
The loader fails fast at startup with a clear error:
| Situation | Error |
|---|---|
| Declared dependency is not loaded | Plugin 'my_plugin' declares dependency 'foo', which is not among the loaded plugins. |
| Dependency graph has a cycle | Plugin dependency cycle detected: a -> b -> a |
If the missing plugin is optional, use a soft check at runtime instead of declaring it as a dependency. For example, peek at ctx["scratchpad"] for a key the other plugin populates, and degrade gracefully when absent.
When Not to Use
- Pure event listeners with no shared state. If your hook does its work entirely from
payload, ordering doesn't matter — leavedependenciesempty and letprioritydecide. - Optional integrations. If your plugin works fine without plugin X but enriches its output when X is present, don't declare X as a dependency. The loader treats missing dependencies as a fatal error.
Example: file_links → branch_switching (deferred)
file_links will eventually merge with the permalink rewrite pipeline so locally-modified or generated files get correct download links. That merger consumes branch_overrides from branch_switching, so the merged plugin will declare:
meta = {
"name": "file_links",
"dependencies": ["branch_switching"],
}
Today file_links runs alone with dependencies: [] because the consumer code isn't wired up yet — declaring it now would force every selfdev config (which often runs without branch_switching) to error at load. The loader treats missing dependencies as fatal, so add the entry only when the consumer code is in place.