- Add persistent docs: PRD, functional design, architecture, repository structure, development guidelines, milestones - Add brainstorming notes (docs/ideas/) - Configure tooling: package.json, tsconfig, eslint, prettier, vitest - Add opencode configuration (commands, skills, agents) - Add .gitignore for node_modules, .env, data/, backups/, build outputs
8.8 KiB
Repository Structure Document Authoring Guide
Basic Principles
1. Clarify Roles
Each directory should have a single, well-defined role.
Bad example:
src/
├── stuff/ # Vague
├── misc/ # Miscellaneous
└── utils/ # Too generic
Good example:
src/
├── commands/ # CLI command implementations
├── services/ # Business logic
├── repositories/ # Data persistence
└── validators/ # Input validation
2. Enforce Layer Separation
Reflect the architecture's layer structure in the directory structure:
src/
├── ui/ # UI layer
│ └── cli/ # CLI implementation
├── services/ # Service layer
│ └── task/ # Task management service
└── repositories/ # Data layer
└── task/ # Task repository
3. Split by Technical Concern (Baseline)
Split directories by related technical concerns:
Basic structure:
src/
├── commands/ # CLI commands
├── services/ # Business logic
├── repositories/ # Data persistence
└── types/ # Type definitions
Mapping to the layer structure:
CLI/UI layer → commands/, cli/
Service layer → services/
Data layer → repositories/, storage/
Designing the Directory Structure
Expressing the Layer Structure
// Bad example: flat structure
src/
├── TaskCLI.ts
├── TaskService.ts
├── TaskRepository.ts
├── UserCLI.ts
├── UserService.ts
└── UserRepository.ts
// Good example: clear layers
src/
├── cli/
│ ├── TaskCLI.ts
│ └── UserCLI.ts
├── services/
│ ├── TaskService.ts
│ └── UserService.ts
└── repositories/
├── TaskRepository.ts
└── UserRepository.ts
Placement of the Test Directory
Recommended structure:
project/
├── src/
│ └── services/
│ └── TaskService.ts
└── tests/
├── unit/
│ └── services/
│ └── TaskService.test.ts
├── integration/
└── e2e/
Rationale:
- Test code is separated from production code
- Easy to exclude tests at build time
- Can be organized by test type
Naming Convention Best Practices
Principles for Directory Names
1. Use the plural form (layer directories)
✅ services/
✅ repositories/
✅ controllers/
❌ service/
❌ repository/
❌ controller/
Rationale: They hold multiple files
2. Use kebab-case
✅ task-management/
✅ user-authentication/
❌ TaskManagement/
❌ userAuthentication/
Rationale: Compatibility with URLs and the file system
3. Use specific names
✅ validators/ # Input validation
✅ formatters/ # Data formatting
✅ parsers/ # Data parsing
❌ utils/ # Too generic
❌ helpers/ # Vague
❌ common/ # Meaningless
Principles for File Names
1. Class files: PascalCase + role suffix
// Service classes
TaskService.ts
UserAuthenticationService.ts
// Repository classes
TaskRepository.ts
UserRepository.ts
// Controller classes
TaskController.ts
2. Function files: camelCase + start with a verb
// Utility functions
formatDate.ts
validateEmail.ts
parseCommandArguments.ts
3. Type definition files: PascalCase or kebab-case
// Interface definitions
Task.ts
UserProfile.ts
// Type definition collections
task-types.d.ts
api-types.d.ts
4. Constant files: UPPER_SNAKE_CASE or kebab-case
// Constant definitions
API_ENDPOINTS.ts
ERROR_MESSAGES.ts
// or
api-endpoints.ts
error-messages.ts
Managing Dependencies
Dependency Rules Between Layers
// ✅ Good example: dependency from an upper layer to a lower layer
// cli/TaskCLI.ts
import { TaskService } from '../services/TaskService';
class TaskCLI {
constructor(private taskService: TaskService) {}
}
// ❌ Bad example: dependency from a lower layer to an upper layer
// services/TaskService.ts
import { TaskCLI } from '../cli/TaskCLI'; // Forbidden!
Avoiding Circular Dependencies
Problematic code:
// services/TaskService.ts
import { UserService } from './UserService';
export class TaskService {
constructor(private userService: UserService) {}
}
// services/UserService.ts
import { TaskService } from './TaskService'; // Circular dependency!
export class UserService {
constructor(private taskService: TaskService) {}
}
Solution 1: Extract shared type definitions
// types/Service.ts
export interface ITaskService { /* ... */ }
export interface IUserService { /* ... */ }
// services/TaskService.ts
import type { IUserService } from '../types/Service';
export class TaskService {
constructor(private userService: IUserService) {}
}
// services/UserService.ts
import type { ITaskService } from '../types/Service';
export class UserService {
constructor(private taskService: ITaskService) {}
}
Solution 2: Rethink the dependencies
// Extract shared functionality into a separate service
// services/NotificationService.ts
export class NotificationService {
notifyTaskAssignment(taskId: string, userId: string): void {
// Notification handling
}
}
// services/TaskService.ts
import { NotificationService } from './NotificationService';
export class TaskService {
constructor(private notificationService: NotificationService) {}
}
// services/UserService.ts
import { NotificationService } from './NotificationService';
export class UserService {
constructor(private notificationService: NotificationService) {}
}
Scaling Strategy
Recommended Structure
Standard pattern:
src/
├── commands/
│ └── TaskCommand.ts
├── services/
│ ├── TaskService.ts
│ └── UserService.ts
├── repositories/
│ ├── TaskRepository.ts
│ └── UserRepository.ts
├── types/
│ ├── Task.ts
│ └── User.ts
├── validators/
│ └── TaskValidator.ts
└── index.ts
Rationale:
- Responsibilities are clear per layer
- No later refactoring required
- Easy to standardize across a team
Timing for Module Separation
Signs that separation should be considered:
- A directory contains 10 or more files
- Related functionality is grouped together
- It can be tested independently
- It has few dependencies on other features
Separation procedure:
// Before: everything placed in services/
services/
├── TaskService.ts
├── TaskValidationService.ts
├── TaskNotificationService.ts
├── UserService.ts
└── UserAuthService.ts
// After: modularized by feature
modules/
├── task/
│ ├── TaskService.ts
│ ├── TaskValidationService.ts
│ └── TaskNotificationService.ts
└── user/
├── UserService.ts
└── UserAuthService.ts
Handling Special Cases
Placement of Shared Code
shared/ or common/ directory
src/
├── shared/
│ ├── utils/ # General-purpose utilities
│ ├── types/ # Shared type definitions
│ └── constants/ # Shared constants
├── commands/
├── services/
└── repositories/
Rules:
- Only things genuinely used across multiple layers
- Do not include anything used in only a single layer
Managing Configuration Files (when applicable)
config/
├── default.ts # Default settings
└── constants.ts # Constant definitions
Managing Scripts (when applicable)
scripts/
├── build.sh # Build script
└── dev-tools.ts # Development helper script
Document Placement
Document Types and Their Locations
Project root:
README.md: Project overviewCONTRIBUTING.md: Contribution guideLICENSE: License
docs/ directory:
product-requirements.md: PRDfunctional-design.md: Functional design documentarchitecture.md: Architecture design documentrepository-structure.md: This documentdevelopment-guidelines.md: Development guidelinesglossary.md: Glossary
Within the source code:
- TSDoc/JSDoc comments: Descriptions of functions and classes
Checklist
- Each directory's role is clearly defined
- The layer structure is reflected in the directories
- Naming conventions are consistent
- The placement policy for test code is decided
- Dependency rules are clear
- There are no circular dependencies
- A scaling strategy has been considered
- Placement rules for shared code are defined
- A management method for configuration files is decided
- Document locations are clear