From virtual-team
Enforces dependency inversion and testable design via checkpoint questions before function signatures, constructors, and methods when writing production code.
npx claudepluginhub ovargas/virtual-team --plugin virtual-teamThis skill uses the workspace's default tool permissions.
Read `stack.md` and check the `design:` field. If the field is missing, default to **recommended**.
Applies SOLID principles, TDD, and clean code practices for writing code, refactoring, architecture planning, code review, and debugging.
Elevates code to senior-engineer quality using SOLID principles, TDD, and clean code practices for writing, refactoring, architecture planning, reviews, and debugging.
Guides OOP class and routine design: enforces LSP checks, prefers containment over inheritance, limits parameters >7, evaluates cohesion. For reviews and refactoring.
Share bugs, ideas, or general feedback.
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:vt-implement — Layer 0 (behavioral discipline), alongside TDD/virtual-team:vt-flow — inherited through /virtual-team:vt-implementThe skill self-configures by reading stack.md. Consumers load it the same way regardless of mode.