.
├── Package.swift root package: the executable target <App> (path App/<App>)
├── App/
│ ├── Resources/Info.plist bundle metadata; CFBundleExecutable = <App>
│ └── <App>/ @main, dependency wiring
│ └── Incubator/ experimental code (see below)
├── Packages/
│ ├── Core/ low-level building blocks: utilities, storage, networking
│ ├── Domain/ models and business logic
│ └── Features/ SwiftUI feature modules + DesignSystem
├── Tools/
│ └── SwiftorrentBench/ headless benchmark executable (Core only), product `swiftorrent-bench`
├── Fixtures/ big-buck-bunny.torrent (local/ is gitignored)
├── Scripts/ build.sh: app-agnostic Release build, .app packaging, install (quits a running copy first)
│ benchmark.sh: compares swiftorrent-bench with aria2c/transmission-cli
├── docs/
│ ├── ARCHITECTURE.md
│ ├── BENCHMARK.md benchmark procedure, results, optimization TODO
│ ├── WORKFLOW.md the user's guide (Claude skips it)
│ ├── decisions/ NNNN-short-title.md
│ ├── images/ README screenshot
│ └── plans/ active plans only
├── tmp/ scratch and logs (gitignored)
├── CLAUDE.md
├── README.md
└── SESSION_STATE.md
- Imports only point down: App → Features → Domain → Core. Skipping a layer downward is fine; importing upward never is.
- Feature targets never import each other. Shared views go in the
DesignSystemtarget; shared logic moves down to Domain. - Domain and Core don't import SwiftUI, AppKit or UIKit.
- No source file belongs to more than one target. Code needed by two targets lives in a package.
The app target only starts the app and wires dependencies. Everything else comes from packages.
New experimental features or tiny helpers may start in App/<App>/Incubator/. Move them into a package as soon as they stabilize or another target needs them.
- Each package has
Package.swift,Sources/<Target>/andTests/<Target>Tests/. Every target gets a test target. swift-tools-version: latest. Platforms: the minimums inREADME.md.- Feature targets use
swiftSettings: [.defaultIsolation(MainActor.self)]. Core and Domain stay nonisolated, with explicitSendabletypes. - Tests load media from
Fixtures/at the repo root (resolved from#filePath).
- Package directories are
Core,Domain,Features; their modules keep theSwiftorrentprefix (SwiftorrentCore,SwiftorrentDomain,SwiftorrentFeatures) to avoid generic module names. - The root
Package.swiftdepends on the three packages by path. The executable targetSwiftorrent(App/Swiftorrent) must matchCFBundleExecutable. - Run each package's tests with
--package-path Packages/<Name>. Tools/SwiftorrentBenchis a second root executable target that depends only on Core. It is not part of the app bundle.- Download-only policy: decisions/0002-download-only-policy.md.
- Hostile-input limits:
BencodeParser.maxDepth,TorrentMetainfoParser.maxPieceLength,PeerSupply.maxPeers, andBoundedHTTPClient(byte-capped HTTP bodies for trackers, exact sources and web-seed ranges) bound what a torrent, peer, tracker or server can make us allocate. Tests:HostileInputTests,BoundedHTTPTests,FuzzTests. - Peer transport:
PeerWireClienttalks to aPeerTransport(TCPPeerTransport,UTPPeerTransportinPeerTransport.swift). uTP is a sans-I/O state machine (UTPConnectionCore, driven by tests with a virtual clock) plus theUTPPacketcodec;UTPProbebacks--utp-probe. See decisions/0006-utp-outbound-fallback.md. - Big types are split into extensions by concern, one file each:
PeerWireClient(connect and wire I/O,+MSE,+Messagesfor availability and BEP 9/10/11,+Downloadfor the request pipeline) andTorrentSession(add/start/pause/resume,+Files,+Speed,+Persistence). Their state stays in the main file, module-internal so the extensions can reach it. - Connection limits:
FileDescriptorLimit(raised at launch) andConnectionBudget(.swarmfor peer connections across torrents,.resolvefor magnet probes); see decisions/0010-shared-connection-budget-patient-resolve.md.