Skill: Project Structure
Guide for project organization and directory structure.
When to use this skill
- •Create a new feature module
- •Organize files correctly
- •Understand the codebase layout
- •Add extensions to existing types
Project Overview
{AppName}/
├── App/
│ ├── Sources/
│ │ ├── {AppName}App.swift # Minimal entry point (imports AppKit)
│ │ └── Resources/
│ │ └── Assets.xcassets/
│ └── Tests/
│ └── UI/ # UI tests only
├── AppKit/ # Testable app code (no TEST_HOST needed)
│ ├── Sources/
│ │ ├── AppContainer.swift # Composition Root (centralized DI)
│ │ ├── Data/
│ │ │ └── AppEnvironment+API.swift
│ │ └── Presentation/
│ │ ├── Navigation/
│ │ │ └── AppNavigationRedirect.swift
│ │ └── Views/
│ │ └── RootContainerView.swift
│ └── Tests/
│ ├── Unit/ # Unit tests
│ ├── Snapshots/ # Snapshot tests
│ └── Shared/ # Shared resources
├── Features/
│ ├── {Feature}/
│ └── Home/
├── Libraries/
│ ├── Core/
│ ├── Networking/
│ └── DesignSystem/
├── Shared/
│ └── Resources/
├── Derived/ # Generated by Tuist (gitignored)
│ └── InfoPlists/
├── Tuist/
│ └── ProjectDescriptionHelpers/
├── Project.swift
├── Tuist.swift
└── CLAUDE.md
Key Architecture:
- •App: Minimal entry point that imports
ChallengeAppKit - •AppKit: Framework containing testable app code (AppContainer, RootContainerView, AppNavigationRedirect). Tests run without TEST_HOST.
Feature Naming
Feature directory names must not contain the word "Feature". Use simple, descriptive names:
// RIGHT Features/User/ Features/Character/ Features/Home/ // WRONG Features/UserFeature/ Features/CharacterFeature/
Feature Module Structure
Each feature module follows this internal structure:
FeatureName/
├── Sources/
│ ├── {Feature}Feature.swift # Public entry point (navigation + deep links)
│ ├── {Feature}Container.swift # Dependency composition (factories)
│ ├── Domain/
│ │ ├── Models/
│ │ │ └── {Name}.swift # Domain models
│ │ ├── UseCases/
│ │ │ └── Get{Name}UseCase.swift # Business logic
│ │ └── Repositories/
│ │ └── {Name}RepositoryContract.swift # Repository contracts
│ ├── Data/
│ │ ├── DataSources/
│ │ │ ├── {Name}RemoteDataSource.swift
│ │ │ └── {Name}MemoryDataSource.swift
│ │ ├── DTOs/
│ │ │ └── {Name}DTO.swift
│ │ └── Repositories/
│ │ └── {Name}Repository.swift
│ └── Presentation/
│ ├── Navigation/ # Feature-level navigation (inside Presentation)
│ │ ├── {Feature}IncomingNavigation.swift # Navigation destinations
│ │ ├── {Feature}OutgoingNavigation.swift # Cross-feature navigation (optional)
│ │ └── {Feature}DeepLinkHandler.swift # Deep link handler
│ ├── {Name}List/
│ │ ├── Navigator/
│ │ │ ├── {Name}ListNavigatorContract.swift
│ │ │ └── {Name}ListNavigator.swift
│ │ ├── Tracker/
│ │ │ ├── {Name}ListTrackerContract.swift
│ │ │ ├── {Name}ListTracker.swift
│ │ │ └── {Name}ListEvent.swift
│ │ ├── Views/
│ │ │ └── {Name}ListView.swift
│ │ └── ViewModels/
│ │ ├── {Name}ListViewModel.swift
│ │ └── {Name}ListViewState.swift
│ └── {Name}Detail/
│ ├── Navigator/
│ │ ├── {Name}DetailNavigatorContract.swift
│ │ └── {Name}DetailNavigator.swift
│ ├── Tracker/
│ │ ├── {Name}DetailTrackerContract.swift
│ │ ├── {Name}DetailTracker.swift
│ │ └── {Name}DetailEvent.swift
│ ├── Views/
│ │ └── {Name}DetailView.swift
│ └── ViewModels/
│ ├── {Name}DetailViewModel.swift
│ └── {Name}DetailViewState.swift
├── Tests/
│ ├── Unit/ # Unit tests (Swift Testing)
│ │ ├── Domain/
│ │ │ └── UseCases/
│ │ │ └── Get{Name}UseCaseTests.swift
│ │ ├── Data/
│ │ │ └── {Name}RepositoryTests.swift
│ │ ├── Presentation/
│ │ │ ├── Navigation/
│ │ │ │ └── {Feature}DeepLinkHandlerTests.swift
│ │ │ └── {Name}List/
│ │ │ └── ViewModels/
│ │ │ └── {Name}ListViewModelTests.swift
│ │ └── Feature/
│ │ └── {Feature}FeatureTests.swift
│ ├── Snapshots/ # Snapshot tests (SnapshotTesting)
│ │ └── Presentation/
│ │ └── {Name}List/
│ │ ├── {Name}ListViewSnapshotTests.swift
│ │ └── __Snapshots__/
│ └── Shared/ # Shared resources
│ ├── Stubs/
│ │ └── {Name}+Stub.swift
│ ├── Mocks/
│ │ ├── Get{Name}UseCaseMock.swift
│ │ └── {Name}RepositoryMock.swift
│ ├── Fixtures/
│ │ └── {name}.json
│ ├── Extensions/
│ │ └── {Name}ViewState+Equatable.swift
│ └── Resources/
│ └── test-avatar.jpg
└── Mocks/ # Public mocks (if needed)
└── {Name}RepositoryMock.swift
Presentation Layer Organization
The Presentation layer groups related Views and ViewModels by feature name:
Presentation/ ├── CharacterDetail/ # Feature: Character detail screen │ ├── Navigator/ │ │ ├── CharacterDetailNavigatorContract.swift │ │ └── CharacterDetailNavigator.swift │ ├── Tracker/ │ │ ├── CharacterDetailTrackerContract.swift │ │ ├── CharacterDetailTracker.swift │ │ └── CharacterDetailEvent.swift │ ├── Views/ │ │ └── CharacterDetailView.swift │ └── ViewModels/ │ ├── CharacterDetailViewModel.swift │ └── CharacterDetailViewState.swift ├── CharacterList/ # Feature: Character list screen │ ├── Navigator/ │ │ ├── CharacterListNavigatorContract.swift │ │ └── CharacterListNavigator.swift │ ├── Tracker/ │ │ ├── CharacterListTrackerContract.swift │ │ ├── CharacterListTracker.swift │ │ └── CharacterListEvent.swift │ ├── Views/ │ │ └── CharacterListView.swift │ └── ViewModels/ │ ├── CharacterListViewModel.swift │ └── CharacterListViewState.swift └── ...
Naming conventions:
- •Folder name matches the feature (e.g.,
CharacterDetail) - •Navigator:
{Feature}Navigator.swiftand{Feature}NavigatorContract.swift - •Tracker:
{Feature}Tracker.swift,{Feature}TrackerContract.swift, and{Feature}Event.swift - •View:
{Feature}View.swift - •ViewModel:
{Feature}ViewModel.swift - •ViewState:
{Feature}ViewState.swift
Extensions
Extensions of external framework types (Foundation, UIKit, SwiftUI, etc.) must be placed in an Extensions/ folder.
Location
Sources/ ├── Extensions/ │ ├── URL+QueryItems.swift │ ├── Date+Formatting.swift │ └── String+Validation.swift └── ... Tests/ ├── Extensions/ │ ├── URLSession+Mock.swift │ ├── HTTPURLResponse+Mock.swift │ └── URLRequest+BodyData.swift └── ...
Naming Convention
Pattern: TypeName+Purpose.swift
// URL+QueryItems.swift
extension URL {
func appendingQueryItems(_ items: [URLQueryItem]) -> URL { ... }
}
// URLSession+Mock.swift (in Tests)
extension URLSession {
static func mockSession() -> URLSession { ... }
}
// Date+Formatting.swift
extension Date {
func formatted(style: DateFormatter.Style) -> String { ... }
}
Tests Directory Structure
Tests/
├── Unit/ # Unit tests (Swift Testing)
│ ├── Domain/
│ │ └── UseCases/
│ │ └── Get{Name}UseCaseTests.swift
│ ├── Data/
│ │ ├── {Name}RepositoryTests.swift
│ │ └── {Name}RemoteDataSourceTests.swift
│ ├── Presentation/
│ │ └── {ScreenName}/
│ │ └── ViewModels/
│ │ └── {ScreenName}ViewModelTests.swift
│ └── Feature/
│ └── {Feature}FeatureTests.swift
├── Snapshots/ # Snapshot tests (SnapshotTesting)
│ └── Presentation/
│ └── {ScreenName}/
│ ├── {ScreenName}ViewSnapshotTests.swift
│ └── __Snapshots__/
├── UI/ # UI tests (XCTest, App only)
└── Shared/ # Shared resources (used by Unit, Snapshots, and UI)
├── Stubs/ # Domain model test data
│ ├── Character+Stub.swift
│ └── Location+Stub.swift
├── Mocks/ # Internal test mocks
│ ├── Get{Name}UseCaseMock.swift
│ └── {Name}RepositoryMock.swift
├── Fixtures/ # JSON fixtures for DTOs
│ ├── character.json
│ └── character_list.json
├── Extensions/ # Test helpers (Equatable, etc.)
│ └── {Name}ViewState+Equatable.swift
├── Scenarios/ # Reusable SwiftMockServer configurations (UI tests)
│ └── UITestCase+Scenarios.swift
└── Resources/ # Test images
└── test-avatar.jpg
Mocks Location
| Location | Visibility | Usage |
|---|---|---|
Mocks/ (framework) | Public | Mocks used by other modules |
Tests/Shared/Mocks/ | Internal | Mocks shared between Unit and Snapshot tests |
FeatureName/
├── Mocks/ # Public mocks ({AppName}FeatureNameMocks framework)
│ └── {Name}RepositoryMock.swift
└── Tests/
└── Shared/
└── Mocks/ # Internal test-only mocks
└── {Name}DataSourceMock.swift
Core Module
Libraries/Core/
├── Sources/
│ ├── AppEnvironment/
│ │ └── AppEnvironment.swift # Base environment enum
│ ├── Feature/
│ │ └── Feature.swift # Feature protocol
│ ├── Navigation/
│ │ ├── NavigationCoordinator.swift # @Observable path manager
│ │ ├── NavigatorContract.swift # Navigation protocol
│ │ ├── NavigationRedirectContract.swift
│ │ ├── Navigation.swift # Base navigation protocol
│ │ ├── AnyNavigation.swift # Type-erased wrapper
│ │ └── DeepLinkHandler.swift
│ ├── ImageLoader/
│ │ ├── ImageLoaderContract.swift
│ │ ├── CachedImageLoader.swift
│ │ └── ImageLoaderEnvironment.swift
│ ├── Tracking/
│ │ ├── TrackerContract.swift
│ │ ├── Tracker.swift
│ │ ├── TrackingEventContract.swift
│ │ └── Providers/
│ │ ├── TrackingProviderContract.swift
│ │ └── ConsoleTrackingProvider.swift
│ └── Extensions/
│ └── ...
├── Tests/
│ └── Unit/
│ ├── AppEnvironment/
│ │ └── AppEnvironmentTests.swift
│ ├── Navigation/
│ │ └── NavigationCoordinatorTests.swift
│ └── Tracking/
│ ├── TrackerTests.swift
│ └── ConsoleTrackingProviderTests.swift
└── Mocks/
├── NavigatorMock.swift
├── TrackerMock.swift
├── ImageLoaderMock.swift
└── Bundle+JSON.swift
Networking Module
Libraries/Networking/
├── Sources/
│ ├── HTTP/
│ │ ├── HTTPClient.swift
│ │ ├── HTTPClientContract.swift
│ │ ├── Endpoint.swift
│ │ ├── HTTPMethod.swift
│ │ └── HTTPError.swift
│ └── Errors/
│ └── DataError.swift # Data layer errors (network, parsing, etc.)
├── Tests/
│ └── Unit/
└── Mocks/
└── HTTPClientMock.swift
Note: DataError is in Networking (not Core) as it represents data layer errors that are transformed to domain errors in repositories.
Shared Directory
The Shared/ directory contains app-specific modules (not reusable across apps).
Resources Module
Shared/Resources/ ├── Sources/ │ ├── Extensions/ │ │ ├── Bundle+Module.swift # Manual Bundle.module accessor │ │ └── String+Localized.swift # localized() extension │ └── Resources/ │ └── Localizable.xcstrings └── Tests/
The Resources module provides:
- •Localization: Centralized
Localizable.xcstringsandString.localized()extension - •Bundle.module: Manual accessor (Tuist generation disabled)
Note: Modules needing Bundle.module must include their own Bundle+Module.swift.
Derived Directory
Tuist generates files in Derived/ (gitignored):
Derived/
└── InfoPlists/
├── {AppName}-Info.plist
├── {AppName}Core-Info.plist
├── {AppName}Resources-Info.plist
├── {AppName}{Feature}-Info.plist
└── ...
Contents: Only Info.plist files for each target.
No generated Swift code: disableBundleAccessors and disableSynthesizedResourceAccessors are enabled in Project.swift.
App and AppKit Directories
The app code is split into two modules:
- •App: Minimal entry point with UI tests only
- •AppKit: Testable app code (unit and snapshot tests run here without TEST_HOST)
App/
├── Sources/
│ ├── {AppName}App.swift # Minimal entry point (imports AppKit)
│ └── Resources/
│ └── Assets.xcassets/
│ ├── AppIcon.appiconset/ # Production icon
│ ├── AppIconDev.appiconset/ # Development icon
│ └── AppIconStaging.appiconset/ # Staging icon
└── Tests/
├── Shared/
│ ├── Robots/ # Robot pattern for UI interactions
│ ├── Scenarios/ # Reusable SwiftMockServer configurations
│ ├── Stubs/ # Test data helpers
│ ├── Fixtures/ # JSON fixtures
│ └── Resources/ # Test images
└── UI/ # UI tests only (XCTest)
AppKit/
├── Sources/
│ ├── AppContainer.swift # Composition Root (centralized DI)
│ ├── Data/
│ │ └── AppEnvironment+API.swift # API configuration extension
│ └── Presentation/
│ ├── Navigation/
│ │ └── AppNavigationRedirect.swift
│ └── Views/
│ └── RootContainerView.swift # Root view with navigation
└── Tests/
├── Unit/ # Unit tests (Swift Testing)
│ ├── Data/
│ │ └── AppEnvironment+APITests.swift
│ └── Presentation/
│ └── Navigation/
│ ├── AppContainerNavigationTests.swift
│ └── AppNavigationRedirectTests.swift
├── Snapshots/ # Snapshot tests
│ └── Presentation/
└── Shared/ # Shared resources
└── Stubs/
Why AppKit? Unit and snapshot tests for app-level code can run without launching the app (no TEST_HOST required).
File Naming Summary
| Component | Naming Pattern | Example |
|---|---|---|
| Feature folder | {Name}/ | Character/ |
| Public entry | {Feature}Feature.swift | CharacterFeature.swift |
| Container | {Feature}Container.swift | CharacterContainer.swift |
| Navigation | Presentation/Navigation/{Feature}IncomingNavigation.swift | Presentation/Navigation/CharacterIncomingNavigation.swift |
| Domain model | {Name}.swift | Character.swift |
| DTO | {Name}DTO.swift | CharacterDTO.swift |
| UseCase | {Action}{Name}UseCase.swift | GetCharacterUseCase.swift |
| Repository | {Name}Repository.swift | CharacterRepository.swift |
| Contract | {Name}Contract.swift | CharacterRepositoryContract.swift |
| DataSource | {Name}{Type}DataSource.swift | CharacterRemoteDataSource.swift |
| Navigator | {ScreenName}Navigator.swift | CharacterDetailNavigator.swift |
| NavigatorContract | {ScreenName}NavigatorContract.swift | CharacterDetailNavigatorContract.swift |
| Tracker | {ScreenName}Tracker.swift | CharacterDetailTracker.swift |
| TrackerContract | {ScreenName}TrackerContract.swift | CharacterDetailTrackerContract.swift |
| Event | {ScreenName}Event.swift | CharacterDetailEvent.swift |
| View | {ScreenName}View.swift | CharacterDetailView.swift |
| ViewModel | {ScreenName}ViewModel.swift | CharacterDetailViewModel.swift |
| ViewState | {ScreenName}ViewState.swift | CharacterDetailViewState.swift |
| Test | {Component}Tests.swift | CharacterRepositoryTests.swift |
| Feature Test | {Feature}FeatureTests.swift | CharacterFeatureTests.swift |
| Stub | {Name}+Stub.swift | Character+Stub.swift |
| Mock | {Name}Mock.swift | CharacterRepositoryMock.swift |
| Extension | {Type}+{Purpose}.swift | URL+QueryItems.swift |
| Bundle accessor | Bundle+Module.swift | Bundle+Module.swift |
| JSON fixture | {name}.json | character.json |
Checklist
- • App contains only
{AppName}App.swiftand UI tests (with Robots, Scenarios, Stubs, Fixtures) - • AppKit contains testable code:
AppContainer.swift,RootContainerView.swift,AppNavigationRedirect.swift - • Feature folder does not contain "Feature" suffix
- • {Feature}Container.swift for dependency composition
- • {Feature}Feature.swift as public entry point with
makeMainView()andresolve() - • Sources organized by layer: Domain, Data, Presentation
- • Navigation folder inside
Presentation/Navigation/ - • Presentation organized by screen: {ScreenName}/Navigator/, {ScreenName}/Tracker/, {ScreenName}/Views/, {ScreenName}/ViewModels/
- • Unit tests in
Tests/Unit/mirroring Sources structure - • Snapshot tests in
Tests/Snapshots/ - • Feature tests in
Tests/Unit/Feature/ - • Extensions in dedicated
Extensions/folder - • Extension files named
{Type}+{Purpose}.swift - • Public mocks in
Mocks/, internal mocks inTests/Shared/Mocks/ - • Stubs in
Tests/Shared/Stubs/ - • JSON fixtures in
Tests/Shared/Fixtures/ - • Test resources in
Tests/Shared/Resources/ - • If module needs
Bundle.module, includeBundle+Module.swift