一键导入
project-structure
Project organization and directory structure. Use when creating features, organizing files, or understanding the codebase layout.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Project organization and directory structure. Use when creating features, organizing files, or understanding the codebase layout.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Swift 6 concurrency patterns. Use when working with async/await, actors, MainActor isolation, or Sendable conformance.
Creates Features for dependency injection. Use when creating features, exposing public entry points, or wiring up dependencies.
Creates Repositories that abstract data access. Use when creating repositories, transforming DTOs to domain models, or implementing local-first caching. Supports remote-only, local-only, and cached (remote + local) repositories with CachePolicy.
Creates a new feature module with minimal viable structure. Use when bootstrapping a new feature from scratch, scaffolding the Tuist module, Container, Feature entry point, DeepLinkHandler, and initial screen with placeholder Text view. Includes all unit tests, mocks, stubs, and app integration. For adding domain/data layers afterward, use /datasource, /repository, /usecase. For enhancing views, use /view, /viewmodel, /navigator.
Creates Navigator for navigation. Use when setting up navigation, adding navigation to ViewModels, or testing navigation behavior.
Creates ViewModels with state management. Use when creating ViewModels, implementing ViewState pattern, or adding state management for features. Delegates to /usecase for domain use cases and to /feature for Container/Feature wiring.
| name | project-structure |
| description | Project organization and directory structure. Use when creating features, organizing files, or understanding the codebase layout. |
Guide for project organization and directory structure.
{AppName}/
├── App/
│ ├── Sources/
│ │ ├── {AppName}App.swift # Minimal entry point (imports AppKit)
│ │ └── Resources/
│ │ └── Assets.xcassets/
│ └── Tests/
│ └── UI/ # UI tests only
├── AppKit/ # Testable app code (SPM local package)
│ ├── Package.swift
│ ├── 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/ # Each feature is an SPM local package
│ ├── {Feature}/
│ │ ├── Package.swift
│ │ └── ...
│ └── Home/
│ ├── Package.swift
│ └── ...
├── Libraries/ # Each library is an SPM local package
│ ├── Core/
│ │ ├── Package.swift
│ │ └── ...
│ ├── Networking/
│ ├── DesignSystem/
│ └── SnapshotTestKit/
├── Shared/
│ └── Resources/
│ ├── Package.swift
│ └── ...
├── Derived/ # Generated by Tuist (gitignored)
│ └── InfoPlists/
├── Tuist/
│ ├── ProjectDescriptionHelpers/
│ └── Package.swift # External SPM dependencies + target settings
├── Project.swift # Root project (app + UI tests + module packages)
├── Workspace.swift # Workspace configuration (code coverage)
├── {AppName}.xctestplan # Test plan aggregating all module test targets (SPM strategy only)
├── Tuist.swift
└── CLAUDE.md
Key Architecture:
ChallengeAppKitFeature directory names must not contain the word "Feature". Use simple, descriptive names:
// RIGHT
Features/User/
Features/Character/
Features/Home/
// WRONG
Features/UserFeature/
Features/CharacterFeature/
Each feature module follows this internal structure:
FeatureName/
├── Package.swift # SPM local package definition
├── 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/
│ │ │ ├── Remote/
│ │ │ │ ├── {Name}RemoteDataSourceContract.swift
│ │ │ │ └── {Name}RESTDataSource.swift
│ │ │ └── Local/
│ │ │ ├── {Name}LocalDataSourceContract.swift
│ │ │ └── {Name}MemoryDataSource.swift
│ │ ├── DTOs/
│ │ │ └── {Name}DTO.swift
│ │ ├── Mappers/
│ │ │ └── {Name}Mapper.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/
│ │ │ ├── Repositories/
│ │ │ │ └── {Name}RepositoryTests.swift
│ │ ├── Presentation/
│ │ │ ├── Navigation/
│ │ │ │ └── {Feature}DeepLinkHandlerTests.swift
│ │ │ └── {Name}List/
│ │ │ └── ViewModels/
│ │ │ └── {Name}ListViewModelTests.swift
│ │ └── Feature/
│ │ └── {Feature}FeatureTests.swift
│ ├── Snapshots/ # Snapshot tests (ChallengeSnapshotTestKit)
│ │ └── 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
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:
CharacterDetail){Feature}Navigator.swift and {Feature}NavigatorContract.swift{Feature}Tracker.swift, {Feature}TrackerContract.swift, and {Feature}Event.swift{Feature}View.swift{Feature}ViewModel.swift{Feature}ViewState.swiftExtensions of external framework types (Foundation, UIKit, SwiftUI, etc.) must be placed in an Extensions/ folder.
Sources/
├── Extensions/
│ ├── URL+QueryItems.swift
│ ├── Date+Formatting.swift
│ └── String+Validation.swift
└── ...
Tests/
├── Extensions/
│ ├── URLSession+Mock.swift
│ ├── HTTPURLResponse+Mock.swift
│ └── URLRequest+BodyData.swift
└── ...
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/
├── Unit/ # Unit tests (Swift Testing)
│ ├── Domain/
│ │ └── UseCases/
│ │ └── Get{Name}UseCaseTests.swift
│ ├── Data/
│ │ ├── Repositories/
│ │ │ └── {Name}RepositoryTests.swift
│ │ └── DataSources/
│ │ ├── Remote/
│ │ │ └── {Name}RemoteDataSourceTests.swift
│ │ └── Local/
│ │ └── {Name}MemoryDataSourceTests.swift
│ ├── Presentation/
│ │ └── {ScreenName}/
│ │ └── ViewModels/
│ │ └── {ScreenName}ViewModelTests.swift
│ └── Feature/
│ └── {Feature}FeatureTests.swift
├── Snapshots/ # Snapshot tests (ChallengeSnapshotTestKit)
│ └── 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
| 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
Libraries/Core/
├── Sources/
│ ├── AppEnvironment/
│ │ └── AppEnvironment.swift # Base environment enum
│ ├── Feature/
│ │ └── FeatureContract.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
│ │ ├── DiskCache/
│ │ │ ├── ImageDiskCacheContract.swift
│ │ │ ├── ImageDiskCache.swift
│ │ │ ├── DiskCacheConfiguration.swift
│ │ │ ├── FileSystemContract.swift
│ │ │ └── FileSystem.swift
│ │ └── MemoryCache/
│ │ ├── ImageMemoryCacheContract.swift
│ │ └── ImageMemoryCache.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
Libraries/Networking/
├── Sources/
│ ├── HTTP/
│ │ ├── HTTPClient.swift
│ │ ├── HTTPClientContract.swift
│ │ ├── Endpoint.swift
│ │ ├── HTTPMethod.swift
│ │ └── HTTPError.swift
├── Tests/
│ └── Unit/
└── Mocks/
└── HTTPClientMock.swift
The Shared/ directory contains app-specific modules (not reusable across apps).
Shared/Resources/
├── Package.swift
├── Sources/
│ ├── Extensions/
│ │ └── String+Localized.swift # localized() extension
│ └── Resources/
│ └── Localizable.xcstrings
└── Tests/
The Resources module provides:
Localizable.xcstrings and String.localized() extensionTuist generates files in Derived/ (gitignored):
Derived/
└── InfoPlists/
├── {AppName}-Info.plist
└── {AppName}UITests-Info.plist
Contents: Only Info.plist files for the app and UI tests targets. Module Info.plists are managed by SPM.
No generated Swift code: disableBundleAccessors and disableSynthesizedResourceAccessors are enabled in Project.swift.
The app code is split into two modules:
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).
| 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 |
| Mapper | {Name}Mapper.swift | CharacterMapper.swift |
| Contract | {Name}Contract.swift | CharacterRepositoryContract.swift |
| DataSource Contract | {Name}{Type}DataSourceContract.swift | CharacterRemoteDataSourceContract.swift |
| DataSource Implementation | {Name}{Impl}DataSource.swift | CharacterRESTDataSource.swift, CharacterMemoryDataSource.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 |
| Package definition | Package.swift | Package.swift |
| JSON fixture | {name}.json | character.json |
{AppName}App.swift and UI tests (with Robots, Scenarios, Stubs, Fixtures)AppContainer.swift, RootContainerView.swift, AppNavigationRedirect.swiftmakeMainView() and resolve()Presentation/Navigation/Tests/Unit/ mirroring Sources structureTests/Snapshots/Tests/Unit/Feature/Extensions/ folder{Type}+{Purpose}.swiftMocks/, internal mocks in Tests/Shared/Mocks/Tests/Shared/Stubs/Tests/Shared/Fixtures/Tests/Shared/Resources/Package.swift with source, mocks, and test targetstestableTargets, SPM: via Challenge.xctestplan)Tuist/Package.swift