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