Scoped Colocation
Overview
Keep related code together by scope. Scoped colocation organizes files by feature or domain rather than technical layer, reducing navigation overhead and making the codebase self-documenting.
Principle: Code that changes together should live together. When you modify a feature, you should not need to navigate across multiple unrelated directories.
REQUIRED BACKGROUND: superpowers:verification-before-completion
When to Use
Always triggered for:
- Creating new features or components
- Reviewing or refactoring existing file structure
- Discussing project organization patterns
- Onboarding to an unfamiliar codebase
- Adding tests, styles, or related assets to existing features
Core Workflow
- Identify the owning feature or domain for the code
- Place code in that feature's directory (not in shared/utils)
- Colocate tests with implementation in the same directory
- Colocate related assets (styles, types, fixtures) with the feature
- Extract to shared only when genuinely shared (3+ consumers)
- Verify directory structure reflects feature boundaries
Core Patterns
Pattern 1: Feature-Scoped Directories
Organize by feature/domain, not by technical concern:
# Anti-pattern: Technical layers
src/
controllers/
OrderController.cs
CustomerController.cs
services/
OrderService.cs
CustomerService.cs
models/
Order.cs
Customer.cs
# Preferred: Feature-scoped
src/
Orders/
OrderController.cs
OrderService.cs
Order.cs
OrderTests.cs
Customers/
CustomerController.cs
CustomerService.cs
Customer.cs
CustomerTests.cs
Pattern 2: Test Colocation
Tests live next to the code they test:
# Anti-pattern: Separate test tree
src/
Components/
Button.tsx
tests/
Components/
Button.test.tsx
# Preferred: Colocated tests
src/
Components/
Button.tsx
Button.test.tsx
Pattern 3: Asset Colocation
Related assets (styles, types, fixtures) stay with their component:
src/
Features/
Dashboard/
Dashboard.tsx
Dashboard.module.css
Dashboard.types.ts
Dashboard.test.tsx
dashboard-fixtures.json
Pattern 4: Shared Code Extraction
Extract only when genuinely shared (used in 3+ places):
src/
Features/
Orders/
... (feature-specific code)
Customers/
... (feature-specific code)
Shared/
Components/
Button.tsx # Used by Orders, Customers, Reports
Utils/
formatDate.ts # Used across multiple features
Rule: Do not prematurely extract. Keep code in features until sharing becomes necessary.
Decision Framework
When to Colocate
- Code is used by only one feature
- Code changes when the feature changes
- Understanding requires feature context
- Tests and implementation evolve together
When to Extract to Shared
- Code is used by 3 or more features
- Code is stable and unlikely to change per-feature
- Code represents a cross-cutting concern (auth, logging)
- Code is a foundational utility (date formatting, validation)
Colocation Hierarchy
1. Same file (functions, types)
2. Same directory (component + test + styles)
3. Feature directory (all feature-related code)
4. Domain directory (related features grouped)
5. Shared (cross-cutting, stable, reusable)
Required Actions
When Creating New Code
- Identify the owning feature or domain
- Place code in that feature's directory
- Colocate tests with implementation
- Colocate related assets (styles, types, fixtures)
- Only extract if truly shared (3+ consumers)
When Reviewing Code Placement
- Check if code is used by only one feature
- If single-feature, move to that feature's directory
- Verify tests are colocated
- Check for orphaned files in wrong directories
When Refactoring Structure
- Identify features/domains in the codebase
- Group related files by feature
- Move single-feature code out of "shared" directories
- Keep genuinely shared code in shared locations
- Update imports and references
Red Flags - STOP
- Creating a new
utils/orhelpers/file for single-feature code - Placing tests in a separate
__tests__/tree far from implementation - Adding feature-specific code to a "common" or "shared" directory
- Organizing primarily by file type (all controllers together, all models together)
- Premature extraction ("might need this elsewhere")
All mean: Consider if the code belongs in a feature directory instead.
Benefits
| Benefit | Description |
|---|---|
| Reduced navigation | Related files in same directory |
| Self-documenting | Directory structure reveals feature boundaries |
| Easier deletion | Remove feature by deleting one directory |
| Clear ownership | Obvious which team owns which code |
| Simpler imports | Shorter, more intuitive import paths |
| Better encapsulation | Feature internals hidden from other features |
Language-Specific Examples
React/TypeScript
src/
features/
auth/
LoginForm.tsx
LoginForm.test.tsx
LoginForm.module.css
useAuth.ts
useAuth.test.ts
auth.types.ts
dashboard/
Dashboard.tsx
Dashboard.test.tsx
DashboardWidget.tsx
useDashboardData.ts
.NET (C#)
src/
Features/
Orders/
CreateOrderCommand.cs
CreateOrderCommandHandler.cs
CreateOrderCommandTests.cs
OrderRepository.cs
OrderDto.cs
Customers/
GetCustomerQuery.cs
GetCustomerQueryHandler.cs
CustomerService.cs
Python
src/
features/
orders/
__init__.py
routes.py
service.py
models.py
test_orders.py
customers/
__init__.py
routes.py
service.py
models.py
test_customers.py
Exceptions
Certain code legitimately belongs in shared/common locations:
- Framework configuration (startup, middleware setup)
- Cross-cutting infrastructure (logging, caching, auth middleware)
- Domain primitives used across all features (Money, DateRange)
- Build/deployment configuration (CI/CD, Docker)
These exceptions should be documented in the project's architecture documentation.
Verification Checklist
Before declaring structure complete:
- Feature code lives in feature directories
- Tests are colocated with implementation
- Related assets are in same directory
- Shared code is genuinely shared (3+ consumers)
- No premature extraction to utils/helpers
- Directory structure reflects feature boundaries
Sample Directory Layouts
Full-Stack Application (React + Node.js)
project/
├── src/
│ ├── features/
│ │ ├── auth/
│ │ │ ├── components/
│ │ │ │ ├── LoginForm.tsx
│ │ │ │ ├── LoginForm.test.tsx
│ │ │ │ └── LoginForm.module.css
│ │ │ ├── hooks/
│ │ │ │ ├── useAuth.ts
│ │ │ │ └── useAuth.test.ts
│ │ │ ├── api/
│ │ │ │ ├── authApi.ts
│ │ │ │ └── authApi.test.ts
│ │ │ └── index.ts # Public exports
│ │ │
│ │ ├── orders/
│ │ │ ├── components/
│ │ │ │ ├── OrderList.tsx
│ │ │ │ ├── OrderList.test.tsx
│ │ │ │ ├── OrderDetail.tsx
│ │ │ │ └── OrderDetail.test.tsx
│ │ │ ├── hooks/
│ │ │ │ ├── useOrders.ts
│ │ │ │ └── useOrders.test.ts
│ │ │ ├── types/
│ │ │ │ └── order.types.ts
│ │ │ └── index.ts
│ │ │
│ │ └── dashboard/
│ │ ├── Dashboard.tsx
│ │ ├── Dashboard.test.tsx
│ │ └── widgets/
│ │ ├── SalesWidget.tsx
│ │ └── SalesWidget.test.tsx
│ │
│ ├── shared/ # Only truly shared (3+ consumers)
│ │ ├── components/
│ │ │ ├── Button.tsx
│ │ │ ├── Button.test.tsx
│ │ │ └── Modal.tsx
│ │ └── utils/
│ │ ├── formatDate.ts
│ │ └── formatDate.test.ts
│ │
│ └── infrastructure/ # Framework/cross-cutting
│ ├── http/
│ │ └── apiClient.ts
│ └── store/
│ └── configureStore.ts
│
└── package.json
.NET Clean Architecture
src/
├── Domain/ # Core business logic
│ ├── Orders/
│ │ ├── Order.cs
│ │ ├── OrderItem.cs
│ │ └── IOrderRepository.cs
│ └── Customers/
│ ├── Customer.cs
│ └── ICustomerRepository.cs
│
├── Application/ # Use cases
│ ├── Orders/
│ │ ├── Commands/
│ │ │ ├── CreateOrder/
│ │ │ │ ├── CreateOrderCommand.cs
│ │ │ │ ├── CreateOrderHandler.cs
│ │ │ │ └── CreateOrderValidator.cs
│ │ │ └── CancelOrder/
│ │ │ ├── CancelOrderCommand.cs
│ │ │ └── CancelOrderHandler.cs
│ │ └── Queries/
│ │ └── GetOrder/
│ │ ├── GetOrderQuery.cs
│ │ └── GetOrderHandler.cs
│ └── Common/
│ └── Behaviors/ # Cross-cutting (logging, validation)
│
├── Infrastructure/ # External concerns
│ ├── Persistence/
│ │ ├── Orders/
│ │ │ └── OrderRepository.cs
│ │ └── AppDbContext.cs
│ └── ExternalServices/
│
└── WebApi/ # Presentation
├── Controllers/
│ ├── OrdersController.cs
│ └── CustomersController.cs
└── Program.cs
tests/
├── Domain.Tests/
│ └── Orders/
│ └── OrderTests.cs
├── Application.Tests/
│ └── Orders/
│ └── CreateOrderHandlerTests.cs
└── WebApi.Tests/
└── Orders/
└── OrdersControllerTests.cs
Python FastAPI
src/
├── features/
│ ├── auth/
│ │ ├── __init__.py
│ │ ├── router.py
│ │ ├── service.py
│ │ ├── models.py
│ │ ├── schemas.py
│ │ └── test_auth.py
│ │
│ ├── orders/
│ │ ├── __init__.py
│ │ ├── router.py
│ │ ├── service.py
│ │ ├── models.py
│ │ ├── schemas.py
│ │ └── test_orders.py
│ │
│ └── customers/
│ ├── __init__.py
│ ├── router.py
│ └── test_customers.py
│
├── shared/
│ ├── __init__.py
│ ├── database.py
│ └── utils/
│ ├── __init__.py
│ └── date_utils.py
│
├── main.py
└── conftest.py # Shared test fixtures
Colocation Audit Checklist
Use this checklist to audit existing codebases:
Phase 1: Discovery
- List all top-level directories
- Identify if organized by technical layer or feature
- Count features/domains in the codebase
- Identify test location pattern
Phase 2: Analysis
For each feature identified:
| Feature | Code Location | Test Location | Colocated? | Action |
|---|---|---|---|---|
| Auth | src/features/auth | tests/auth | ✓ Yes | None |
| Orders | src/services/ | tests/services/ | ✗ No | Move |
| Utils | src/utils/ | tests/utils/ | ⚠ Check | Review usage |
Phase 3: Shared Code Audit
For each file in shared/, common/, utils/:
| File | Used By | Count | Action |
|---|---|---|---|
| formatDate.ts | auth, orders, reports | 3 | Keep in shared |
| orderHelpers.ts | orders only | 1 | Move to orders/ |
| constants.ts | auth, orders | 2 | Keep but watch |
Phase 4: Refactoring Priority
Based on analysis, prioritize:
- High: Single-feature code in shared directories
- Medium: Tests not colocated with implementation
- Low: Assets (styles, fixtures) not colocated
Audit Script
#!/bin/bash
# colocation-audit.sh
echo "=== Colocation Audit ==="
echo ""
# Check for common anti-patterns
echo "## Anti-pattern Detection"
# Separate test directories
if [ -d "tests" ] || [ -d "__tests__" ]; then
TEST_COUNT=$(find . -name "*.test.*" -o -name "*_test.*" | wc -l)
COLOCATED=$(find src -name "*.test.*" -o -name "*_test.*" 2>/dev/null | wc -l)
echo "Total tests: $TEST_COUNT"
echo "Colocated tests: $COLOCATED"
if [ "$COLOCATED" -lt "$TEST_COUNT" ]; then
echo "⚠ Some tests not colocated with source"
fi
fi
# Check utils/helpers directories
for dir in utils helpers common; do
if [ -d "src/$dir" ]; then
FILE_COUNT=$(find "src/$dir" -type f -name "*.ts" -o -name "*.js" -o -name "*.py" | wc -l)
echo "⚠ Found src/$dir with $FILE_COUNT files - review for single-feature code"
fi
done
# Check for feature directories
echo ""
echo "## Feature Directory Detection"
if [ -d "src/features" ]; then
echo "✓ Feature-based structure detected"
ls -d src/features/*/ 2>/dev/null | while read dir; do
FEATURE=$(basename "$dir")
TEST_FILES=$(find "$dir" -name "*.test.*" -o -name "*_test.*" | wc -l)
echo " $FEATURE: $TEST_FILES colocated tests"
done
else
echo "⚠ No features/ directory - consider restructuring"
fi
echo ""
echo "=== Audit Complete ==="
Evidence Template
## Colocation Audit Results
**Repository**: [repo-name]
**Date**: YYYY-MM-DD
**Auditor**: [name]
### Current Structure
[Describe current organization pattern]
### Findings
| Category | Status | Details |
| -------------------- | ------ | ------------------------- |
| Feature directories | ✓/✗ | [describe] |
| Test colocation | ✓/✗ | [X]% colocated |
| Shared code validity | ✓/✗ | [X] files, [Y] single-use |
### Recommended Actions
1. [ ] Move [X] to feature directory [Y]
2. [ ] Colocate tests for [feature]
3. [ ] Review shared/utils for single-use code
### Verification
After refactoring:
- [ ] All tests pass
- [ ] Import paths updated
- [ ] No orphaned files