ios-app-builder — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ios-app-builder (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Build iOS applications following Apple's official architecture guidance, modern Swift concurrency patterns, and SwiftUI best practices.
| Task | Reference File |
|---|---|
| Architecture layers (UI, Domain, Data) | architecture.md |
| SwiftUI patterns & navigation | swiftui-patterns.md |
| Swift Actors & concurrency | actors.md |
| Project & build configuration | project-setup.md |
| Project structure & modules | modularization.md |
| Testing approach | testing.md |
Creating a new project? → Read project-setup.md for SPM and Xcode setup → Read modularization.md for module structure → Use templates in assets/templates/
Adding a new feature? → Create feature module following modularization.md → Follow patterns in architecture.md
Building UI screens? → Read swiftui-patterns.md → Create Route + Screen + ViewModel
Setting up data layer? → Read data layer section in architecture.md → Create Repository protocol + actor implementation + @ModelActor
Working with concurrency? → Read actors.md for actors, Sendable, async/await → Follow the actor decision tree
Writing tests? → Read testing.md → Use Swift Testing framework with protocol-based test doubles
┌─────────────────────────────────────────┐
│ UI Layer │
│ (SwiftUI Views + @Observable VMs) │
├─────────────────────────────────────────┤
│ Domain Layer │
│ (Use Cases - optional, for reuse) │
├─────────────────────────────────────────┤
│ Data Layer │
│ (Repositories + DataSources) │
└─────────────────────────────────────────┘App/ # App target - entry point, navigation, @main
Features/
├── FeatureName/
│ ├── Sources/ # Route, Screen, ViewModel
│ └── Tests/ # Feature tests
Core/
├── Model/ # Domain models (pure Swift structs)
├── Data/ # Repositories (protocols + actor implementations)
├── Database/ # SwiftData models, @ModelActor
├── Network/ # URLSession, API models, DTOs
├── Common/ # Shared utilities, extensions
├── UI/ # Reusable SwiftUI components
├── DesignSystem/ # Theme, colors, typography, SF Symbols
└── Testing/ # Test utilities, test doublesFeatures/MyFeature/Package.swift with dependencies on Core modulesMyFeatureRoute.swift - Owns ViewModel (@State), handles navigationMyFeatureScreen.swift - Receives ViewModel (let), renders UIMyFeatureViewModel.swift - @MainActor @Observable with individual propertiesExpose individual properties — SwiftUI's @Observable tracks each property accessed per view, enabling granular re-renders. Avoid wrapping state in a single enum (that forces full re-renders).
@MainActor
@Observable
final class MyFeatureViewModel {
private(set) var items: [Item] = []
private(set) var isLoading = false
var errorMessage: String?
private let repository: MyRepository
init(repository: MyRepository) {
self.repository = repository
}
func onAppear() async {
isLoading = true
defer { isLoading = false }
do {
items = try await repository.getItems()
} catch {
errorMessage = error.localizedDescription
}
}
func deleteItem(id: String) async {
do {
try await repository.deleteItem(id: id)
items.removeAll { $0.id == id }
} catch {
errorMessage = error.localizedDescription
}
}
func refresh() async {
await onAppear()
}
}@Statelet — no wrapper needed, SwiftUI auto-tracks @Observable@Bindable locally in the body// Route: owns ViewModel via @State, connects navigation
struct MyFeatureRoute: View {
@State private var viewModel: MyFeatureViewModel
init(repository: MyRepository) {
viewModel = MyFeatureViewModel(repository: repository)
}
var body: some View {
MyFeatureScreen(viewModel: viewModel)
.task { await viewModel.onAppear() }
.refreshable { await viewModel.refresh() }
.navigationTitle("My Feature")
}
}
// Screen: receives ViewModel, reads properties directly
struct MyFeatureScreen: View {
let viewModel: MyFeatureViewModel
var body: some View {
List(viewModel.items) { item in
Text(item.name)
.swipeActions {
Button("Delete", role: .destructive) {
Task { await viewModel.deleteItem(id: item.id) }
}
}
}
.overlay {
if viewModel.isLoading && viewModel.items.isEmpty {
ProgressView()
}
}
.overlay {
if let error = viewModel.errorMessage, viewModel.items.isEmpty {
ContentUnavailableView(
"Error",
systemImage: "exclamationmark.triangle",
description: Text(error)
)
}
}
}
}When to use a state enum instead: Useenum UiStateonly for truly mutually exclusive states like onboarding flows (step1 | step2 | step3) or auth state (loggedIn | loggedOut), where you cannot show content and loading simultaneously.
| View role | Wrapper | Example |
|---|---|---|
| Creates the ViewModel | @State | @State private var viewModel = MyVM() |
| Receives from parent | let (none) | let viewModel: MyVM |
Needs $ bindings | @Bindable | @Bindable var viewModel = viewModel |
| Shared app-wide | @Environment | @Environment(AppState.self) var appState |
// Protocol: defines contract, Sendable for cross-isolation use
protocol MyRepository: Sendable {
func getItems() async throws -> [MyModel]
func observeItems() -> AsyncStream<[MyModel]>
func updateItem(_ item: MyModel) async throws
func sync() async throws
}
// Implementation: actor for thread-safe shared state
actor OfflineFirstMyRepository: MyRepository {
private let persistence: MyPersistenceActor
private let network: NetworkClient
init(persistence: MyPersistenceActor, network: NetworkClient) {
self.persistence = persistence
self.network = network
}
func getItems() async throws -> [MyModel] {
try await persistence.fetchAll()
}
func observeItems() -> AsyncStream<[MyModel]> {
persistence.observeAll()
}
func updateItem(_ item: MyModel) async throws {
try await persistence.upsert(item)
}
func sync() async throws {
let remoteItems = try await network.fetchItems()
for item in remoteItems {
try await persistence.upsert(item.toDomainModel())
}
}
}Swift 6+ (strict concurrency)
iOS 17+ (required for @Observable, SwiftData, #Preview)
SwiftData (persistence)
Swift Testing (unit tests)
XCTest (UI tests)Use Swift Package Manager for multi-module projects:
-strict-concurrency=complete)See project-setup.md for complete configuration.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.