From appkit
Use when reviewing native macOS AppKit Swift code before committing — checks MVC/MVVM, Swift 6 concurrency and main-actor correctness, memory management (retain cycles), target-action vs bindings, accessibility, theming, security, and performance, catching issues the compiler and UI tests won't find.
How this skill is triggered — by the user, by Claude, or both
Slash command
/appkit:appkit-code-reviewThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Run a code review **after the app builds and before committing**. This catches quality issues that aren't build errors and aren't visible in UI tests — patterns that compile and run but are wrong, fragile, or slow.
Run a code review after the app builds and before committing. This catches quality issues that aren't build errors and aren't visible in UI tests — patterns that compile and run but are wrong, fragile, or slow.
Read through the project's Swift files and check each section below. Lean on three layers of tooling first, then human judgment:
Compiler warnings. Build with warnings visible (xcodebuild ... | xcpretty, or read the raw log). In Swift 6 language mode, data-race and main-actor isolation violations are diagnostics — treat them as must-fix, not noise. Enable -warnings-as-errors in CI once clean.
swift-format. Lint against the project's own .swift-format (a JSON config you commit at the repo root, not a compiled binary — no unsigned artifact to ship/verify). swift-format is Apple's official formatter/linter; it ships inside the Xcode toolchain (also runnable as swift format …) and via brew install swift-format. Lint without rewriting files:
swift-format lint --strict --recursive --configuration .swift-format Sources/
(--strict makes any finding a non-zero exit for CI; drop it for advisory-only. To auto-apply formatting: swift-format format --in-place --recursive Sources/.) The config should enable the lint rules that matter most for AppKit correctness — NeverForceUnwrap, NeverUseForceTry, NeverUseImplicitlyUnwrappedOptionals — plus consistent formatting (import ordering, lowerCamelCase, early-exits, no semicolons). NeverUseImplicitlyUnwrappedOptionals is the one most likely to be noisy for programmatic AppKit's "set-in-viewDidLoad" properties — turn it off in the config if that pattern is intentional in your codebase.
swift-format has no custom-rule mechanism (unlike the WinUI Roslyn analyzer or SwiftLint's custom_rules), so the AppKit-semantic pitfalls below aren't enforced by it — cover them with the manual checklist plus these quick grep passes:
grep -rnE 'DispatchQueue\.main\.sync|\.wait\(\)' Sources/ # main-thread blocking / deadlock
grep -rnE 'NSColor\((red|calibratedRed|srgbRed|deviceRed):' Sources/ # hardcoded RGB (won't theme)
grep -rnE 'NSFont\(name:' Sources/ # hardcoded font (ignores type ramp)
grep -rnE '\bUI(View|ViewController|Color|Button|Label|TableView)\b' Sources/ # UIKit leaked into AppKit
grep -rLE 'setAccessibilityIdentifier' Sources/**/*ViewController*.swift # controllers missing a11y ids
The Swift static analyzer / Instruments. For deeper checks: xcodebuild analyze (clang/Swift static analysis), and Instruments (Leaks, Allocations, Time Profiler) when you suspect leaks or hot paths.
NSView, NSColor, NSImage, NSViewController) — those belong in the view layer or small mappersNSViewController/NSWindowController) do navigation, sheet/dialog coordination, and wiring — not business logicString, Bool, enums); the controller binds it to controlsasync/await for async work; completion handlers only at framework boundaries@MainActorawait MainActor.run { } or @MainActor methodsDispatchQueue.main.sync, no semaphore .wait() on the main thread, no .result-style sync waits — these deadlock the UITask.detached / await someActor.work()), results applied on @MainActorSendable respected across actor boundaries; no captured non-Sendable mutable state in concurrent closures[weak self] in escaping closures that outlive the call; weak delegates and target references where Cocoa expects themNSWindowController/top-level controllers retained for their lifetime (premature dealloc → window vanishes)NotificationCenter, KVO) on deinit if not using the block/NSKeyValueObservation token formTimer, DispatchSourceTimer) invalidated/cancelled; no strong target cyclessetAccessibilityIdentifier on every interactive control — unique and stable (UI tests depend on it)setAccessibilityLabel on icon-only controls / controls without visible textNSButton, NSPopUpButton, …) rather than click handlers on plain viewsnextKeyView loop, keyEquivalents); focus ring visibleNSColors / asset-catalog named colors only — no hardcoded RGB for chromeNSVisualEffectView; NSGlassEffectView on Tahoe, gated)controlSizeSecItem* / a wrapper) for credentialsNSOpenPanel/NSSavePanel + security-scoped bookmarks, not raw absolute pathsProcess args, never a shell string)WKWebView (if used) hardened: untrusted content isolated, JS disabled where not needed, navigation restrictedNSTableView/NSOutlineView/NSCollectionView (view recycling) — not a giant stack of viewslayoutSubtreeIfNeeded/forced layout not called in hot paths; constraints not churned per frameusing-equivalent resource cleanup (defer, scoped FileHandle/streams) for disposablesString(localized:) / a string catalog (.xcstrings) — not hardcodedFormatStyle / DateFormatter+NumberFormatter with the current locale — not manual formatsuserInterfaceLayoutDirection)After reviewing, summarize:
npx claudepluginhub markmals/mac-dev-skills --plugin appkitProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.