Module Boundary Design
Purpose
Design a module boundary that is strong enough to survive implementation pressure.
This skill is for:
- introducing a new module
- splitting a large module into smaller ones
- isolating a subsystem
- clarifying ownership and dependency direction
- reducing accidental coupling
- replacing "helper/service/utils" sprawl with explicit structure
The goal is not to rename folders. The goal is to define a module that has:
- a clear job
- explicit contracts
- controlled state
- bounded dependencies
- a realistic adoption path
Use when
Use this skill when:
- one code area is too large or too mixed in responsibility
- boundaries between concerns are blurry
- multiple parts of the system depend on internals they should not touch
- a new module must be introduced cleanly
- the user wants to split a subsystem into coherent parts
- the user wants to stop architecture from collapsing into generic shared code
Typical trigger phrases:
- "выдели новый модуль"
- "разрежь это на модули"
- "спроектируй границы модуля"
- "как вынести это в отдельный модуль"
- "we need clearer module boundaries"
- "split this subsystem"
- "reduce coupling here"
Do not use when
Do not use this skill for:
- broad architecture option comparison
- full system design docs
- milestone planning
- generic refactoring with no structural boundary question
- bug fixing
- naming-only cleanups
- "just move files around" tasks with no design intent
If the user has not yet chosen an overall architecture direction, use an architecture-options-analysis skill first. If the module design is fixed and execution steps are needed, use an implementation-plan skill next.
Inputs
Expected inputs:
- current code area or subsystem
- target reason for modularization
- known pain points
- existing dependencies, call paths, or ownership issues
Optional inputs:
- performance constraints
- compatibility constraints
- deployment boundaries
- test constraints
- organizational ownership
- migration constraints
Outputs
Always produce:
- current state summary
- proposed module boundary
- module responsibilities
- explicit non-responsibilities
- ownership and dependency direction
- input/output contracts
- state model
- extension points
- anti-coupling rules
- testing strategy
- phased adoption plan
Core principles
- A module must have one coherent responsibility envelope.
- Boundaries must be explicit enough that future code can respect them.
- Shared code is not a virtue by itself.
- "helper", "service", and "utils" are warning signs unless they describe a real boundary.
- A module should expose contracts, not internals.
- Coupling must be controlled by direction, not by convention alone.
Constraints
- Focus on the boundary, not just file placement.
- Non-responsibilities are mandatory.
- Anti-coupling rules are mandatory.
- Contracts must be strong enough that consumers do not need internal knowledge.
- State ownership must be explicit.
- Prefer incremental extraction over big-bang moves when possible.
Procedure
Summarize the current state. First identify:
- what code exists now
- what responsibilities are currently mixed together
- what pain points exist
- what dependencies flow in and out
- which internal details are leaking across boundaries
Identify why modularization is needed. Be explicit about the actual pressure:
- too many responsibilities in one place
- unstable dependencies
- unclear ownership
- poor testability
- repeated logic with hidden coupling
- migration pressure
- scaling or performance isolation
- interface confusion
Define the proposed module goal. State what the module is meant to encapsulate. The goal must be operational, for example:
- coordinate ingestion of tracking frames
- expose touch-candidate extraction
- manage persistence for event snapshots
- evaluate business rules for rollout gating
Avoid vague goals like:
- "shared helpers"
- "common utilities"
- "support logic"
Define responsibilities. List what the module is responsible for. Responsibilities should be concrete and bounded.
Define non-responsibilities. Explicitly state what the module does not own. This is mandatory. Examples:
- does not manage persistence
- does not own orchestration
- does not call external services directly
- does not render user-facing outputs
- does not perform training
A module without explicit non-responsibilities will expand until it becomes a junk drawer.
Define ownership. Clarify:
- who owns the module conceptually
- what other modules may depend on it
- whether it is a leaf module, domain module, adapter module, or orchestration module
- what direction dependencies are allowed to flow
Define input/output contracts. For every important entry point, specify:
- input shape, type, or schema
- output shape, type, or schema
- sync/async expectations
- error surface
- idempotency or ordering expectations if relevant
The contract must be strong enough that consumers do not need to know internals.
Define the state model. Specify:
- whether the module is stateless or stateful
- what state it owns
- what state it only reads
- what state transitions matter
- whether state is mutable, append-only, cached, derived, or externally sourced
- what invariants must hold
Define extension points. Identify where future variation is legitimate. Good extension points are narrow and intentional:
- strategy interface for one policy choice
- registry for one known family of handlers
- pluggable storage backend under a stable contract
Do not create generic extension points "just in case".
Define anti-coupling rules. State the rules that protect the boundary, for example:
- consumers may only use public interfaces
- no cross-module access to internal state
- dependency direction is one-way
- orchestration stays outside the module
- persistence adapters do not import domain logic
- feature flags do not leak into domain primitives
These rules should be specific enough to review against.
- Define testing strategy. The module test strategy should include:
- unit tests for internal logic where appropriate
- contract tests for public interfaces
- state transition tests for stateful modules
- narrow integration tests for boundary interactions
- anti-regression checks for phased adoption
Testing must align with the boundary, not just the file structure.
- Define phased adoption. Explain how the module can be introduced safely:
- wrap existing behavior behind the new boundary
- migrate one call site or responsibility slice at a time
- maintain compatibility shims if needed
- stop once the old path is no longer required
- remove temporary bridges deliberately
The adoption plan must minimize simultaneous rewrites.
- Check for boundary smell. Before finalizing, ask:
- is the module too broad?
- is it just a renamed utils package?
- does it own behavior or only collect helpers?
- are inputs/outputs explicit?
- is dependency direction enforceable?
- can the module be tested as a unit of responsibility?
Decision rules
Responsibility rule
If the module cannot be described by one coherent responsibility envelope, the boundary is probably wrong.
Non-responsibility rule
If the design cannot clearly state what the module does not own, it is too vague.
Contract rule
If consumers still need to know internal behavior to use the module safely, the contract is too weak.
State rule
If state ownership is unclear, bugs and leakage will follow. State must be assigned explicitly.
Extension-point rule
Only define extension points for realistic variation. Do not create framework-like abstractions by default.
Coupling rule
If the design relies on "developers being careful" instead of explicit dependency direction, the boundary is weak.
Testing rule
If the module cannot be tested through its public surface, the boundary is likely not real.
Adoption rule
Prefer designs that support incremental extraction over big-bang moves.
Optional subagent use
For larger or messier systems, explicitly spawn bounded subagents before finalizing the module design:
- one subagent to map current responsibilities and coupling
- one subagent to inspect public/private usage patterns
- one subagent to identify the cheapest phased extraction path
Wait for their memos, then synthesize the final boundary design. Do not spawn subagents for a small local extraction.
References
Use the supporting references when needed:
references/module-boundary-template.mdreferences/contracts-and-state-checklist.mdreferences/anti-coupling-and-adoption-checklist.md
Definition of done
- The proposed module has a clear responsibility envelope.
- Non-responsibilities are explicit.
- Contracts and state ownership are concrete.
- Anti-coupling rules are specific enough to review against.
- The testing strategy matches the boundary.
- The adoption path is incremental and realistic.
Final response format
Return the result in this structure:
- Current state
- Why modularization is needed
- Proposed module
- Responsibilities
- Non-responsibilities
- Ownership and dependency direction
- Input/output contracts
- State model
- Extension points
- Anti-coupling rules
- Testing strategy
- Phased adoption plan
Positive examples
Use this skill for:
- "выдели это в отдельный модуль"
- "разрежь этот кусок системы на модули"
- "спроектируй границы нового модуля"
- "как перестать тащить это в utils"
- "design a proper boundary for this subsystem"
- "split this large module cleanly"
Negative examples
Do not use this skill for:
- "сравни варианты архитектуры"
- "сделай план реализации"
- "напиши design doc на всю систему"
- "почини баг"
- "ускорь этот модуль"