Plugin Seams: Where Architecture Becomes Extractable¶
A seam is a contract plus the modules that implement it. It is simultaneously the shape that makes a component both extractable and pluggable — the same property viewed in two directions.
The Core Insight¶
If a group of modules depends on one declared contract and little else, you can lift them out whole. Whatever you lift out plugs back in against that same contract. This is the link between the "decompose" and "recompose" halves of the project.
Extractable and pluggable are the same structural property: - When extracting, you take the contract + implementers and leave behind the consumers - When recomposing, the consumers depend on the contract from outside — the plug point is exactly that dependency
Cohesion Predicts Outcome¶
Seams are ranked by cohesion: how self-contained the unit is.
Where:
- unit — the contract module plus every module implementing it
- escapes — dependencies of the unit that lie outside it, i.e. what would come along in an extraction
- cohesion ranges from 0.0 (everything escapes) to 1.0 (nothing does)
Verdicts¶
| Cohesion | Verdict | Meaning |
|---|---|---|
| ≥ 0.9 | clean | Extract with confidence; almost nothing extra comes along |
| ≥ 0.6 | extractable | Safe to extract, but some dependencies escape; assess their cost |
| < 0.6 | entangled | Declared as a plug point, but implementations are entangled with the rest of the codebase |
| 0 | declared-unused | Contract with zero implementers; not a plug point |
The score predicts the outcome. Evidence from this codebase:
langs/base.pyscores 0.88 (extractable) with exactly one escaping dependency. This is the component that was successfully extracted into a standalone pip-installable package; its one escape was precisely the module that extraction pulled in.extract/rewriters/base.pyscores 1.0 (clean) with zero escapes — a perfect extraction candidate.
Implementers vs Consumers¶
The distinction that makes the score meaningful is between:
- Implementers: Modules that declare a type deriving from the contract
- Consumers: Modules that merely use the contract
Only implementers belong to the extractable unit. Consumers stay behind and keep depending on the contract from outside — which is exactly what a plug point is for.
This matters. Counting consumers as members would make every popular interface look like a huge, incohesive seam. Instead, the true picture emerges: a contract with 2 implementers and 15 consumers is a real plug point. A contract with 27 dependents but 45 escaping dependencies (a real Go project) has a declared interface that isn't actually decoupled.
Type Derivation¶
For languages with explicit inheritance syntax (class X(Base):), this is straightforward. For Go, which has no implements keyword:
- A dependent that declares any type of its own (struct, interface, custom type) is treated as an implementer — imprecise, and deliberately so
- Full type checking is out of scope; this is a structural signal that works well in practice
Matched on the final segment of each base, so base.LanguageParser and a bare LanguageParser both count.
Declared ≠ Honoured¶
A project may have a declared plugin interface with many implementers but still be entangled. This gap is worth reporting rather than smoothing over.
Example: A real Go project has plugin/plugin.go (the contract) with 27 dependents. Only 2 are true implementers; the rest are consumers. But 45 dependencies escape — the plugins are not actually decoupled from the rest of the codebase. The verdict is entangled, not clean, correctly flagging that the plug point exists on paper but not in practice.
This is more useful than pretending all dependencies are equal. Drydock reports: - The number of true implementers - The cohesion score (evidence of whether the seam is actually decoupled) - All escaping dependencies (what extraction would drag along)
How Contract Modules Are Detected¶
A module is treated as a contract if it:
- Declares abstract types —
Protocol(Python),interface(Go, Java, C#, TypeScript),trait(Rust),ABCwith@abstractmethod(Python) - Is depended upon by other modules
The contract surface is every type the module declares, not only the abstract ones. This is because contract modules typically ship both a protocol and a convenience base:
# base.py (the contract)
class LanguageParser(Protocol): # Abstract contract
def parse(self, source: str) -> AST:
...
class BaseParser(LanguageParser): # Concrete base for implementers
def parse(self, source: str) -> AST:
...
Implementers typically derive from BaseParser, not the protocol itself. Matching only the protocol name found zero implementers in this real codebase; matching all declared types found all seven.
Practical Limits & Honesty¶
Seam detection is structural, not semantic. Real limitations:
Go Has No implements Keyword¶
A dependent that declares any type of its own is treated as an implementer. This is imprecise — a module could declare a type that has nothing to do with the interface — but it's the best structural signal available without full type checking. In practice, it works well because intentional plugin interfaces have many implementers in that package, making them stand out.
Type Counting is Approximate for Non-Python¶
Python uses AST for precise symbol extraction. Other languages use regex patterns. Class and type counts are approximate, but this doesn't affect cycle detection, dependency flow, or seam cohesion — only the abstractness metric in architecture analysis.
A Project Can Have Zero Seams¶
Some codebases have no declared contract with multiple implementers. This is a real finding: "0 of 10 most-depended-upon hubs define an abstract contract" means there is no plugin architecture to extract along. Zero seams is not a failure; it is an architectural choice.
Finding & Extracting Seams¶
List All Seams¶
Returns JSON ranked by cohesion (cleanest first), including: - Contract module and types it declares - Number of implementers - All escaping dependencies - Cohesion score and verdict
List Seams as Markdown with Extraction Commands¶
Shows each seam as a section with: - Cohesion and verdict - Implementer and escape counts - A ready-to-run extraction command
Extract a Seam as a Standalone Package¶
Plans the extraction (no writes). To execute:
Extraction includes: - The contract module - All modules that implement it - All their internal dependencies (closure) - Consumers are excluded intentionally — they stay behind and depend on the contract from outside
The Nautical Metaphor¶
Like finding the seams in a ship's hull where plates join perfectly, seams in code are places where modules fit together so cleanly they can be lifted out whole. The tighter the seam (higher cohesion), the safer the extraction.
See Also¶
- Extract — Material extraction command with seam selection
- Architecture Analyzer — Plugin Seams principle details
- Boundaries — Another way to find extraction points