From virtual-team
Enforces dependency inversion and testable design via checkpoint questions before writing function signatures and constructors. Supports strict, recommended, and off modes via stack.md.
How this skill is triggered — by the user, by Claude, or both
Slash command
/virtual-team:design-principlesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Read `stack.md` and check the `design:` field. If the field is missing, default to **recommended**.
Read stack.md and check the design: field. If the field is missing, default to recommended.
design: value | Behavior |
|---|---|
strict | Every function boundary must pass the checkpoint. Rewrite if it fails. |
recommended | Check at key boundaries (constructors, service functions, handlers). Flag skips. |
off | No enforcement. Skip the rest of this skill. |
If design: off — stop reading.
Accept what you need. Don't create what you use.
A function that creates its own collaborators is a function that can't be tested in isolation. A function that receives them can be tested with any substitute — mock, stub, fake, or the real thing.
This is Dependency Inversion: depend on the behavior you need, not the thing that provides it.
Before writing a function signature, constructor, or method — pause and ask:
List the external collaborators: database access, HTTP clients, file system, other services, time, randomness.
If the answer is "nothing" — no checkpoint needed. Pure logic is already testable.
If the answer is "something" — continue to question 2.
If you're about to write new, import a concrete client, or call a constructor for something that does I/O — you're creating, not accepting. Restructure.
The parameter type should describe what you need, not who provides it.
``` // Depends on behavior — any implementation works interface UserRepository { findById(id: string): Promise save(user: User): Promise } ``` ``` // Depends on implementation — locked to Postgres function createUser(repo: PostgresUserRepository, data: NewUser) ```Not every language has explicit interfaces. The principle still applies:
The key: your function's contract is with a shape, not a name.
Not every function needs this checkpoint. Focus on boundaries — where your code meets the outside world:
| Boundary | Checkpoint applies | Why |
|---|---|---|
| Service/use-case functions | Yes | These orchestrate — they need collaborators |
| Constructors / factory functions | Yes | This is where dependencies get wired |
| Route handlers / controllers | Yes | These connect HTTP to business logic |
| Pure business logic | No | No external dependencies to invert |
| Simple utilities | No | formatDate() doesn't need dependency injection |
| Glue code / composition root | No | This is where you wire concretes — that's its job |
Every application has one place where concrete implementations are wired together. This is the composition root — the entry point, the main function, the DI container setup.
Concrete instantiation belongs here, not in business logic:
// composition root — the ONE place that knows about concretes
const repo = new PostgresUserRepository(pool)
const emailer = new SmtpEmailService(config)
const handler = new CreateUserHandler(repo, emailer)
app.post('/api/users', (req, res) => handler.handle(req, res))
If you find yourself writing new ConcreteService() outside the composition root, pause. You're wiring in the wrong place.
This skill and TDD are complementary:
When you follow both:
On mocking: TDD's guidance to prefer "real code over mocks" is about not mocking to paper over bad design. When the design is right — when dependencies are injected as abstractions — mocking is the clean, correct approach for unit tests. Integration tests still use real implementations.
Before marking implementation steps complete:
All modes:
Strict additionally:
| Thought | Response |
|---|---|
| "Passing dependencies everywhere is boilerplate" | It's explicit. Explicit dependencies are debuggable. Hidden ones aren't. |
| "This is over-engineering for a small project" | If you're writing tests, you need testable code. Size doesn't change that. |
| "I'll refactor to abstractions later" | Later never comes. The concrete dependency spreads through the codebase. |
| "My language doesn't have interfaces" | You don't need the keyword. Accept the shape, not the name. |
| "DI containers are complex" | You don't need a container. Constructor parameters are dependency injection. |
This skill is loaded by:
/virtual-team:implement — Layer 0 (behavioral discipline), alongside TDD/virtual-team:flow — inherited through /virtual-team:implementThe skill self-configures by reading stack.md. Consumers load it the same way regardless of mode.
npx claudepluginhub ovargas/virtual-team --plugin virtual-teamEnforces SOLID principles, TDD (red-green-refactor), and clean code practices to elevate code quality. Activates during coding, refactoring, architecture, code review, and debugging.
Applies SRP, DRY, YAGNI, naming, error handling, dependency direction, and Kent Beck's four rules of simple design when writing, reviewing, or refactoring code.
Designs software in small, focused units with minimal surface areas that can be combined freely without knowing each other's internals. Applicable when units are hard to reuse independently or replacements require touching many files.