viper-uikit — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited viper-uikit (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.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.
Approach: Production-First Iterative Refactoring — This skill is built for production enterprise codebases where stability and reviewability matter more than speed. Architecture changes are delivered through iterative refactoring — small, focused PRs (<=200 lines, single concern) tracked in a refactoring/ directory. AI tools consistently generate VIPER code with retain cycles, UIKit in Presenters, business logic in Presenters instead of Interactors, and strong references where weak ones are required. Every rule here exists to prevent those mistakes.Enterprise-grade UIKit VIPER architecture skill. Opinionated: prescribes passive Views (UIViewController IS the View), single-use-case Interactors, UIKit-free Presenters, protocol-isolated module boundaries, enum-based Builders with constructor injection, and optional Coordinator integration for multi-flow apps. VIPER's value is testability at scale — each layer is independently testable behind protocols. The tradeoff is boilerplate (5-15 files per module), which means code generation and consistent conventions are essential.
View (UIViewController) → Passive. Renders what Presenter says. Forwards user actions. Zero decisions.
Presenter → Traffic cop. Translates Interactor data → ViewModels. Decides WHEN to navigate.
Imports Foundation ONLY — never UIKit.
Interactor → Single use case. Business logic, data orchestration. Independent of UI.
Talks to Services/DataManagers via injected protocols.
Entity → Plain data structs (PONSOs). No behavior beyond computed properties.
Router/Wireframe → HOW to navigate. Holds weak ref to ViewController. Performs push/present/dismiss.
Builder/Module → Factory that assembles all components and wires references. Transient — creates and releases.UIKit Nav Stack ──strong──> UIViewController (View)
View ──strong──> Presenter
Presenter ──weak──> View ← MUST be weak (AnyObject protocol)
Presenter ──strong──> Interactor
Presenter ──strong──> Router
Interactor ──weak──> Presenter ← MUST be weak (output protocol: AnyObject)
Router ──weak──> ViewController ← MUST be weak (UIKit owns the VC)
Builder ──transient──> all ← creates, wires, returns VC, releasesIf ANY `weak` becomes `strong`, the module NEVER deallocates. Add deinit { print("deallocated") } to every VIPER component during development.
Is it about HOW something looks on screen (layout, animation, UIKit delegates)?
├── YES → VIEW
Is it about WHAT data to fetch, filter, sort, validate, combine?
├── YES → INTERACTOR (use case / business logic)
Is it about HOW to FORMAT data for display, or WHICH view state to show?
├── YES → PRESENTER (date formatting, string composition, ViewState mapping)
Is it about HOW to navigate to another screen?
├── YES → ROUTER (push, present, dismiss, transitions)
Is it a plain data container?
├── YES → ENTITY (struct, no logic beyond computed properties)
Is it about HOW to talk to a network/database?
└── YES → SERVICE / DATA MANAGER (external to VIPER, injected into Interactor)Is it a complex feature (Feed, Search, Editor, Checkout)?
├── YES → Full VIPER module (all 5 layers + Builder)
Is it a simple form (Login, Settings edit)?
├── YES → VIPER-lite (skip Interactor if no API/business rules, Presenter talks to Service directly)
Is it static content (About, Legal)?
├── YES → Plain ViewController or MVVM — VIPER is overkill
Is it a reusable UI component (Cell, Widget)?
├── YES → Custom UIView, NOT a VIPER module
Is it a background service (API, Storage)?
└── YES → Service class injected into Interactors — NOT a VIPER moduleOne-time result return (picker, form)?
├── Simple result → Closure callback on Builder
├── Complex/multiple → Delegate protocol (ModuleOutput)
Ongoing state sync between modules?
├── Known consumer → Shared service with delegate/closure
├── Unknown/multiple → NotificationCenter (app-wide only)
Forward-only data (navigating to detail)?
└── Pass via Router → Builder → Presenter (IDs or DTOs)The PRESENTING module's Router dismisses.
Presented module notifies via delegate → presenting Presenter receives → presenting Router dismisses.
The presented module NEVER calls dismiss on itself — presenting module must react to closure.Default workflow: Analyze & Refactor (below). New module creation applies the same patterns from a clean slate. In production enterprise codebases, most work is iterative modernization — not greenfield.
When: First encounter with a legacy UIKit MVC or poorly-structured codebase. The most common enterprise scenario.
references/anti-patterns.md)refactoring/ directory with per-feature plan files (references/refactoring-workflow.md)refactoring/discovered.md with full descriptions- [x] and update the Progress tableWhen: Building a new feature screen from scratch. Apply all patterns from the start.
{Module}Contract.swift (references/module-contracts.md)references/interactor-patterns.md)references/presenter-patterns.md)references/view-patterns.md)references/router-navigation.md)references/module-assembly.md)references/testing.md)When: Incrementally migrating one screen from MVC to VIPER.
weak var view protocol refModule.build()deinit fires on ALL components when navigating away, zero UIKit in PresenterWhen: Multi-flow navigation (auth + main, tabs) or deep linking requirements exceed per-module Routers.
start(), childCoordinators: [Coordinator] (references/coordinator-integration.md)UINavigationControllerDelegate for back-button cleanup of child coordinators<critical_rules> Whether generating new code or refactoring existing code, every output must be production-ready and PR-shippable — small, focused, and testable. ALWAYS:
showUserProfile(viewModel:), NOT setLabelText(_:)weak var with AnyObject-constrained protocols: Presenter→View, Interactor→Presenter(output), Router→VCViewState<T> enum for async data — never separate boolean flagsweak var viewController: UIViewController? — UIKit owns the VC[weak self] in Interactor callbacks/closures — module may be dismissed during async work<thought> analyzing ownership chain and retain cycle risks.</critical_rules>
When generating tests, ALWAYS:
var stubbed* and var *CallCount trackingaddTeardownBlock { [weak sut] in XCTAssertNil(sut) }viewController as weak (UIKit owns the VC), so if the test doesn't retain the VC itself, it deallocates the moment the test stack frame assigns it, and sut.viewController becomes nil before sut.navigateTo...() is even called. This is not optional — testing real Router behavior (not just mocks) requires this exact pattern:func test_navigateToDetail_pushesDetailVC() {
let sut = CheckoutRouter()
// STRONG reference — the test owns the VC, Router only has weak
let hostVC = UIViewController()
let mockNav = MockNavigationController(rootViewController: hostVC)
sut.viewController = hostVC // assigned to weak ref; hostVC keeps it alive
sut.navigateToConfirmation(orderId: "123")
XCTAssertTrue(mockNav.pushedViewController is OrderConfirmationViewController)
_ = hostVC // silence unused-warning; retention is the whole point
}A comprehensive VIPER test suite for a feature includes Interactor tests, Presenter tests with a mock Router, AND at least one real-Router test using this pattern to verify the navigation wiring actually works — not just that the Presenter calls router.navigateTo().
<fallback_strategies> When refactoring legacy code to VIPER, you may encounter stubborn issues. If you fail to fix the same error twice, break the loop:
AnyObject constraint. Use Xcode Debug Memory Graph. Why: missing even one weak creates an invisible retain cycle — the module never deallocates, leaking memory on every navigation.deinit to ALL 5 components. Navigate away. Missing log = that component is retained. Walk the reference chain from that component.ViewEventHandler, ItemActions, SearchHandler. Presenter conforms to all; mocks only to what they test. Why: one monolithic protocol forces mocks to stub 30+ methods even when testing one behavior.viewDidLoad fires BEFORE init completes for tab children. Wire Presenter in Builder before returning VC, NOT in viewDidLoad. Why: if Presenter wiring happens in viewDidLoad, the Presenter receives lifecycle events before it has its dependencies set.</fallback_strategies>
Before finalizing generated or refactored VIPER code, verify ALL:
[ ] Presenter does NOT import UIKit — only Foundation (and Combine if using publishers)
[ ] All cross-layer references use protocols, not concrete types
[ ] View only communicates with Presenter (never Interactor/Router directly)
[ ] Business logic in Interactor, presentation logic in Presenter — not mixed
[ ] Networking/persistence in injected services, not raw in Interactor
[ ] Router holds weak reference to VC
[ ] Interactor holds weak reference to Presenter (output)
[ ] Presenter holds weak reference to View
[ ] All weak-referenced protocols constrained to AnyObject
[ ] All Interactor callbacks dispatch to main thread before reaching View
[ ] ViewState<T> used for async data — no separate boolean flags
[ ] Module Builder creates all components before VC enters lifecycle
[ ] Dependencies injected via constructor with protocol types
[ ] Entities shared across modules live outside module folders
[ ] deinit added to all components during development
[ ] Tests exist for Interactor and Presenter
[ ] File size — new files <= 400 lines
[ ] PR scope — changes within defined scope, discoveries logged in refactoring/discovered.mdBefore generating async Interactor or migrating completion handlers: determine the project's concurrency approach. If unclear from context, ask the user.
| Project's concurrency stack | Companion skill | Apply when |
|---|---|---|
async/await, actors, Swift 6 | skills/swift-concurrency/SKILL.md | Migrating completion handlers, writing async Interactors, actor-based state |
DispatchQueue, OperationQueue | skills/gcd-operations/SKILL.md | Reviewing existing queue code, thread-safe Interactor state |
| Mixed (GCD stays, new code gets async/await) | Both skills | Apply GCD rules to existing code, concurrency rules to new code |
| Comprehensive testing beyond VIPER | skills/ios-testing/SKILL.md | See references/viper-testing.md for VIPER-specific patterns |
| Security audit | skills/ios-security/SKILL.md | Auditing Keychain usage, network security in VIPER apps |
Core references (read for every VIPER task):references/rules.md,references/module-contracts.md,references/memory-management.md. Then consult the specific reference based on which layer you're working with.
| Reference | When to Read |
|---|---|
| Understanding VIPER | |
references/rules.md | Do's and Don'ts quick reference: priority rules and critical anti-patterns |
references/module-contracts.md | Protocol design: 6 protocols per module, naming conventions, AnyObject constraints |
references/memory-management.md | Ownership chain, retain cycle debugging, closure captures, deallocation verification |
references/anti-patterns.md | AI-specific mistakes, severity-ranked violations, detection checklist |
| Building Modules | |
references/view-patterns.md | Passive View rules, lifecycle forwarding, UITableView/UICollectionView data flow |
references/presenter-patterns.md | UIKit-free Presenter, ViewState mapping, Entity→ViewModel translation |
references/interactor-patterns.md | Single use case, service injection, Entity boundaries, async/Combine patterns |
references/router-navigation.md | Weak VC reference, push/present/dismiss, deep linking, Coordinator delegation |
references/module-assembly.md | Builder/Factory patterns, dependency injection, wiring order, storyboard vs programmatic |
| Testing & Migration | |
references/testing.md | Interactor/Presenter/Router testing, mock patterns, memory leak assertions, snapshot testing |
references/coordinator-integration.md | When Routers aren't enough, Coordinator hierarchy, VIPER+Coordinator wiring |
references/migration-patterns.md | MVC→VIPER extraction, VIPER→SwiftUI migration via UIHostingController adapter |
| Enterprise | |
references/enterprise-patterns.md | Error propagation chain, ViewState management, analytics decoration, thread safety |
references/refactoring-workflow.md | refactoring/ directory protocol, per-feature plans, PR sizing, phase ordering |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.