
iOS 프로젝트에서 Tuist를 빼려고 하면 보통 다음 문제가 바로 나온다.
Tuist가 모듈을 만들어줬는데
이제 Feature를 추가할 때마다 직접 폴더를 만들어야 하나?
Package.swift도 매번 수정해야 하나?
Xcode Target도 사람이 연결해야 하나?
결국 별도의 Scaffold CLI를 또 만들어야 하나?
예전이라면 실제로 다음과 같은 도구를 하나 만들었을 가능성이 높다.
make feature name=Profile
# 또는
./scripts/create_feature.sh Profile
조금 더 발전하면 Swift Argument Parser로 사내 CLI까지 만든다.
myapp module create Profile
하지만 2026년에는 여기까지 할 필요가 없는 프로젝트가 꽤 많다.
새 iOS 프로젝트를 만든다면 오히려 다음 구조를 먼저 검토한다.
SwiftPM
→ 실제 모듈과 Dependency 관리
Xcode
→ App / Widget / Extension 같은 Application Target만 관리
AI Coding Agent
→ 모듈 생성과 Package.swift 수정
AGENTS.md
→ Architecture 규칙
.ai/templates/
→ Clean Architecture 템플릿
xcodebuild / SwiftPM
→ 실제 검증
개발자가 사용하는 인터페이스는 자연어다.
FeatureProfile 모듈 만들어줘.
Clean Architecture 템플릿을 사용하고
Domain은 Core,
Data는 Network,
Presentation은 DesignSystem을 사용해.
AppComposition에도 연결하고
빌드까지 검증해줘.
Agent가 뒤에서 알아서 처리한다.
Template 확인
↓
폴더 생성
↓
Swift 파일 생성
↓
Package.swift 수정
↓
Dependency 연결
↓
순환 참조 확인
↓
Build
↓
Test
↓
변경 결과 보고
중요한 점이 하나 있다.
AI Agent가 Swift Package Manager를 대체하는 것은 아니다.
구조는 여전히:
SwiftPM
=
Package Manager / Build Graph
AI Agent
=
SwiftPM을 조작하는 개발 인터페이스
다.
즉 사용자 입장에서는 CLI를 거의 사용하지 않지만, 실제 검증은 여전히 결정론적인 SwiftPM과 Xcode가 담당한다.
이 글에서는 Tuist를 완전히 제거한 프로젝트에서 이 구조를 처음부터 만들어본다.
Tuist가 편했던 이유는 여러 역할을 한곳에서 처리했기 때문이다.
Project.swift
↓
Target 생성
Dependency 연결
Scheme
Configuration
Project 생성
그리고 다음과 같은 Scaffold도 만들 수 있었다.
FeatureHome
FeatureLogin
FeatureProfile
Tuist를 제거하면서 이 모든 것을 다른 Project Generator 하나로 그대로 옮길 필요는 없다.
역할을 나누면 된다.
Dependency / Module Graph
→ SwiftPM
App Target
→ Xcode
Source Folder
→ Xcode Folder + SwiftPM
Environment
→ xcconfig
Module Scaffold
→ AI Agent
Architecture Rule
→ AGENTS.md
Validation
→ SwiftPM + xcodebuild
이렇게 하면 별도의 Project Generator 자체가 필요 없어지는 경우가 많다.
Apple도 Local Swift Package를 앱 내부 코드의 모듈화 수단으로 공식 지원한다.
그리고 최근 Xcode의 folder 기반 프로젝트 구조에서는 디스크의 파일 추가·삭제를 프로젝트 파일에 하나씩 기록하는 방식보다 훨씬 작은 Project File 표현을 사용할 수 있다.
파일을 추가할 때마다:
project.pbxproj
+ PBXFileReference
+ PBXBuildFile
+ PBXGroup
가 계속 바뀌던 문제를 줄일 수 있다.
그래서 목표를 다음처럼 잡는다.
MyApp.xcodeproj
담당:
MyApp
Widget
NotificationExtension
ShareExtension
Signing
Capability
Scheme
Build Settings
반대로 일반 Swift 코드는 대부분 SwiftPM으로 내린다.
처음에는 이런 구조가 자연스럽게 보인다.
Packages/
├── Core/
│ └── Package.swift
├── Network/
│ └── Package.swift
├── FeatureHome/
│ └── Package.swift
├── FeatureLogin/
│ └── Package.swift
└── FeatureProfile/
└── Package.swift
이것도 좋은 구조다.
특히 각 Package를 독립적으로 배포하거나 팀별 Ownership을 강하게 나눠야 한다면 적합하다.
하지만 하나의 iOS 앱 안에서만 사용하는 Module이라면 먼저 하나의 Local Package + 여러 Target 구조를 검토한다.
Packages/
└── AppModules/
├── Package.swift
├── Sources/
└── Tests/
그 안에 Target을 여러 개 만든다.
Core
Network
DesignSystem
FeatureHomeDomain
FeatureHomeData
FeatureHomePresentation
FeatureHomeComposition
FeatureProfileDomain
FeatureProfileData
FeatureProfilePresentation
FeatureProfileComposition
AppComposition
이 구조에는 큰 장점이 있다.
Xcode App Target이 Local Package 하나만 알면 된다.
최종 구조를 이렇게 만든다.
MyApp.xcodeproj
│
│ dependency
▼
AppModules
│
▼
AppComposition
│
┌────┴──────────┐
│ │
▼ ▼
FeatureHome FeatureProfile
App Target은:
import AppComposition
정도만 알고 있다.
새로운 Feature가 생겨도:
FeatureSettings
FeatureCamera
FeatureSearch
Xcode Project에 새로운 Package Reference를 계속 추가하는 대신 AppModules/Package.swift 안에서 Target Graph만 수정한다.
따라서 Agent가 .pbxproj를 직접 수정할 일이 크게 줄어든다.
이 부분이 Tuist 제거 구조에서 꽤 중요하다.
처음 프로젝트를 다음처럼 만든다.
MyApp/
├── App/
│ ├── MyApp.xcodeproj
│ ├── MyApp/
│ │ ├── MyApp.swift
│ │ └── Resources/
│ └── Extensions/
│
├── Packages/
│ └── AppModules/
│ ├── Package.swift
│ ├── Sources/
│ │ ├── Core/
│ │ ├── Network/
│ │ ├── DesignSystem/
│ │ ├── FeatureHomeDomain/
│ │ ├── FeatureHomeData/
│ │ ├── FeatureHomePresentation/
│ │ ├── FeatureHomeComposition/
│ │ └── AppComposition/
│ │
│ └── Tests/
│
├── Config/
│ ├── Dev.xcconfig
│ ├── Stage.xcconfig
│ └── Prod.xcconfig
│
├── .ai/
│ └── templates/
│ └── clean-feature/
│
└── AGENTS.md
이 구조에서 Xcode Project는 매우 작다.
실제 애플리케이션 코드는 대부분:
Packages/AppModules
안에 존재한다.
Agent에게 가장 먼저 알려줘야 하는 것은 Folder 이름이 아니다.
Dependency Direction이다.
Feature 단위 Clean Architecture를 만든다면 다음처럼 한다.
App
│
▼
AppComposition
│
▼
FeatureProfileComposition
/ \
▼ ▼
Presentation Data
│ │
│ ▼
└──────────────► Domain
│
▼
Core
그리고 공통 Infra는:
Data
│
└────► Network
Presentation
│
└────► DesignSystem
로 연결한다.
중요한 Rule:
Domain
→ UIKit 금지
→ SwiftUI 금지
→ Network 구현체 금지
Presentation:
Domain O
Data X
Data:
Domain O
Presentation X
Feature 간 직접 참조:
FeatureHome
↓
FeatureProfile
X
가 기본이다.
Feature 간 연결은:
AppComposition
또는
Shared Domain
에서 해결한다.
Clean Architecture를 만들다 보면 흔히 이런 문제가 생긴다.
Presentation이 실제 Repository 구현체를 만들기 시작한다.
let repository = LiveProfileRepository()
let useCase = LoadProfileUseCase(repository: repository)
그러면:
Presentation
↓
Data
Dependency가 생긴다.
이를 막기 위해 Composition을 따로 둔다.
FeatureProfileComposition
이 Target만:
Data
Presentation
Domain
을 모두 알고 있다.
Composition Root가 실제 Dependency를 조립한다.
Packages/AppModules/Package.swift
// swift-tools-version: 6.3
import PackageDescription
let package = Package(
name: "AppModules",
platforms: [
.iOS("18.0")
],
products: [
.library(
name: "AppComposition",
targets: ["AppComposition"]
)
],
targets: [
.target(
name: "Core"
),
.target(
name: "Network",
dependencies: [
"Core"
]
),
.target(
name: "DesignSystem"
),
.target(
name: "FeatureHomeDomain",
dependencies: [
"Core"
]
),
.target(
name: "FeatureHomeData",
dependencies: [
"FeatureHomeDomain",
"Network"
]
),
.target(
name: "FeatureHomePresentation",
dependencies: [
"FeatureHomeDomain",
"DesignSystem"
]
),
.target(
name: "FeatureHomeComposition",
dependencies: [
"FeatureHomeDomain",
"FeatureHomeData",
"FeatureHomePresentation"
]
),
.target(
name: "AppComposition",
dependencies: [
"FeatureHomeComposition"
]
),
.testTarget(
name: "FeatureHomeDomainTests",
dependencies: [
"FeatureHomeDomain"
]
)
]
)
실제 Deployment Target은 앱 정책에 맞춰 바꾸면 된다.
Swift 6.3은 2026년 3월 정식 출시됐고, SwiftPM에서는 Swift Build 통합 Preview도 제공하기 시작했다.
중요한 것은 특정 버전보다 Package.swift가 Module Graph의 Source of Truth가 되는 구조다.
MyApp.swift
import SwiftUI
import AppComposition
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
AppRootFactory.make()
}
}
}
App Target의 역할이 매우 작아진다.
App 시작
Signing
Capability
Lifecycle
AppComposition 호출
정도만 담당한다.
여기가 이번 구조의 핵심이다.
예전에는:
Tuist Scaffold
또는
사내 CLI
가 Template을 가지고 있었다.
이제 Template을 그냥 Repository에 둔다.
.ai/
└── templates/
└── clean-feature/
├── template.json
│
├── Domain/
│ ├── __FEATURE__.swift
│ ├── __FEATURE__Repository.swift
│ └── Load__FEATURE__UseCase.swift
│
├── Data/
│ └── Live__FEATURE__Repository.swift
│
├── Presentation/
│ ├── __FEATURE__View.swift
│ └── __FEATURE__ViewModel.swift
│
├── Composition/
│ └── __FEATURE__FeatureFactory.swift
│
└── Tests/
└── Load__FEATURE__UseCaseTests.swift
__FEATURE__가 Placeholder다.
.ai/templates/clean-feature/template.json
{
"template": "clean-feature",
"placeholder": "__FEATURE__",
"layers": [
"Domain",
"Data",
"Presentation",
"Composition"
],
"defaultDependencies": {
"Domain": [
"Core"
],
"Data": [
"Domain",
"Network"
],
"Presentation": [
"Domain",
"DesignSystem"
],
"Composition": [
"Domain",
"Data",
"Presentation"
]
}
}
Agent가 Template 구조를 쉽게 이해할 수 있다.
__FEATURE__.swift
public struct __FEATURE__: Sendable, Equatable {
public let id: String
public init(id: String) {
self.id = id
}
}
Repository:
public protocol __FEATURE__Repository: Sendable {
func load() async throws -> __FEATURE__
}
UseCase:
public struct Load__FEATURE__UseCase: Sendable {
private let repository: any __FEATURE__Repository
public init(
repository: any __FEATURE__Repository
) {
self.repository = repository
}
public func execute() async throws -> __FEATURE__ {
try await repository.load()
}
}
Domain에는 UI와 Network 구현이 없다.
import Feature__FEATURE__Domain
import Network
public struct Live__FEATURE__Repository:
__FEATURE__Repository,
Sendable {
private let client: any HTTPClient
public init(
client: any HTTPClient
) {
self.client = client
}
public func load() async throws -> __FEATURE__ {
let response: __FEATURE__Response =
try await client.get("/profile")
return __FEATURE__(
id: response.id
)
}
}
실제 Endpoint까지 Template에서 고정하면 좋지 않다.
Agent가 Feature 요구사항을 보고 수정하게 한다.
Swift Observation을 사용한다면:
import Observation
import Feature__FEATURE__Domain
@MainActor
@Observable
public final class __FEATURE__ViewModel {
public private(set) var state: State = .idle
private let loadUseCase: Load__FEATURE__UseCase
public init(
loadUseCase: Load__FEATURE__UseCase
) {
self.loadUseCase = loadUseCase
}
public func load() async {
state = .loading
do {
let value = try await loadUseCase.execute()
state = .loaded(value)
} catch {
state = .failed
}
}
}
extension __FEATURE__ViewModel {
public enum State {
case idle
case loading
case loaded(__FEATURE__)
case failed
}
}
View:
import SwiftUI
public struct __FEATURE__View: View {
@State
private var viewModel: __FEATURE__ViewModel
public init(
viewModel: __FEATURE__ViewModel
) {
self._viewModel = State(
initialValue: viewModel
)
}
public var body: some View {
content
.task {
await viewModel.load()
}
}
@ViewBuilder
private var content: some View {
switch viewModel.state {
case .idle,
.loading:
ProgressView()
case .loaded(let value):
Text(value.id)
case .failed:
ContentUnavailableView(
"불러오지 못했습니다",
systemImage: "exclamationmark.triangle"
)
}
}
}
이 부분은 프로젝트가 TCA라면 TCA Template으로 바꿔도 된다.
Architecture Rule만 유지하면 된다.
import SwiftUI
import Network
import Feature__FEATURE__Domain
import Feature__FEATURE__Data
import Feature__FEATURE__Presentation
public enum __FEATURE__FeatureFactory {
@MainActor
public static func make(
httpClient: any HTTPClient
) -> some View {
let repository =
Live__FEATURE__Repository(
client: httpClient
)
let useCase =
Load__FEATURE__UseCase(
repository: repository
)
let viewModel =
__FEATURE__ViewModel(
loadUseCase: useCase
)
return __FEATURE__View(
viewModel: viewModel
)
}
}
Dependency 조립은 여기에서만 한다.
이 방식의 장점은:
"FeatureProfile 만들어줘"
라고 했다고 해서 매번 Agent가 새로운 Architecture를 생각하지 않는다는 것이다.
대신:
Clean Feature Template
+
AGENTS.md
+
현재 요구사항
을 사용한다.
결과적으로 Agent는 생성기에 가까워진다.
Codex는 Repository 안의 AGENTS.md를 읽고 프로젝트 구조, 명령어, 테스트 방법과 Coding Convention을 따를 수 있다.
따라서 Root에 다음을 만든다.
# iOS Project Rules
## Architecture
Application dependency direction:
App
→ AppComposition
→ FeatureComposition
→ Presentation / Data
→ Domain
→ Core
Data may depend on:
- Domain
- Network
Presentation may depend on:
- Domain
- DesignSystem
Domain may depend on:
- Core
Feature-to-Feature dependency is prohibited.
## Project
The Xcode project contains only:
- App
- Widget
- Extension
- Signing
- Capability
Do not modify `.xcodeproj` for normal feature creation.
## SwiftPM
Application modules live in:
`Packages/AppModules`
`Package.swift` is the source of truth for module dependencies.
## New Feature
When creating a new feature:
1. Read `.ai/templates/clean-feature/`.
2. Create Domain target.
3. Create Data target.
4. Create Presentation target.
5. Create Composition target.
6. Create Domain tests.
7. Update `Package.swift`.
8. Connect FeatureComposition to AppComposition.
9. Never add direct Feature-to-Feature dependencies.
10. Do not add dependencies not required by the feature.
## Naming
For `Profile` create:
- FeatureProfileDomain
- FeatureProfileData
- FeatureProfilePresentation
- FeatureProfileComposition
## Validation
After changing package structure:
1. Resolve SwiftPM dependencies.
2. Inspect the dependency graph.
3. Build the package/app.
4. Run relevant tests.
5. Report commands actually executed.
Never report a test as passed unless it was executed.
## Xcode
Do not edit `project.pbxproj` unless the requested change requires:
- App target
- Widget
- Extension
- Capability
- Signing
- Scheme / configuration changes
이 파일이 사실상 프로젝트의 Module Generator Policy가 된다.
Feature를 추가하고 싶다.
기존:
tuist scaffold feature --name Profile
또는:
myapp create-feature Profile
AI Agent 방식:
Profile 기능 추가해줘.
clean-feature 템플릿 사용.
화면:
ProfileView
필요 기능:
- 사용자 프로필 조회
- 새로고침
Dependency:
- Core
- Network
- DesignSystem
기존 Feature를 직접 참조하지 마.
생성 후 Dependency Graph와
iOS Simulator Build까지 검증해줘.
끝이다.
Agent는 먼저:
AGENTS.md
를 읽는다.
그다음:
.ai/templates/clean-feature/
를 확인한다.
그리고 생성한다.
FeatureProfileDomain/
FeatureProfileData/
FeatureProfilePresentation/
FeatureProfileComposition/
Agent가 다음을 추가한다.
.target(
name: "FeatureProfileDomain",
dependencies: [
"Core"
]
),
.target(
name: "FeatureProfileData",
dependencies: [
"FeatureProfileDomain",
"Network"
]
),
.target(
name: "FeatureProfilePresentation",
dependencies: [
"FeatureProfileDomain",
"DesignSystem"
]
),
.target(
name: "FeatureProfileComposition",
dependencies: [
"FeatureProfileDomain",
"FeatureProfileData",
"FeatureProfilePresentation"
]
),
.testTarget(
name: "FeatureProfileDomainTests",
dependencies: [
"FeatureProfileDomain"
]
)
그리고 AppComposition Dependency에:
"FeatureProfileComposition"
을 추가한다.
예를 들어 Route가 있다.
public enum AppRoute {
case home
case profile
}
Agent가 Profile 연결을 추가한다.
case .profile:
ProfileFeatureFactory.make(
httpClient: dependencies.httpClient
)
이제 Xcode App Target에는 변화가 없다.
변경:
Package.swift
Sources/
Tests/
AppComposition
정도다.
변경하지 않음:
project.pbxproj
이 구조가 안정되면 Feature 추가 PR에서 Xcode Project Conflict가 크게 줄어든다.
여기서부터 검증이다.
Agent에게 파일 생성만 맡기면 안 된다.
반드시 실제 Toolchain이 판단하게 한다.
먼저:
swift package resolve \
--package-path Packages/AppModules
그리고:
swift package show-dependencies \
--package-path Packages/AppModules
SwiftPM은 Dependency Graph를 실제로 Resolve한다.
순환 Dependency나 잘못된 Package 설정이 있으면 여기서 발견할 수 있다.
swift test \
--package-path Packages/AppModules
을 사용할 수 있다.
다만 UIKit·SwiftUI와 iOS 전용 Framework에 의존하는 테스트는 macOS에서 실행하는 swift test만으로 충분하지 않을 수 있다.
이 경우 Xcode/iOS Simulator 검증을 사용한다.
이 부분을 Agent Rule에도 명시한다.
예:
xcodebuild \
-project App/MyApp.xcodeproj \
-scheme MyApp \
-destination 'generic/platform=iOS Simulator' \
build
Agent가 실제 Build 결과를 확인한다.
파일 생성 완료
≠
빌드 성공
이다.
특정 iPhone 이름을 Rule에 고정하지 않는 편이 좋다.
Agent가:
xcrun simctl list devices available
로 사용 가능한 Simulator를 확인한다.
그 뒤 실제 테스트 가능한 Destination을 선택한다.
즉:
AGENTS.md
"iPhone 17 Pro를 사용해"
처럼 특정 환경을 고정하기보다는:
현재 설치된 iOS Simulator에서
사용 가능한 기기를 선택해 테스트한다.
라고 적는 편이 안전하다.
Agent가 다음처럼 끝내게 한다.
FeatureProfile 생성 완료
생성 Target:
- FeatureProfileDomain
- FeatureProfileData
- FeatureProfilePresentation
- FeatureProfileComposition
- FeatureProfileDomainTests
변경:
- Packages/AppModules/Package.swift
- AppComposition/AppRoute.swift
Dependency:
ProfileData
→ ProfileDomain
→ Core
ProfileData
→ Network
ProfilePresentation
→ ProfileDomain
→ DesignSystem
검증:
- swift package resolve: PASS
- dependency graph: PASS
- xcodebuild: PASS
- tests: PASS
Xcode project 변경:
없음
이렇게 하면 사람이 Review하기도 쉽다.
Agent에게 단순 Build만 요청하지 않는다.
다음 Rule도 확인하게 한다.
FeatureProfilePresentation
↓
FeatureProfileData
X
FeatureProfileDomain
↓
Network
X
FeatureHome
↓
FeatureProfile
X
SwiftPM Build가 성공하더라도 Architecture Rule 위반은 있을 수 있기 때문이다.
구현 Agent:
Feature 생성
↓
Reviewer Agent:
Package.swift 분석
Dependency Direction 확인
Public API 확인
Test 확인
↓
Build:
xcodebuild
이 구조면 더 안정적이다.
예를 들어 CI Script가 Package.swift를 읽어 다음을 검사한다.
Domain → Data
X
Presentation → Data
X
Feature → Feature
X
처음부터 이런 도구를 만들 필요는 없다.
프로젝트가 커진 뒤 반복되는 실수가 생겼을 때 추가하면 된다.
초기에는 Agent Review + SwiftPM Build만으로도 꽤 충분하다.
사내 CLI를 만들면 결국 CLI 자체를 유지해야 한다.
새 Architecture 추가
↓
CLI 수정
↓
Template 수정
↓
CLI Release
↓
개발자 업데이트
AI Agent + Repository Template은 다르다.
Template PR
↓
Merge
↓
모든 Agent가 바로 새 규칙 사용
별도의 Binary 배포가 없다.
예:
Presentation/
+ Coordinator.swift
Template 자체를 PR로 Review한다.
다음부터 생성되는 Feature가 모두 같은 구조를 사용한다.
Tuist Template의 역할을 Repository 자체가 담당한다.
예를 들어 ViewModel 방식에서 TCA로 이동한다고 하자.
기존:
Presentation
View
ViewModel
새 방식:
Presentation
Feature
State
Action
Reducer
View
그러면:
.ai/templates/clean-feature/
와:
AGENTS.md
를 수정한다.
Agent에게:
새 Feature부터 TCA Template을 사용하고
기존 Feature는 자동 Migration하지 마.
라고 적을 수도 있다.
.ai/templates/
├── clean-feature/
├── clean-feature-tca/
├── core-module/
├── network-service/
├── widget/
└── app-extension/
하지만 Widget이나 Extension처럼 실제 Xcode Target이 필요한 것은 별도로 취급한다.
Agent가 일반 Feature처럼 .xcodeproj를 건드리지 않고 끝낼 수 없기 때문이다.
사용자:
설정 화면 추가해줘.
Agent:
단순 UI Feature
→ clean-feature
사용자:
Notification Service Extension 추가해줘.
Agent:
Xcode Target 필요
→ extension template
→ project 변경 필요
이렇게 분기한다.
일반 Feature
→ Package.swift만 수정
Widget
→ Xcode Project 변경 허용
App Extension
→ Xcode Project 변경 허용
Capability
→ Xcode Project / Entitlement 검토
Signing
→ Human Review
Agent가 무엇을 건드려야 하는지 판단하기 쉬워진다.
하나의 AppModules가 항상 정답은 아니다.
다음 상황에서는 Package를 분리하는 편이 좋다.
다른 앱에서도 사용
독립 Release 필요
다른 Repository로 이동 가능
팀 Ownership 분리
외부 배포 가능성
독립적인 Dependency Graph 필요
예:
NetworkKit
AnalyticsKit
CameraKit
같은 모듈이다.
이때는:
Packages/
├── NetworkKit/
├── CameraKit/
└── AppModules/
처럼 하이브리드 구조로 가도 된다.
기준을 아주 단순하게 잡을 수 있다.
이 App 안에서만 사용
→ AppModules 안 Target
여러 App / Repository에서 사용
→ 별도 Swift Package
이렇게 하면 Package.swift가 지나치게 많이 생기는 것도 막을 수 있다.
SwiftPM은 Local Package Dependency를 공식적으로 지원하며, Local Package는 버전 Resolution 없이 지정한 경로의 Source를 그대로 사용한다.
또:
Package.resolved
Package Traits
Package Plugins
Package Registry
show-dependencies
같은 기능을 제공한다.
Swift 6.3에서는 Xcode와 같은 오픈소스 빌드 엔진인 Swift Build를 SwiftPM에서 사용하는 통합 기능도 Preview로 들어갔다.
방향 자체가:
Xcode Build
SwiftPM Build
사이의 기반을 점점 통합하는 쪽이다.
잘못된 요청:
어떤 Library가 필요한지 네가 알아서
최신 버전으로 추가해.
보다는:
기존 Dependency로 먼저 해결해.
새 외부 Package가 꼭 필요하면
추가하기 전에 이유와 대안을 보고해.
가 좋다.
AGENTS.md:
## External Dependencies
Do not add a new external dependency automatically.
Before adding one:
1. Check whether the current project can solve the problem.
2. Explain why a new dependency is required.
3. Report its license and maintenance risk.
4. Wait for approval.
Dependency 추가는 Supply Chain 문제이기도 하다.
AI가:
Dependency Graph는 문제없어 보입니다.
라고 말하는 것은 검증이 아니다.
SwiftPM이:
Resolved
해야 한다.
Agent:
코드가 컴파일될 것 같습니다.
도 검증이 아니다.
Xcode가:
BUILD SUCCEEDED
해야 한다.
역할을 나눈다.
Agent
→ 생성 / 수정 / 판단
SwiftPM
→ Dependency 검증
Compiler
→ Type 검증
Tests
→ 동작 검증
이 구조가 좋다.
개발자는:
CLI 명령을 기억하지 않는다.
Agent는:
필요한 CLI를 알고 실행한다.
따라서 정확하게는:
CLI-less Developer Experience
+
CLI-backed Verification
이다.
Custom CLI를 하나 더 유지하지 않아도 된다.
아침에 개발자가 말한다.
FeatureFavorites 만들어줘.
Clean Architecture 기본 Template.
Domain:
즐겨찾기 Entity와 Repository Protocol
Data:
현재 Network 사용
Presentation:
SwiftUI + Observation
Composition:
기존 AppDependencies 사용
Home Feature와 직접 Dependency 만들지 마.
생성 후 Build와 Test 확인해줘.
Agent가 작업한다.
몇 분 뒤 개발자는 코드 Review만 한다.
기존:
Architecture 변경
↓
Tuist Template 수정
↓
Project.swift 수정
↓
tuist generate
↓
Xcode
Agent + SwiftPM:
Architecture 변경
↓
Repository Template 수정
↓
AGENTS.md 수정
↓
Agent가 Package.swift와 Source 수정
↓
SwiftPM / Xcode Build
중간에 Project Generation 단계 자체가 없다.
최종적으로는 이렇게 간다.
MyApp.xcodeproj
│
App Target
│
▼
AppComposition
│
SwiftPM AppModules
│
┌────────────────┼────────────────┐
│ │ │
Core Shared Features
│ │ │
Network DesignSystem Home
Storage Profile
Logging Camera
Settings
그리고 옆에:
AGENTS.md
+
.ai/templates/
+
Codex
를 둔다.
하지만 중요한 차이가 있다.
Tuist Scaffold는:
정해진 Parameter
↓
정해진 Template
중심이다.
Agent는:
Template
+
Architecture Rule
+
현재 Repository
+
사용자 요구사항
을 함께 본다.
예를 들어:
Profile은 Network가 필요하지만
Settings는 Network가 필요 없음
을 판단해 Dependency를 다르게 만들 수 있다.
그래서 불필요한 Dependency도 줄일 수 있다.
Agent에게:
적당히 좋은 Architecture로 만들어줘.
라고 하면 매번 구조가 달라질 수 있다.
좋은 방식:
Template은 고정
Dependency Rule은 고정
Naming Rule은 고정
변경 가능 영역만 Agent 판단
이다.
AI를 Architecture 결정자로 쓰기보다 Architecture 규칙을 실행하는 Agent로 쓰는 편이 안정적이다.
Xcode Project에서 일반 Business Logic을 분리한다.
App
과:
AppModules
로 나눈다.
SwiftPM에:
Core
Network
DesignSystem
AppComposition
을 만든다.
첫 Feature를 사람이 제대로 만든다.
FeatureHome
이게 Reference Implementation이 된다.
FeatureHome 구조에서:
clean-feature Template
을 만든다.
AGENTS.md에 Architecture Rule을 기록한다.
Agent에게 두 번째 Feature를 생성시킨다.
FeatureProfile
Diff를 사람이 Review한다.
잘못된 패턴이 나오면 Prompt를 길게 고치는 대신:
Template
또는
AGENTS.md
를 고친다.
Agent가 같은 실수를 두 번 했다.
매번:
이번에는 Data에서 Presentation 참조하지 마.
라고 말하지 않는다.
AGENTS.md에 Rule을 추가한다.
Data targets MUST NOT depend on Presentation targets.
Agent 실패를 Repository Rule 개선으로 돌린다.
그렇게 시간이 지나면 프로젝트 자체가 Agent에게 점점 더 이해하기 쉬운 구조가 된다.
Tuist를 제거한다고 해서 다시 새로운 Project Generator나 Scaffold CLI를 만들어야 하는 것은 아니다.
2026년 iOS 프로젝트에서는 역할을 훨씬 단순하게 나눌 수 있다.
SwiftPM
Module / Dependency
Xcode
Application Target
Signing
Capability
Repository Template
Architecture Skeleton
AGENTS.md
Architecture Policy
AI Agent
Module 생성
Package.swift 수정
Dependency 연결
검증 실행
그리고 실제 검증은:
SwiftPM
Compiler
xcodebuild
Tests
가 한다.
이 구조에서 개발자는:
tuist scaffold ...
swift package ...
project.yml ...
같은 생성 명령을 매번 기억할 필요가 없다.
그냥 말하면 된다.
FeatureProfile 만들어줘.
우리 Clean Architecture 규칙대로 만들고
필요한 Dependency만 연결하고
빌드와 테스트까지 확인해줘.
Agent가 Repository에 이미 존재하는 Template과 Policy를 읽고 실행한다.
여기서 가장 중요한 부분은 AI가 마음대로 Architecture를 만드는 것이 아니다.
Architecture
사람이 정의
Template
Repository가 보관
Rule
AGENTS.md가 강제
반복 작업
Agent가 수행
하는 구조다.
한 줄로 정리하면:
Tuist를 AI로 대체하는 것이 아니라,
SwiftPM을 기반으로
Tuist가 제공하던 반복적인 개발자 경험을
AI Agent에게 맡기는 것이다.
저라면 지금 새 iOS 프로젝트에서는 작은 Xcode Project + SwiftPM Local Package + Clean Architecture Template + AGENTS.md + Coding Agent부터 시작한다.
그리고 실제로 불편해지는 지점이 나타날 때만 XcodeGen이나 Bazel 같은 더 무거운 도구를 추가한다.
AI Agent 시대에는 새로운 CLI를 하나 더 만드는 것보다, 프로젝트 자체를 Agent가 안정적으로 이해하고 수정할 수 있는 구조로 만드는 것이 점점 더 중요해지고 있다.
Apple Developer — Organizing your code with local packages
앱 내부 코드를 Local Swift Package로 분리해 모듈화하고 유지보수성과 재사용성을 높이는 Apple 공식 가이드.
Apple Developer — Managing files and folders in your Xcode project
File-system folder 기반 프로젝트 구조와, 파일 추가·삭제 시 Project File 변경을 줄여 Merge Conflict를 줄이는 Xcode 공식 가이드.
Swift.org — Swift 6.3 Released
Swift 6.3의 Swift Package Manager 개선과 Swift Build 통합 Preview를 설명하는 공식 릴리스 자료.
Swift Package Manager — Adding dependencies to a Swift package
package(path:)를 이용한 Local Package Dependency와 SwiftPM Dependency 관리에 대한 공식 문서.
Swift Package Manager — Resolving package dependency failures
swift package resolve, Package.resolved, swift package show-dependencies를 이용한 Dependency Resolution과 Graph 확인 방법.
OpenAI — Introducing Codex
Repository의 AGENTS.md를 통해 Codex에 코드 구조, 프로젝트 규칙, Test Command와 작업 방식을 제공하는 공식 자료.
OpenAI — Unrolling the Codex agent loop
Codex가 Repository와 디렉터리별 AGENTS.md를 어떻게 읽고, Sandbox·Tool·Instruction을 결합해 실제 작업을 수행하는지 설명한 공식 자료.
Apple은 Local Swift Package를 앱 내부 코드 모듈화에 사용하는 방식을 공식적으로 지원한다. Local Package는 앱과 같은 Repository에서 관리할 수 있으며 필요하면 이후 별도 Repository로 분리해 재사용할 수도 있다.
현재 Xcode의 folder 기반 프로젝트 구조는 디스크의 Folder와 Project Navigator를 동기화하고, 파일 추가·삭제 때 Project File 표현을 크게 늘리지 않도록 설계되어 있어 기존 Group 중심 구조보다 project.pbxproj 변경과 Merge Conflict를 줄이는 데 도움이 된다.
SwiftPM은 Local Dependency에 package(path:)를 공식 지원하고, swift package resolve로 Dependency Graph를 해석하며 swift package show-dependencies로 전체 의존 관계를 확인할 수 있다.
Swift 6.3은 2026년 3월 정식 출시됐으며 SwiftPM이 Swift Build를 사용할 수 있는 통합 기능이 Preview로 추가됐다. Swift 프로젝트의 Build Infrastructure를 장기적으로 더 통합하려는 방향을 확인할 수 있다.
Codex는 Repository의 AGENTS.md를 프로젝트 작업 지침으로 사용할 수 있다. Code Convention뿐 아니라 Repository 구조, Test 명령, 검증 절차 등을 제공할 수 있고, 하위 디렉터리에 더 구체적인 AGENTS.md를 배치해 Scope별 규칙을 적용하는 것도 가능하다.
따라서 이 구조에서 AI Agent는 새로운 Package Manager가 아니라 Repository Policy를 읽고 SwiftPM·Xcode Toolchain을 조작하는 자동화 계층으로 보는 것이 가장 정확하다.