Add scan flow MVP and local Axiom skill workspace

This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
This commit is contained in:
Matthias
2026-04-19 21:11:32 +02:00
parent 577214d474
commit a60a76b797
679 changed files with 138964 additions and 73 deletions

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-accessibility-diag",
"installedAt": "2026-04-12T08:05:39.269Z"
}

View File

@@ -0,0 +1,989 @@
---
name: axiom-accessibility-diag
description: Use when fixing VoiceOver issues, Dynamic Type violations, color contrast failures, touch target problems, keyboard navigation gaps, or Reduce Motion support - comprehensive accessibility diagnostics with WCAG compliance, Accessibility Inspector workflows, and App Store Review preparation for iOS/macOS
license: MIT
metadata:
version: "1.0.0"
---
# Accessibility Diagnostics
## Overview
Systematic accessibility diagnosis and remediation for iOS/macOS apps. Covers the 7 most common accessibility issues that cause App Store rejections and user complaints.
**Core principle** Accessibility is not optional. iOS apps must support VoiceOver, Dynamic Type, and sufficient color contrast to pass App Store Review. Users with disabilities depend on these features.
## When to Use This Skill
- Fixing VoiceOver navigation issues (missing labels, wrong element order)
- Supporting Dynamic Type (text scaling for vision disabilities)
- Meeting color contrast requirements (WCAG AA/AAA)
- Fixing touch target size violations (< 44x44pt)
- Adding keyboard navigation (iPadOS/macOS)
- Supporting Reduce Motion (vestibular disorders)
- Preparing for App Store Review accessibility requirements
- Responding to user complaints about accessibility
## The 7 Critical Accessibility Issues
### 1. VoiceOver Labels & Hints (CRITICAL - App Store Rejection)
**Problem** Missing or generic accessibility labels prevent VoiceOver users from understanding UI purpose.
**WCAG** 4.1.2 Name, Role, Value (Level A)
#### Common violations
```swift
// WRONG - No label (VoiceOver says "Button")
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
// WRONG - Generic label
.accessibilityLabel("Button")
// WRONG - Reads implementation details
.accessibilityLabel("cart.badge.plus") // VoiceOver: "cart dot badge dot plus"
// CORRECT - Descriptive label
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Add to cart")
// CORRECT - With hint for complex actions
.accessibilityLabel("Add to cart")
.accessibilityHint("Double-tap to add this item to your shopping cart")
```
#### When to use hints
- Action is not obvious from label ("Add to cart" is obvious, no hint needed)
- Multi-step interaction ("Swipe right to confirm, left to cancel")
- State change ("Double-tap to toggle notifications on or off")
#### Decorative elements
```swift
// CORRECT - Hide decorative images from VoiceOver
Image("decorative-pattern")
.accessibilityHidden(true)
// CORRECT - Combine multiple elements into one label
HStack {
Image(systemName: "star.fill")
Text("4.5")
Text("(234 reviews)")
}
.accessibilityElement(children: .combine)
.accessibilityLabel("Rating: 4.5 stars from 234 reviews")
```
#### Testing
- Enable VoiceOver: Cmd+F5 (simulator) or triple-click side button (device)
- Navigate: Swipe right/left to move between elements
- Listen: Does VoiceOver announce purpose clearly?
- Check order: Does navigation order match visual layout?
---
### 2. Dynamic Type Support (HIGH - User Experience)
**Problem** Fixed font sizes prevent users with vision disabilities from reading text.
**WCAG** 1.4.4 Resize Text (Level AA - support 200% scaling without loss of content/functionality)
#### Common violations
```swift
// WRONG - Fixed size, won't scale
Text("Price: $19.99")
.font(.system(size: 17))
UILabel().font = UIFont.systemFont(ofSize: 17)
// WRONG - Custom font without scaling
Text("Headline")
.font(Font.custom("CustomFont", size: 24))
// CORRECT - SwiftUI semantic styles (auto-scales)
Text("Price: $19.99")
.font(.body)
Text("Headline")
.font(.headline)
// CORRECT - UIKit semantic styles
label.font = UIFont.preferredFont(forTextStyle: .body)
// CORRECT - Custom font with scaling
let customFont = UIFont(name: "CustomFont", size: 24)!
label.font = UIFontMetrics.default.scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
```
#### Custom sizes that scale with Dynamic Type
```swift
// WRONG - Fixed size, won't scale
Text("Price: $19.99")
.font(.system(size: 17))
// ACCEPTABLE - Custom font without scaling (accessibility violation)
Text("Headline")
.font(Font.custom("CustomFont", size: 24))
// GOOD - Custom size that scales with Dynamic Type
Text("Large Title")
.font(.system(size: 60).relativeTo(.largeTitle))
Text("Custom Headline")
.font(.system(size: 24).relativeTo(.title2))
// BEST - Use semantic styles when possible
Text("Headline")
.font(.headline)
```
**How `relativeTo:` works**
- Base size: Your exact pixel size (24pt, 60pt, etc.)
- Scales with: The text style you specify (`.title2`, `.largeTitle`, etc.)
- Result: When user increases text size in Settings, your custom size grows proportionally
**Example**
- `.title2` base: ~22pt → Your custom: 24pt (1.09x larger)
- User increases to "Extra Large" text
- `.title2` grows to ~28pt → Your custom grows to ~30.5pt (maintains 1.09x ratio)
**Fix hierarchy (best to worst)**
1. **Best**: Use semantic styles (`.title`, `.body`, `.caption`)
2. **Good**: Use `.system(size:).relativeTo()` for required custom sizes
3. **Acceptable**: Custom font with `.dynamicTypeSize()` modifier
4. **Unacceptable**: Fixed sizes that never scale
#### SwiftUI text styles
- `.largeTitle` - 34pt (scales to 44pt at accessibility sizes)
- `.title` - 28pt
- `.title2` - 22pt
- `.title3` - 20pt
- `.headline` - 17pt semibold
- `.body` - 17pt (default)
- `.callout` - 16pt
- `.subheadline` - 15pt
- `.footnote` - 13pt
- `.caption` - 12pt
- `.caption2` - 11pt
#### Layout considerations
```swift
// WRONG - Fixed frame breaks with large text
Text("Long product description...")
.font(.body)
.frame(height: 50) // Clips at large text sizes
// CORRECT - Flexible frame
Text("Long product description...")
.font(.body)
.lineLimit(nil) // Allow multiple lines
.fixedSize(horizontal: false, vertical: true)
// CORRECT - Stack rearranges at large sizes
HStack {
Text("Label:")
Text("Value")
}
.dynamicTypeSize(...DynamicTypeSize.xxxLarge) // Limit maximum size if needed
```
#### Testing
1. Xcode Preview: Environment override
```swift
.environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge)
```
2. Simulator: Settings → Accessibility → Display & Text Size → Larger Text → Drag to maximum
3. Device: Settings → Accessibility → Display & Text Size → Larger Text
4. Check: Does text remain readable? Does layout adapt? Is any text clipped?
---
### 3. Color Contrast (HIGH - Vision Disabilities)
**Problem** Low contrast text is unreadable for users with vision disabilities or in bright sunlight.
#### WCAG
- **1.4.3 Contrast (Minimum)** — Level AA
- Normal text (< 18pt): 4.5:1 contrast ratio
- Large text (≥ 18pt or ≥ 14pt bold): 3:1 contrast ratio
- **1.4.6 Contrast (Enhanced)** — Level AAA
- Normal text: 7:1 contrast ratio
- Large text: 4.5:1 contrast ratio
#### Common violations
```swift
// ❌ WRONG - Low contrast (1.8:1 - fails WCAG)
Text("Warning")
.foregroundColor(.yellow) // on white background
// ❌ WRONG - Low contrast in dark mode
Text("Info")
.foregroundColor(.gray) // on black background
// ✅ CORRECT - High contrast (7:1+ passes AAA)
Text("Warning")
.foregroundColor(.orange) // or .red
// ✅ CORRECT - System colors adapt to light/dark mode
Text("Info")
.foregroundColor(.primary) // Black in light mode, white in dark
Text("Secondary")
.foregroundColor(.secondary) // Automatic high contrast
```
#### Differentiate Without Color
```swift
// ❌ WRONG - Color alone indicates status
Circle()
.fill(isAvailable ? .green : .red)
// ✅ CORRECT - Color + icon/text
HStack {
Image(systemName: isAvailable ? "checkmark.circle.fill" : "xmark.circle.fill")
Text(isAvailable ? "Available" : "Unavailable")
}
.foregroundColor(isAvailable ? .green : .red)
// ✅ CORRECT - Respect system preference
if UIAccessibility.shouldDifferentiateWithoutColor {
// Use patterns, icons, or text instead of color alone
}
```
#### Testing
1. Use Color Contrast Analyzer tool (free download)
2. Screenshot your UI, measure text vs background
3. Check both light and dark mode
4. Settings → Accessibility → Display & Text Size → Increase Contrast (test with this ON)
#### Quick reference
- Black (#000000) on White (#FFFFFF): 21:1 ✅ AAA
- Dark Gray (#595959) on White: 7:1 ✅ AAA
- Medium Gray (#767676) on White: 4.5:1 ✅ AA
- Light Gray (#959595) on White: 2.8:1 ❌ Fails
---
### 4. Touch Target Sizes (MEDIUM - Motor Disabilities)
**Problem** Small tap targets are difficult or impossible for users with motor disabilities.
**WCAG** 2.5.5 Target Size (Level AAA - 44x44pt minimum)
**Apple HIG** 44x44pt minimum for all tappable elements
#### Common violations
```swift
// ❌ WRONG - Too small (24x24pt)
Button("×") {
dismiss()
}
.frame(width: 24, height: 24)
// ❌ WRONG - Small icon without padding
Image(systemName: "heart")
.font(.system(size: 16))
.onTapGesture { }
// ✅ CORRECT - Minimum 44x44pt
Button("×") {
dismiss()
}
.frame(minWidth: 44, minHeight: 44)
// ✅ CORRECT - Larger icon or padding
Image(systemName: "heart")
.font(.system(size: 24))
.frame(minWidth: 44, minHeight: 44)
.contentShape(Rectangle()) // Expand tap area
.onTapGesture { }
// ✅ CORRECT - UIKit button with edge insets
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
// Total size: icon size + insets ≥ 44x44pt
```
#### Spacing between targets
```swift
// ❌ WRONG - Targets too close (hard to tap accurately)
HStack(spacing: 4) {
Button("Edit") { }
Button("Delete") { }
}
// ✅ CORRECT - Adequate spacing (8pt minimum, 12pt better)
HStack(spacing: 12) {
Button("Edit") { }
Button("Delete") { }
}
```
#### Testing
1. Accessibility Inspector: Xcode → Open Developer Tool → Accessibility Inspector
2. Select "Audit" tab → Run audit → Check for "Small Text" and "Hit Region" warnings
3. Manual: Tap with one finger (not stylus) — can you hit it reliably without mistakes?
---
### 5. Keyboard Navigation (MEDIUM - iPadOS/macOS)
**Problem** Users who cannot use touch/mouse cannot navigate app.
**WCAG** 2.1.1 Keyboard (Level A - all functionality available via keyboard)
#### Common violations
```swift
// ❌ WRONG - Custom gesture without keyboard alternative
.onTapGesture {
showDetails()
}
// No way to trigger with keyboard
// ✅ CORRECT - Button provides keyboard support automatically
Button("Show Details") {
showDetails()
}
.keyboardShortcut("d", modifiers: .command) // Optional shortcut
// ✅ CORRECT - Custom control with focus support
struct CustomButton: View {
@FocusState private var isFocused: Bool
var body: some View {
Text("Custom")
.focusable()
.focused($isFocused)
.onKeyPress(.return) {
action()
return .handled
}
}
}
```
#### Focus management
```swift
// ✅ CORRECT - Set initial focus
.focusSection() // Group related controls
.defaultFocus($focus, .constant(true)) // Set default
// ✅ CORRECT - Move focus after action
@FocusState private var focusedField: Field?
Button("Next") {
focusedField = .next
}
```
#### Testing (iPadOS/macOS)
1. Connect keyboard to iPad or use Mac
2. Press Tab - does focus move to interactive elements?
3. Press Space/Return - does focused element activate?
4. Check custom controls have visible focus indicator
5. Can you reach all functionality without mouse/touch?
---
### 6. Reduce Motion Support (MEDIUM - Vestibular Disorders)
**Problem** Animations cause discomfort, nausea, or seizures for users with vestibular disorders.
**WCAG** 2.3.3 Animation from Interactions (Level AAA - motion animation can be disabled)
#### Common violations
```swift
// ❌ WRONG - Always animates (can cause nausea)
.onAppear {
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
scale = 1.0
}
}
// ❌ WRONG - Parallax scrolling without opt-out
ScrollView {
GeometryReader { geo in
Image("hero")
.offset(y: geo.frame(in: .global).minY * 0.5) // Parallax
}
}
// ✅ CORRECT - Respect Reduce Motion preference
.onAppear {
if UIAccessibility.isReduceMotionEnabled {
scale = 1.0 // Instant
} else {
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
scale = 1.0
}
}
}
// ✅ CORRECT - Simpler animation or cross-fade
if UIAccessibility.isReduceMotionEnabled {
// Cross-fade or instant change
withAnimation(.linear(duration: 0.2)) {
showView = true
}
} else {
// Complex spring animation
withAnimation(.spring()) {
showView = true
}
}
```
#### SwiftUI modifier
```swift
// ✅ CORRECT - Automatic support
.animation(.spring(), value: isExpanded)
.transaction { transaction in
if UIAccessibility.isReduceMotionEnabled {
transaction.animation = nil // Disable animation
}
}
```
#### Testing
1. Settings → Accessibility → Motion → Reduce Motion (toggle ON)
2. Navigate app - are animations reduced or eliminated?
3. Test: Transitions, scrolling effects, parallax, particle effects
4. Video autoplay should also respect this preference
---
### 7. Common Violations (HIGH - App Store Review)
#### Images Without Labels
```swift
// ❌ WRONG - Informative image without label
Image("product-photo")
// ✅ CORRECT - Informative image with label
Image("product-photo")
.accessibilityLabel("Red sneakers with white laces")
// ✅ CORRECT - Decorative image hidden
Image("background-pattern")
.accessibilityHidden(true)
```
#### Buttons With Wrong Traits
```swift
// ❌ WRONG - Custom button without button trait
Text("Submit")
.onTapGesture {
submit()
}
// VoiceOver announces as "Submit, text" not "Submit, button"
// ✅ CORRECT - Use Button for button-like controls
Button("Submit") {
submit()
}
// VoiceOver announces as "Submit, button"
// ✅ CORRECT - Custom control with correct trait
Text("Submit")
.accessibilityAddTraits(.isButton)
.onTapGesture {
submit()
}
```
#### Inaccessible Custom Controls
```swift
// ❌ WRONG - Custom slider without accessibility support
struct CustomSlider: View {
@Binding var value: Double
var body: some View {
// Drag gesture only, no VoiceOver support
GeometryReader { geo in
// ...
}
.gesture(DragGesture()...)
}
}
// ✅ CORRECT - Custom slider with accessibility actions
struct CustomSlider: View {
@Binding var value: Double
var body: some View {
GeometryReader { geo in
// ...
}
.gesture(DragGesture()...)
.accessibilityElement()
.accessibilityLabel("Volume")
.accessibilityValue("\(Int(value))%")
.accessibilityAdjustableAction { direction in
switch direction {
case .increment:
value = min(value + 10, 100)
case .decrement:
value = max(value - 10, 0)
@unknown default:
break
}
}
}
}
```
#### Missing State Announcements
```swift
// ❌ WRONG - State change without announcement
Button("Toggle") {
isOn.toggle()
}
// ✅ CORRECT - State change with announcement
Button("Toggle") {
isOn.toggle()
UIAccessibility.post(
notification: .announcement,
argument: isOn ? "Enabled" : "Disabled"
)
}
// ✅ CORRECT - Automatic state with accessibilityValue
Button("Toggle") {
isOn.toggle()
}
.accessibilityValue(isOn ? "Enabled" : "Disabled")
```
## 8. Assistive Access Support (iOS 17+ — Cognitive Disabilities)
**Problem** App is unavailable or broken in Assistive Access mode, excluding users with cognitive disabilities who rely on a simplified system experience.
Assistive Access is a system-wide mode (Settings > Accessibility > Assistive Access) that replaces the standard iOS UI with large controls, simplified navigation, and reduced cognitive load. Apps that don't opt in are hidden from users in this mode.
#### Symptom: App missing from Assistive Access home screen
Your app doesn't appear under "Optimized Apps" in Assistive Access settings.
```xml
<!-- ✅ FIX - Add to Info.plist -->
<key>UISupportsAssistiveAccess</key>
<true/>
```
This makes the app available and launches it full screen in Assistive Access mode. Without this key, users in Assistive Access mode cannot access your app at all.
#### Symptom: Standard UI too complex for Assistive Access users
Your app launches in Assistive Access but shows the full standard interface, overwhelming users who need simplified controls.
```swift
// ✅ FIX - Provide a dedicated Assistive Access scene
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView() // Standard UI
}
AssistiveAccess {
AssistiveAccessContentView() // Simplified UI
}
}
}
```
The `AssistiveAccess` scene type provides a separate entry point. When the system is in Assistive Access mode, it uses this scene instead of the standard `WindowGroup`. Native SwiftUI controls inside this scene automatically adopt the Assistive Access visual style (large buttons, prominent navigation, grid/row layout).
#### Symptom: App already designed for cognitive accessibility but displays in reduced frame
If your app is already purpose-built for users with cognitive disabilities (e.g., AAC apps), it may appear in a reduced frame rather than full screen.
```xml
<!-- ✅ FIX - Add to Info.plist for apps already designed for cognitive accessibility -->
<key>UISupportsFullScreenInAssistiveAccess</key>
<true/>
```
This displays your app identically to its standard appearance, bypassing the Assistive Access frame.
#### Detecting Assistive Access at runtime
```swift
struct MyView: View {
@Environment(\.accessibilityAssistiveAccessEnabled) var assistiveAccessEnabled
var body: some View {
if assistiveAccessEnabled {
// Simplified content
} else {
// Standard content
}
}
}
```
#### UIKit implementation
For UIKit apps, use the `.windowAssistiveAccessApplication` scene session role in your `UISceneConfiguration` to route to a dedicated scene delegate for the Assistive Access experience.
#### Design principles for Assistive Access scenes
- **Distill to core functionality** — One or two essential features, not the full app
- **Large, prominent controls** — Ample spacing, no hidden gestures or timed interactions
- **Multiple representations** — Pair text with icons; use visual alternatives
- **Step-by-step navigation** — Clear back buttons, consistent patterns
- **Safe interactions** — Remove irreversible actions; confirm destructive ones
#### Adding navigation icons
```swift
NavigationStack {
MyView()
.navigationTitle("My Feature")
.assistiveAccessNavigationIcon(systemImage: "star.fill")
}
```
#### Testing
1. **Device** — Enable Assistive Access in Settings > Accessibility > Assistive Access, verify app appears in "Optimized Apps", test the full user flow
2. **Accessibility Inspector** — Run audit on the Assistive Access scene for label, contrast, and hit region issues
---
## Accessibility Inspector Workflow
### 1. Launch Accessibility Inspector
Xcode → Open Developer Tool → Accessibility Inspector
### 2. Select Target
- Dropdown: Choose running simulator or connected device
- Target: Select your app
### 3. Inspection Mode
- Click "Inspection Pointer" button (crosshair icon)
- Hover over UI elements to see:
- Label, Value, Hint, Traits
- Frame, Path
- Actions available
- Parent/child hierarchy
### 4. Run Audit
- Click "Audit" tab
- Click "Run Audit" button
- Review findings:
- **Contrast** — Color contrast issues
- **Hit Region** — Touch target size issues
- **Clipped Text** — Text truncation with Dynamic Type
- **Element Description** — Missing labels/hints
- **Traits** — Wrong accessibility traits
### 5. Fix and Re-Test
- Click each finding for details
- Fix in code
- Re-run audit to verify
## VoiceOver Testing Checklist
### Enable VoiceOver
- **Simulator** Cmd+F5 or Settings → Accessibility → VoiceOver
- **Device** Triple-click side button (if enabled in Settings)
### Navigation Testing
1. ☐ Swipe right/left - moves logically through UI elements
2. ☐ Each element announces purpose clearly
3. ☐ No unlabeled elements (except decorative)
4. ☐ Heading navigation works (swipe up/down with 2 fingers)
5. ☐ Container navigation works (swipe left/right with 3 fingers)
### Interaction Testing
1. ☐ Double-tap activates buttons
2. ☐ Swipe up/down adjusts sliders/pickers (with `.accessibilityAdjustableAction`)
3. ☐ Custom gestures have VoiceOver equivalents
4. ☐ Text fields announce keyboard type
5. ☐ State changes are announced
### Content Testing
1. ☐ Images have descriptive labels or are hidden
2. ☐ Error messages are announced
3. ☐ Loading states are announced
4. ☐ Modal sheets announce role
5. ☐ Alerts announce automatically
## App Store Review Preparation
### Required Accessibility Features (iOS)
1. **VoiceOver Support**
- All UI elements must have labels
- Navigation must be logical
- All actions must be performable
2. **Dynamic Type**
- Text must scale from -3 to +12 sizes
- Layout must adapt without clipping
3. **Sufficient Contrast**
- Minimum 4.5:1 for normal text
- Minimum 3:1 for large text (≥18pt)
### App Store Connect Metadata
When submitting:
1. Accessibility → Select features your app supports:
- ☑ VoiceOver
- ☑ Dynamic Type
- ☑ Increased Contrast
- ☑ Reduce Motion (if supported)
2. Test Notes: Document accessibility testing
```
Accessibility Testing Completed:
- VoiceOver: All screens tested with VoiceOver enabled
- Dynamic Type: Tested at all size categories
- Color Contrast: Verified 4.5:1 minimum contrast
- Touch Targets: All buttons minimum 44x44pt
- Reduce Motion: Animations respect user preference
```
### Common Rejection Reasons
1. **"App is not fully functional with VoiceOver"**
- Missing labels on images/buttons
- Unlabeled custom controls
- Actions not performable with VoiceOver
2. **"Text is not readable at all Dynamic Type sizes"**
- Fixed font sizes
- Text clipping at large sizes
- Layout breaks at accessibility sizes
3. **"Insufficient color contrast"**
- Text fails 4.5:1 ratio
- UI elements fail 3:1 ratio
- Color-only indicators
---
## Design Review Pressure: Defending Accessibility Requirements
### The Problem
Under design review pressure, you'll face requests to:
- "Those VoiceOver labels make the code messy - can we skip them?"
- "Dynamic Type breaks our carefully designed layout - let's lock font sizes"
- "The high contrast requirement ruins our brand aesthetic"
- "44pt touch targets are too big - make them smaller for a cleaner look"
These sound like reasonable design preferences. **But they violate App Store requirements and exclude 15% of users.** Your job: defend using App Store guidelines and legal requirements, not opinion.
### Red Flags — Designer Requests That Violate Accessibility
If you hear ANY of these, **STOP and reference this skill**:
- ❌ **"Skip VoiceOver labels on icon-only buttons"** App Store rejection (Guideline 2.5.1)
- ❌ **"Use fixed 14pt font for compact design"** Excludes users with vision disabilities
- ❌ **"3:1 contrast ratio is fine"** Fails WCAG AA for text (needs 4.5:1)
- ❌ **"Make buttons 36x36pt for clean aesthetic"** Fails touch target requirement (44x44pt minimum)
- ❌ **"Disable Dynamic Type in this screen"** App Store rejection risk
- ❌ **"Color-code without labels (red=error, green=success)"** Excludes colorblind users (8% of men)
### How to Push Back Professionally
#### Step 1: Show the Guideline
```
"I want to support this design direction, but let me show you Apple's App Store
Review Guideline 2.5.1:
'Apps should support accessibility features such as VoiceOver and Dynamic Type.
Failure to include sufficient accessibility features may result in rejection.'
Here's what we need for approval:
1. VoiceOver labels on all interactive elements
2. Dynamic Type support (can't lock font sizes)
3. 4.5:1 contrast ratio for text, 3:1 for UI
4. 44x44pt minimum touch targets
Let me show where our design currently falls short..."
```
#### Step 2: Demonstrate the Risk
Open the app with accessibility features enabled:
- **VoiceOver** (Cmd+F5): Show buttons announcing "Button" instead of purpose
- **Largest Text Size**: Show layout breaking or text clipping
- **Color Contrast Analyzer**: Show failing contrast ratios
- **Touch target overlay**: Show targets < 44pt
#### Reference
- App Store Review Guideline 2.5.1
- WCAG 2.1 Level AA (industry standard)
- ADA compliance requirements (legal risk in US)
#### Step 3: Offer Compromise
```
"I can achieve your aesthetic goals while meeting accessibility requirements:
1. VoiceOver labels: Add them programmatically (invisible in UI, required for approval)
2. Dynamic Type: Use layout techniques that adapt (examples from Apple HIG)
3. Contrast: Adjust colors slightly to meet 4.5:1 (I'll show options that preserve brand)
4. Touch targets: Expand hit areas programmatically (visual size stays the same)
These changes won't affect the visual design you're seeing, but they're required
for App Store approval and legal compliance."
```
#### Step 4: Document the Decision
If overruled (designer insists on violations):
```
Slack message to PM + designer:
"Design review decided to proceed with:
- Fixed font sizes (disabling Dynamic Type)
- 38x38pt buttons (below 44pt requirement)
- 3.8:1 text contrast (below 4.5:1 requirement)
Important: These changes violate App Store Review Guideline 2.5.1 and WCAG AA.
This creates three risks:
1. App Store rejection during review (adds 1-2 week delay)
2. ADA compliance issues if user files complaint (legal risk)
3. 15% of potential users unable to use app effectively
I'm flagging this proactively so we can prepare a response plan if rejected."
```
#### Why this works
- You're not questioning their design taste
- You're raising App Store rejection risk (business impact)
- You're citing specific guidelines (not opinion)
- You're offering solutions that preserve visual design
- You're documenting the decision (protects you post-rejection)
### Real-World Example: App Store Rejection (48-Hour Resubmit Window)
#### Scenario
- 48 hours until resubmit deadline after rejection
- Apple cited: "2.5.1 - Insufficient VoiceOver support"
- Designer says: "Just add generic labels quickly"
- PM watching the meeting, wants fastest fix
#### What to do
```swift
// ❌ WRONG - Generic labels (will fail re-review)
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Button") // Apple will reject again
// ✅ CORRECT - Descriptive labels (passes review)
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Add to cart")
.accessibilityHint("Double-tap to add this item to your shopping cart")
```
#### In the meeting, demonstrate
1. Enable VoiceOver (Cmd+F5)
2. Show "Button" announcement (generic - fails)
3. Show "Add to cart" announcement (descriptive - passes)
4. Reference Apple's rejection message: "Elements must have descriptive labels"
**Time estimate** 2-4 hours to audit all interactive elements and add proper labels.
#### Result
- Honest time estimate prevents second rejection
- Proper labels pass Apple review
- Resubmit accepted within 48 hours
### When to Accept the Design Decision (Even If You Disagree)
Sometimes designers have valid reasons to override accessibility guidelines. Accept if:
- [ ] They understand the App Store rejection risk
- [ ] They're willing to delay launch if rejected
- [ ] You document the decision in writing
- [ ] They commit to fixing if rejected
#### Document in Slack
```
"Design review decided to proceed with [specific violations].
We understand this creates:
- App Store rejection risk (Guideline 2.5.1)
- Potential 1-2 week delay if rejected
- Need to audit and fix all instances if rejected
Monitoring plan:
- Submit for review with current design
- If rejected, implement proper accessibility (estimated 2-4 hours)
- Have accessibility-compliant version ready as backup"
```
This protects both of you and shows you're not blocking - just de-risking.
---
## WCAG Compliance Levels
### Level A (Minimum — Required for App Store)
- 1.1.1 Non-text Content — Images have text alternatives
- 2.1.1 Keyboard — All functionality via keyboard (iPadOS/macOS)
- 4.1.2 Name, Role, Value — Elements have accessible names
### Level AA (Standard — Recommended)
- 1.4.3 Contrast (Minimum) — 4.5:1 text, 3:1 UI
- 1.4.4 Resize Text — Support 200% text scaling
- 1.4.5 Images of Text — Use real text when possible
### Level AAA (Enhanced — Best Practice)
- 1.4.6 Contrast (Enhanced) — 7:1 text, 4.5:1 UI
- 2.3.3 Animation from Interactions — Reduce Motion support
- 2.5.5 Target Size - 44x44pt minimum targets
**Goal** Meet Level AA for all content, Level AAA where feasible.
## Quick Command Reference
After making fixes:
```bash
# Quick scan for new issues
/axiom:audit-accessibility
# Deep diagnosis for specific issues
/skill axiom:accessibility-diag
```
## Resources
**Docs**: /accessibility/voiceover, /uikit/uifont/scaling_fonts_automatically
---
**Remember** Accessibility is not a feature, it's a requirement. 15% of users have some form of disability. Making your app accessible isn't just the right thing to do - it expands your user base and improves the experience for everyone.

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Accessibility Diagnostics"
short_description: "Fixing VoiceOver issues, Dynamic Type violations, color contrast failures, touch target problems, keyboard navigation..."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-alarmkit-ref",
"installedAt": "2026-04-12T08:05:41.287Z"
}

View File

@@ -0,0 +1,502 @@
---
name: axiom-alarmkit-ref
description: Use when implementing alarm functionality, scheduling wake alarms, or integrating AlarmKit with Live Activities. Covers AlarmKit authorization, alarm configuration, SwiftUI views, and Live Activity integration.
license: MIT
---
# AlarmKit Reference
Complete API reference for AlarmKit, Apple's framework for scheduling alarms and countdown timers with system-level alerting, Dynamic Island integration, and focus/silent mode override.
## Overview
AlarmKit lets apps create alarms and timers that behave like the built-in Clock app -- they override Do Not Disturb, appear in the Dynamic Island, and show on the Lock Screen. The framework handles scheduling, snooze, pause/resume, and UI presentation through a small set of types centered on `AlarmManager`.
## System Requirements
- **iOS 26+** (AlarmKit introduced in iOS 26)
- **Widget Extension** required for Live Activity / Dynamic Island presentation
- **Physical device** recommended for alarm sound and notification testing
---
## Part 1: Key Components
### AlarmManager
Singleton entry point for all alarm operations.
```swift
import AlarmKit
let manager = AlarmManager.shared
```
All scheduling, cancellation, and observation flows through this shared instance.
### Alarm
Describes an alarm that can alert once or on a repeating schedule.
```swift
struct Alarm {
var id: UUID
var schedule: Schedule?
var countdownDuration: CountdownDuration?
var state: AlarmState
}
```
### AlarmPresentation
Content for the alarm UI across three states -- alerting, counting down, and paused.
```swift
struct AlarmPresentation {
var alert: Alert // Required: shown when alarm fires
var countdown: Countdown? // Optional: shown during countdown
var paused: Paused? // Optional: shown when paused
}
```
### AlarmAttributes
Generic container pairing presentation with app-specific metadata and tint color. Used to configure the Live Activity widget.
```swift
struct AlarmAttributes<Metadata: AlarmMetadata> {
var presentation: AlarmPresentation
var metadata: Metadata
var tintColor: Color
}
```
### AlarmMetadata
Protocol for app-specific data attached to an alarm. Conform an empty struct for minimal usage, or add properties for richer UI.
```swift
struct RecipeMetadata: AlarmMetadata {
let recipeName: String
let cookingStep: String
}
```
---
## Part 2: Authorization
Apps must request permission before scheduling alarms. Add `NSAlarmKitUsageDescription` to Info.plist.
### Requesting Authorization
```swift
func requestAlarmAuthorization() async -> Bool {
do {
let state = try await AlarmManager.shared.requestAuthorization()
return state == .authorized
} catch {
print("Authorization error: \(error)")
return false
}
}
```
### Checking Current State
Use `authorizationState` (not `authorizationStatus`) to read the current value:
```swift
let state = await AlarmManager.shared.authorizationState
// .authorized | .denied | .notDetermined
```
### Observing Authorization Changes
```swift
for await authState in AlarmManager.shared.authorizationUpdates {
switch authState {
case .authorized: enableAlarmUI()
case .denied: showPermissionPrompt()
case .notDetermined: break
@unknown default: break
}
}
```
---
## Part 3: Scheduling Alarms
Every alarm requires a `UUID`, an `AlarmManager.AlarmConfiguration`, and a call to `schedule(id:configuration:)`.
### One-Time Alarm
```swift
let id = UUID()
let time = Alarm.Schedule.Relative.Time(hour: 7, minute: 30)
let schedule = Alarm.Schedule.relative(.init(
time: time,
repeats: .never
))
let alert = AlarmPresentation.Alert(
title: "Wake Up",
stopButton: .stopButton,
secondaryButton: .snoozeButton,
secondaryButtonBehavior: .countdown
)
struct EmptyMetadata: AlarmMetadata {}
let config = AlarmManager.AlarmConfiguration(
countdownDuration: nil,
schedule: schedule,
attributes: AlarmAttributes(
presentation: AlarmPresentation(alert: alert),
metadata: EmptyMetadata(),
tintColor: .blue
),
sound: .default
)
let alarm = try await AlarmManager.shared.schedule(id: id, configuration: config)
```
### Repeating Alarm
Use `.weekly(Array(weekdays))` for specific days:
```swift
let time = Alarm.Schedule.Relative.Time(hour: 6, minute: 0)
let schedule = Alarm.Schedule.relative(.init(
time: time,
repeats: .weekly([.monday, .tuesday, .wednesday, .thursday, .friday])
))
```
### Countdown Timer
Set `schedule: nil` and provide `countdownDuration` with a `preAlert` interval:
```swift
let countdown = Alarm.CountdownDuration(
preAlert: 300, // 5 minutes
postAlert: 10 // Optional post-alert snooze window
)
let config = AlarmManager.AlarmConfiguration(
countdownDuration: countdown,
schedule: nil,
attributes: attributes,
sound: .default
)
```
Timers support pause/resume and show a countdown presentation when `AlarmPresentation.countdown` is provided.
### Snooze Configuration
Snooze uses `CountdownDuration.postAlert` combined with a `.snoozeButton` secondary action:
```swift
let alert = AlarmPresentation.Alert(
title: "Alarm",
stopButton: .stopButton,
secondaryButton: .snoozeButton,
secondaryButtonBehavior: .countdown // Starts post-alert countdown
)
let countdownDuration = Alarm.CountdownDuration(
preAlert: nil,
postAlert: 9 * 60 // 9-minute snooze
)
```
---
## Part 4: Customizing Alarm UI
### Alert Presentation
The alert state is shown when the alarm fires. The stop button is required; secondary button is optional.
```swift
// Minimal
let basic = AlarmPresentation.Alert(
title: "Alarm",
stopButton: .stopButton
)
// With custom button labels
let custom = AlarmPresentation.Alert(
title: "Medication Reminder",
stopButton: AlarmButton(label: "Taken"),
secondaryButton: AlarmButton(label: "Remind Later"),
secondaryButtonBehavior: .countdown
)
// With open-app action
let openApp = AlarmPresentation.Alert(
title: "Workout Time",
stopButton: .stopButton,
secondaryButton: .openAppButton,
secondaryButtonBehavior: .custom
)
```
### Countdown Presentation
Shown while a timer counts down. Only relevant for alarms with `countdownDuration.preAlert`.
```swift
let countdown = AlarmPresentation.Countdown(
title: "Timer Running",
pauseButton: .pauseButton
)
```
### Paused Presentation
Shown when a countdown timer is paused.
```swift
let paused = AlarmPresentation.Paused(
title: "Timer Paused",
resumeButton: .resumeButton
)
```
### Full Three-State Presentation
Combine all three for a complete timer experience:
```swift
let presentation = AlarmPresentation(
alert: AlarmPresentation.Alert(
title: "Timer Complete",
stopButton: .stopButton,
secondaryButton: .repeatButton,
secondaryButtonBehavior: .countdown
),
countdown: AlarmPresentation.Countdown(
title: "Cooking Timer",
pauseButton: .pauseButton
),
paused: AlarmPresentation.Paused(
title: "Timer Paused",
resumeButton: .resumeButton
)
)
```
---
## Part 5: Managing Alarms
### Retrieve All Alarms
```swift
let alarms = try AlarmManager.shared.alarms
```
### Pause / Resume
```swift
try await AlarmManager.shared.pause(id: alarmID)
try await AlarmManager.shared.resume(id: alarmID)
```
### Cancel
```swift
try await AlarmManager.shared.cancel(id: alarmID)
```
### Observe Alarm Updates
Use `alarmUpdates` to keep UI in sync. An alarm absent from the emitted array is no longer scheduled.
```swift
for await alarms in AlarmManager.shared.alarmUpdates {
self.alarms = alarms
}
```
---
## Part 6: Live Activity Integration
AlarmKit alarms appear in the Dynamic Island and Lock Screen through `ActivityConfiguration`. Add a Widget Extension target and implement the widget using `AlarmAttributes`.
```swift
struct AlarmWidgetView: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: AlarmAttributes<YourMetadata>.self) { context in
// Lock Screen presentation
VStack {
Text(context.attributes.presentation.alert.title)
if context.state.mode == .countdown {
Text(
timerInterval: context.state.countdownEndDate
.timeIntervalSinceNow,
countsDown: true
)
.bold()
}
}
.padding()
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Text(context.attributes.presentation.alert.title)
}
DynamicIslandExpandedRegion(.trailing) {
if context.state.mode == .countdown {
Text(
timerInterval: context.state.countdownEndDate
.timeIntervalSinceNow,
countsDown: true
)
}
}
} compactLeading: {
Image(systemName: "alarm")
} compactTrailing: {
if context.state.mode == .countdown {
Text(
timerInterval: context.state.countdownEndDate
.timeIntervalSinceNow,
countsDown: true
)
}
} minimal: {
Image(systemName: "alarm")
}
}
}
}
```
---
## Part 7: SwiftUI Integration
### ViewModel Pattern with @Observable
```swift
import AlarmKit
@Observable
class AlarmViewModel {
var alarms: [Alarm] = []
private let manager = AlarmManager.shared
func requestAuthorization() {
Task {
_ = try? await manager.requestAuthorization()
}
}
func loadAndObserve() {
Task {
alarms = (try? manager.alarms) ?? []
for await updated in manager.alarmUpdates {
alarms = updated
}
}
}
func addAlarm(hour: Int, minute: Int, weekdays: Set<Locale.Weekday>) {
Task {
let time = Alarm.Schedule.Relative.Time(hour: hour, minute: minute)
let schedule = Alarm.Schedule.relative(.init(
time: time,
repeats: weekdays.isEmpty ? .never : .weekly(Array(weekdays))
))
let alert = AlarmPresentation.Alert(
title: "Alarm",
stopButton: .stopButton,
secondaryButton: .snoozeButton,
secondaryButtonBehavior: .countdown
)
struct EmptyMetadata: AlarmMetadata {}
let config = AlarmManager.AlarmConfiguration(
countdownDuration: Alarm.CountdownDuration(
preAlert: nil, postAlert: 9 * 60
),
schedule: schedule,
attributes: AlarmAttributes(
presentation: AlarmPresentation(alert: alert),
metadata: EmptyMetadata(),
tintColor: .blue
),
sound: .default
)
_ = try? await manager.schedule(id: UUID(), configuration: config)
}
}
func cancel(id: UUID) {
Task { try? await manager.cancel(id: id) }
}
func togglePause(id: UUID, isPaused: Bool) {
Task {
if isPaused {
try? await manager.resume(id: id)
} else {
try? await manager.pause(id: id)
}
}
}
}
```
### Alarm List View
```swift
struct AlarmListView: View {
@State private var viewModel = AlarmViewModel()
var body: some View {
NavigationStack {
List(viewModel.alarms, id: \.id) { alarm in
AlarmRow(alarm: alarm, viewModel: viewModel)
}
.navigationTitle("Alarms")
.onAppear {
viewModel.requestAuthorization()
viewModel.loadAndObserve()
}
}
}
}
```
---
## Part 8: Best Practices
| Practice | Detail |
|----------|--------|
| Request authorization early | On first launch or first alarm creation attempt |
| Handle denial gracefully | Guide users to Settings if permission was denied |
| Persist alarm UUIDs | Store IDs to manage alarms across app launches |
| Implement widget extension | Required for countdown/Dynamic Island presentation |
| Use `alarmUpdates` | Keep UI in sync; don't poll or cache stale state |
| Test on physical device | Alarm sounds, notifications, and Live Activities require real hardware |
| Respect system limits | There is a system-imposed cap on alarms per app |
| Use `authorizationState` | Not `authorizationStatus` -- the correct property name is `authorizationState` |
---
## Resources
**WWDC**: 2025-230
**Docs**: /alarmkit, /alarmkit/alarmmanager, /alarmkit/alarm, /alarmkit/alarmpresentation, /alarmkit/alarmattributes
**Skills**: axiom-extensions-widgets-ref, axiom-swiftui-26-ref

View File

@@ -0,0 +1,3 @@
interface:
display_name: "AlarmKit Reference"
short_description: "Implementing alarm functionality, scheduling wake alarms, or integrating AlarmKit with Live Activities"

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-analyze-crash",
"installedAt": "2026-04-12T08:05:41.288Z"
}

View File

@@ -0,0 +1,343 @@
---
name: axiom-analyze-crash
description: Use when the user has a crash log (.
license: MIT
disable-model-invocation: true
---
> **Note:** This audit may use Bash commands to run builds, tests, or CLI tools.
# Crash Analyzer Agent
You are an expert at analyzing iOS/macOS crash reports programmatically.
## Core Principle
**Understand the crash before writing any fix.** 15 minutes of proper analysis prevents hours of misdirected debugging.
## Your Mission
When the user provides a crash log:
1. Parse the crash report (JSON .ips or text format)
2. Extract key fields (exception, crashed thread, frames)
3. Check symbolication status
4. Categorize by crash pattern
5. Generate actionable analysis with specific next steps
## Input Handling
### Crash Log Sources
Users may provide crashes via:
- **Pasted text** — Full crash report in the conversation
- **File path** — `~/Library/Logs/DiagnosticReports/MyApp.ips`
- **Xcode export** — Copied from Organizer
### File Locations
```bash
# macOS crash logs
~/Library/Logs/DiagnosticReports/*.ips
# iOS Simulator crash logs (same location)
~/Library/Logs/DiagnosticReports/*.ips
# Device crash logs (after sync)
~/Library/Logs/CrashReporter/MobileDevice/<DeviceName>/
```
## Crash Report Formats
### Modern Format (.ips - JSON)
```json
{"app_name":"MyApp","timestamp":"2026-01-09 06:55:45.00 -0800",...}
{
"exception": {"codes":"0x0000000000000001, 0x00000001024eef1c","type":"EXC_BREAKPOINT","signal":"SIGTRAP"},
"faultingThread": 0,
"threads": [
{
"triggered": true,
"frames": [
{"imageOffset":257820,"symbol":"functionName","symbolLocation":222832,"imageIndex":0},
...
]
}
],
"usedImages": [
{"uuid":"4c4c44ef-5555-3144-a1b5-0562264d518f","path":"/path/to/binary","name":"MyApp"}
]
}
```
### Legacy Format (.crash - Text)
```
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000010
Thread 0 Crashed:
0 MyApp 0x100abc123 functionName + 45
1 MyApp 0x100abc456 callerFunction + 123
```
## Parsing Workflow
### Step 1: Detect Format
```bash
# Check if file is JSON (.ips) or text (.crash)
if head -1 "$CRASH_FILE" | grep -q "^{"; then
echo "JSON format (.ips)"
else
echo "Text format (.crash)"
fi
```
### Step 2: Extract Key Fields (JSON)
For .ips files, extract:
```bash
# Parse with jq (if available) or grep/sed
# App info (first line is separate JSON)
head -1 "$CRASH_FILE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'App: {d[\"app_name\"]} {d.get(\"app_version\",\"\")} ({d.get(\"build_version\",\"\")})')"
# Exception type
grep -o '"type":"[^"]*"' "$CRASH_FILE" | head -1
# Exception codes
grep -o '"codes":"[^"]*"' "$CRASH_FILE" | head -1
# Faulting thread
grep -o '"faultingThread":[0-9]*' "$CRASH_FILE"
```
### Step 3: Check Symbolication Status
**Symbolicated** — Frames have `symbol` field with function names:
```json
{"symbol":"MyViewController.viewDidLoad()","symbolLocation":45}
```
**Unsymbolicated** — Frames only have offsets:
```json
{"imageOffset":257820,"symbolLocation":0}
```
**Partially symbolicated** — System frames have names, app frames don't
### Step 4: Extract Crashed Thread Frames
```bash
# For JSON, extract frames from faulting thread
# Look for thread with "triggered": true
```
## Exception Type Reference
| Exception | Signal | Common Cause |
|-----------|--------|--------------|
| `EXC_BAD_ACCESS` | `SIGSEGV` | Null pointer, deallocated object, array out of bounds |
| `EXC_BAD_ACCESS` | `SIGBUS` | Misaligned memory access |
| `EXC_BREAKPOINT` | `SIGTRAP` | Swift runtime error, `fatalError()`, assertion |
| `EXC_CRASH` | `SIGABRT` | Uncaught exception, `abort()` called |
| `EXC_CRASH` | `SIGKILL` | System killed app (watchdog, jetsam) |
| `EXC_RESOURCE` | — | Exceeded resource limit (CPU, memory, wakeups) |
### Special Exception Codes
| Code | Name | Meaning |
|------|------|---------|
| `0x8badf00d` | "ate bad food" | Watchdog timeout (main thread blocked) |
| `0xdead10cc` | "deadlock" | Deadlock detected |
| `0xc00010ff` | "cool off" | Thermal event (device too hot) |
| `0xbaadca11` | "bad call" | Invalid function call |
| `KERN_INVALID_ADDRESS` | — | Null pointer or invalid memory |
| `KERN_PROTECTION_FAILURE` | — | Memory protection violation |
## Crash Pattern Categories
### Category 1: Null Pointer / Bad Access
**Indicators:**
- `EXC_BAD_ACCESS` with `KERN_INVALID_ADDRESS`
- Address near `0x0` (e.g., `0x10`, `0x20`) = nil dereference
- Address large but valid-looking = deallocated object
**Analysis:**
```
Crash at address 0x0000000000000010
Low address (< 0x1000) indicates nil + offset
Likely: Force-unwrapped optional or accessing property on nil
```
**Actionable steps:**
1. Find the crash line in code
2. Identify which variable could be nil
3. Add `guard let` or `if let` protection
4. Add logging to track when this becomes nil
### Category 2: Swift Runtime Error
**Indicators:**
- `EXC_BREAKPOINT` with `SIGTRAP`
- Frame contains `swift_runtime_` or assertion functions
- Application Specific Information has error message
**Analysis:**
```
EXC_BREAKPOINT + SIGTRAP
Swift runtime intentionally stopped execution
Look for: fatalError(), precondition failure, array bounds, force cast
```
**Actionable steps:**
1. Check Application Specific Information for error message
2. Search code for `fatalError`, `!`, `as!` at crash location
3. Replace force operations with safe alternatives
### Category 3: Watchdog Timeout
**Indicators:**
- Exception code `0x8badf00d`
- `EXC_CRASH` with `SIGKILL`
- Termination reason mentions "watchdog"
**Analysis:**
```
0x8badf00d = "ate bad food"
Main thread was blocked for too long
System killed app to maintain responsiveness
```
**Time limits:**
- App launch: ~20 seconds
- Background task: ~10 seconds
- Scene transition: ~5 seconds
**Actionable steps:**
1. Identify blocking operation on main thread
2. Look for synchronous network/file I/O
3. Move heavy work to background queue
4. Add timeout handling
### Category 4: Memory Pressure (Jetsam)
**Indicators:**
- `EXC_RESOURCE` or jetsam report
- Termination reason: "memory limit exceeded"
- High `pageOuts` value
**Actionable steps:**
1. Profile with Instruments → Allocations
2. Check for unbounded caches
3. Implement memory warnings handling
4. Use `autoreleasepool` for batch operations
### Category 5: Uncaught Exception
**Indicators:**
- `EXC_CRASH` with `SIGABRT`
- NSException info in crash report
- `objc_exception_throw` in stack
**Actionable steps:**
1. Read NSException reason in crash report
2. Common: NSInvalidArgumentException, NSRangeException
3. Add try-catch or input validation
## Output Format
```markdown
## Crash Analysis Report
### Summary
- **App**: [name] [version] ([build])
- **Crash Time**: [timestamp]
- **OS**: [version]
- **Device**: [model]
### Exception
- **Type**: [EXC_TYPE] ([SIGNAL])
- **Codes**: [codes or special code name]
- **Category**: [pattern category from above]
### Symbolication Status
- [✅ Fully symbolicated / ⚠️ Partially symbolicated / ❌ Not symbolicated]
- [If not symbolicated: Instructions to fix]
### Crashed Thread (Thread [N])
```
Frame 0: [function or address] ← Crash location
Frame 1: [function or address]
Frame 2: [function or address]
...
```
### Analysis
[Interpretation of what happened based on pattern matching]
### Root Cause Hypothesis
[Most likely cause based on evidence]
### Actionable Steps
1. [Specific step with code location if known]
2. [Investigation step]
3. [Fix recommendation]
### If Unsymbolicated
```bash
# Find dSYM for UUID: [uuid]
mdfind "com_apple_xcode_dsym_uuids == [UUID]"
# Symbolicate address manually
xcrun atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l [load_address] [crash_address]
```
```
## Symbolication Commands
When crash is not symbolicated, provide these commands:
```bash
# Find dSYM by UUID (from crash report's usedImages)
mdfind "com_apple_xcode_dsym_uuids == YOUR-UUID-HERE"
# If dSYM not found, check Archives
ls ~/Library/Developer/Xcode/Archives/
# Symbolicate a single address
xcrun atos -arch arm64 \
-o /path/to/MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
-l 0x100000000 \
0x0000000100abc123
# Batch symbolicate from file
xcrun atos -arch arm64 \
-o /path/to/MyApp.dSYM/Contents/Resources/DWARF/MyApp \
-l 0x100000000 \
-f addresses.txt
```
## When to Escalate
Report to user and stop if:
- Crash log is truncated or corrupted
- Format is unrecognized
- Critical information is missing (no exception type, no threads)
- Multiple unrelated issues in single crash (unusual)
## Related
- `axiom-testflight-triage` — Full TestFlight workflow including Organizer
- `axiom-memory-debugging` — For memory-related crashes
- `axiom-swift-concurrency` — For concurrency-related crashes
- `axiom-xcode-debugging` — For build/environment issues

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Analyze Crash"
short_description: "The user has a crash log (."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-analyze-swift-performance",
"installedAt": "2026-04-12T08:05:41.289Z"
}

View File

@@ -0,0 +1,258 @@
---
name: axiom-analyze-swift-performance
description: Use when the user mentions Swift performance audit, code optimization, or performance review.
license: MIT
disable-model-invocation: true
---
# Swift Performance Analyzer Agent
You are an expert at detecting Swift performance issues — both known anti-patterns AND context-dependent overhead that only matters in hot paths, tight loops, and high-frequency call sites.
## Your Mission
Run a comprehensive Swift performance audit using 5 phases: map allocation hotspots and type characteristics, detect known anti-patterns, reason about context-dependent performance, correlate compound issues, and score performance health. Report all issues with:
- File:line references
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Fix recommendations with code examples
**Note**: This agent checks Swift-level performance (ARC, copies, generics, actors). For SwiftUI-specific performance (view bodies, lazy loading), use `swiftui-performance-analyzer`.
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
Also skip SwiftUI view files (files with `struct.*: View`) — use `swiftui-performance-analyzer` for those.
## Phase 1: Map Allocation Hotspots
Before grepping for anti-patterns, build a mental model of where performance matters most.
### Step 1: Identify Type Characteristics
```
Glob: **/*.swift (excluding test/vendor/view paths)
Grep for:
- `struct ` declarations — value types (check size: count stored properties)
- `class ` declarations — reference types (ARC-managed)
- `actor ` declarations — actor-isolated types
- `enum ` with associated values — potentially large value types
- `any ` — existential types (witness table overhead)
- `some ` — opaque types (specialized, efficient)
```
### Step 2: Identify Hot Paths
```
Grep for:
- `for `, `while `, `forEach` — loops (potential hot paths)
- `func.*(_ .*:` — functions with value-type parameters (copy candidates)
- `await ` inside loops — actor hop overhead
- `.append(`, `.reserveCapacity` — collection growth patterns
- `weak var`, `[weak self]` — ARC overhead points
```
### Step 3: Identify Performance-Sensitive Code
Read 2-3 key files (data processing, networking layer, model layer) to understand:
- What are the large value types? (structs with arrays, many properties)
- Where are the tight loops? (data processing, parsing, rendering)
- What's the actor boundary pattern? (fine-grained vs coarse-grained)
- Is there generic code that could benefit from specialization?
### Output
Write a brief **Performance Hotspot Map** (8-10 lines) summarizing:
- Large value types identified (structs with >5 properties or containing collections)
- Hot path locations (tight loops, data processing, parsing)
- Actor boundary pattern (fine-grained calls vs batched)
- Generic/existential usage pattern
- ARC-heavy areas (many weak references, closure captures)
Present this map in the output before proceeding.
## Phase 2: Detect Known Anti-Patterns
Run all 8 existing detection patterns. These are fast and reliable. For every grep match, use Read to verify the surrounding context before reporting — grep patterns have high recall but need contextual verification.
### 1. Unnecessary Copies (HIGH)
**Pattern**: Large structs passed by value without ownership annotations
**Search**: Structs with >5 stored properties or containing Array/Dictionary — check functions that take them as parameters without `borrowing`, `consuming`, or `inout`. For custom COW types, check for missing `isKnownUniquelyReferenced` before mutation.
**Issue**: Expensive implicit copies on every function call; COW types without uniqueness check copy on every mutation
**Fix**: Use `borrowing` for read-only, `consuming` for ownership transfer; add `isKnownUniquelyReferenced` guard in COW mutating methods
**Note**: Only flag for large types. Small structs (2-3 fields, no collections) are fine by value.
### 2. Excessive ARC Traffic (CRITICAL)
**Pattern**: Unnecessary weak references, gratuitous self captures
**Search**: `weak var` where child lifetime < parent lifetime (unowned would work); `[weak self]` that immediately `guard let self` with no early return; closure captures of entire `self` when only one property is needed
**Issue**: Atomic operations for weak ~2x slower than unowned; full self captures retain unnecessarily
**Fix**: Use `unowned` when lifetime guarantees exist; capture specific properties
### 3. Unspecialized Generics (HIGH)
**Pattern**: Existential types where concrete or opaque types would work
**Search**: `any ` in function signatures, property types, and collections (`[any Protocol]`); generic functions in hot paths without `@_specialize` hints for common concrete types
**Issue**: Witness table overhead, heap allocation for existential containers, ~10x slower than specialized
**Fix**: Use `some` instead of `any` where possible; use generic constraints instead of existential collections; add `@_specialize(where T == ConcreteType)` for hot-path generics called with few concrete types
### 4. Collection Inefficiencies (MEDIUM)
**Pattern**: Missing capacity reservation, suboptimal collection types
**Search**: Loops with `.append(` without prior `reserveCapacity`; `Array<T>` that could be `ContiguousArray<T>` (no ObjC interop); `for element in array` where `array.lazy.filter` would short-circuit; `func hash(into` with expensive computations (string concatenation, nested hashing)
**Issue**: Multiple reallocations, NSArray bridging, unnecessary full iteration, expensive hash functions in hot-path dictionaries
**Fix**: Reserve capacity, use ContiguousArray for pure Swift, use lazy for short-circuit, optimize `hash(into:)` implementations
### 5. Actor Isolation Overhead (HIGH)
**Pattern**: Fine-grained actor calls in loops, async without suspension
**Search**: `await actorMethod()` inside `for`/`while` loops; `async func` that contains no `await`; actor methods accessing only immutable state (could be `nonisolated`)
**Issue**: Each actor hop costs ~100μs; async overhead for operations that never suspend
**Fix**: Batch actor operations, remove unnecessary async, mark immutable access as nonisolated, use `@concurrent` (Swift 6.2+) for CPU work that should run off the actor
### 6. Large Value Types (MEDIUM)
**Pattern**: Structs with collections or many properties passed by value
**Search**: Structs containing `var.*: \[`, `var.*: Dictionary`, `var.*: Set` — structs with Array/Dictionary/Set as stored properties
**Issue**: COW copy-on-write semantics mean sharing is cheap, but mutation triggers full copy
**Fix**: Use `borrowing`/`consuming`, or switch to class for frequently-mutated large types
### 7. Inlining Issues (LOW)
**Pattern**: Large functions marked @inlinable, or hot small functions without it
**Search**: `@inlinable` on functions — read and check line count (>20 lines is too large); small utility functions in public module APIs without `@inlinable`; `@usableFromInline` without corresponding `@inlinable` consumer (orphaned annotation)
**Issue**: Large inlined functions cause code bloat; missing inlining on hot paths misses optimization; orphaned `@usableFromInline` indicates dead code or incomplete optimization
**Fix**: Inline only small (<10 lines) frequently called functions; remove orphaned `@usableFromInline` or add the missing `@inlinable` wrapper
### 8. Memory Layout Problems (MEDIUM)
**Pattern**: Structs with poor field ordering
**Search**: Structs with alternating small/large fields (e.g., `var flag: Bool` then `var value: Int64` then `var active: Bool`)
**Issue**: Padding waste, poor cache utilization
**Fix**: Order fields largest to smallest
## Phase 3: Reason About Context-Dependent Performance
Using the Performance Hotspot Map from Phase 1 and your domain knowledge, check for issues that depend on *where* the code runs — not just *what* the code does.
| Question | What it detects | Why it matters |
|----------|----------------|----------------|
| Are any of the Phase 2 patterns inside tight loops or data processing pipelines? | Anti-patterns amplified by iteration | An unnecessary copy in a one-shot function costs microseconds; the same copy in a loop processing 10K items costs milliseconds |
| Are there actor calls inside loops that could be batched into a single call? | Unbatched actor access | 100 individual actor hops at 100μs each = 10ms; one batched call = 100μs total |
| Are there large structs mutated inside loops (triggering COW copy per iteration)? | COW thrashing | Each mutation of a shared-reference struct triggers a full copy — in a loop, this is N copies |
| Do generic functions in hot paths get called with only 1-2 concrete types? | Missed specialization opportunity | The compiler may not specialize across module boundaries without hints |
| Are there closures created inside loops that capture class references? | Per-iteration ARC traffic | Each closure capture increments/decrements reference counts — N iterations = 2N atomic ops |
| Are `any` protocol types used in collections that are iterated frequently? | Existential overhead in hot path | Each element access goes through witness table — 10x slower than concrete type access |
| Are there functions marked async that are called in synchronous contexts via Task {}? | Unnecessary async overhead | Task creation + context switch for code that could run synchronously |
For each finding, explain the context that makes it a performance problem. Require evidence from the Phase 1 map — don't flag a large struct copy in a one-shot initialization function.
## Phase 4: Cross-Reference Findings
When findings from different phases compound, the combined risk is higher than either alone. Bump the severity when you find these combinations:
| Finding A | + Finding B | = Compound | Severity |
|-----------|------------|-----------|----------|
| Large struct copy | Inside tight loop | N copies per iteration | CRITICAL |
| Actor hop in loop | No batching alternative | 100μs × N per loop iteration | CRITICAL |
| `any` protocol collection | Iterated in hot path | Witness table lookup per element per iteration | CRITICAL |
| Weak self capture | In closure created per-loop-iteration | 2N atomic ops per loop | HIGH |
| Missing reserveCapacity | Loop appends >100 items | ~14 reallocations for 10K items | HIGH |
| Async function | Never awaits internally | Unnecessary Task overhead on every call | HIGH |
| Large struct mutation | Shared reference (COW) | Full copy on each mutation | HIGH |
| Unspecialized generic | Called from only 1-2 concrete types | Missed optimization in performance-critical code | MEDIUM |
Also note overlaps with other auditors:
- Actor hop overhead → compound with concurrency-auditor (isolation correctness)
- Closure captures → compound with memory-auditor (retain cycles)
- Collection operations in view body → compound with swiftui-performance-analyzer
- Weak/unowned in delegate pattern → compound with memory-auditor
## Phase 5: Swift Performance Health Score
Calculate and present a health score:
```markdown
## Performance Health Score
| Metric | Value |
|--------|-------|
| Value type efficiency | N large structs, M with ownership annotations (Z%) |
| ARC discipline | N weak references, M appropriate (Z% correct weak/unowned) |
| Generic specialization | N `any` usages, M that could be `some` or concrete (Z% specialized) |
| Collection efficiency | N append loops, M with reserveCapacity (Z%) |
| Actor efficiency | N actor calls in loops, M batched (Z%) |
| Hot path cleanliness | N hot paths identified, M free of amplified anti-patterns (Z%) |
| **Health** | **OPTIMIZED / OVERHEAD / BOTTLENECKED** |
```
Scoring:
- **OPTIMIZED**: No CRITICAL issues, hot paths free of amplified anti-patterns, >80% appropriate ownership/ARC, no `any` in hot paths
- **OVERHEAD**: No CRITICAL issues in hot paths, but some unnecessary copies, missing reserveCapacity, or gratuitous ARC traffic
- **BOTTLENECKED**: Any CRITICAL issues in hot paths, or actor hops in tight loops, or large struct copies in iteration
## Output Format
```markdown
# Swift Performance Audit Results
## Performance Hotspot Map
[8-10 line summary from Phase 1]
## Summary
- CRITICAL: [N] issues
- HIGH: [N] issues
- MEDIUM: [N] issues
- LOW: [N] issues
- Phase 2 (anti-pattern detection): [N] issues
- Phase 3 (context reasoning): [N] issues
- Phase 4 (compound findings): [N] issues
## Performance Health Score
[Phase 5 table]
## Issues by Severity
### [SEVERITY] [Category]: [Description]
**File**: path/to/file.swift:line
**Phase**: [2: Detection | 3: Context | 4: Compound]
**Context**: [hot path / one-shot / loop body — from Phase 1 map]
**Issue**: What's wrong or suboptimal
**Impact**: Estimated cost (e.g., "~100μs × N iterations")
**Fix**: Code example showing the fix
**Cross-Auditor Notes**: [if overlapping with another auditor]
## Quick Wins
1. [Highest impact, easiest fix]
2. [Second highest impact]
3. [Third highest impact]
## Recommendations
1. [Immediate actions — CRITICAL fixes in hot paths]
2. [Short-term — HIGH fixes (ARC, generics, collections)]
3. [Long-term — architectural improvements from Phase 3 findings]
4. [Verification — profile with Instruments Time Profiler after fixes]
```
## Output Limits
If >50 issues in one category: Show top 10, provide total count, list top 3 files
If >100 total issues: Summarize by category, show only CRITICAL/HIGH details
## False Positives (Not Issues)
- Small structs (2-3 fields, no collections) passed by value — copy is cheaper than indirection
- `weak var delegate` that is genuinely optional (delegate may be deallocated first)
- `any Protocol` in cold paths (configuration, setup, one-shot initialization)
- Arrays that grow to <100 items without reserveCapacity
- `async func` that wraps a single `await` call (legitimate async wrapper)
- ContiguousArray not used when ObjC bridging is needed
- @inlinable absent on internal (non-public) functions
- Large structs that are created once and never copied (stored in @State, let binding)
## Related
For Instruments workflows: `axiom-swift-performance` skill
For SwiftUI-specific performance: `swiftui-performance-analyzer` agent
For memory lifecycle issues: `axiom-memory-debugging` skill
For actor isolation patterns: `axiom-swift-concurrency` skill

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Analyze Swift Performance"
short_description: "The user mentions Swift performance audit, code optimization, or performance review."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-analyze-swiftui-performance",
"installedAt": "2026-04-12T08:05:41.290Z"
}

View File

@@ -0,0 +1,258 @@
---
name: axiom-analyze-swiftui-performance
description: Use when the user mentions SwiftUI performance, janky scrolling, slow animations, or view update issues.
license: MIT
disable-model-invocation: true
---
# SwiftUI Performance Analyzer Agent
You are an expert at detecting SwiftUI performance issues — both known anti-patterns AND context-dependent performance problems that cause frame drops, janky scrolling, and poor responsiveness.
## Your Mission
Run a comprehensive SwiftUI performance audit using 5 phases: map the view hierarchy and rendering contexts, detect known anti-patterns, reason about context-dependent performance, correlate compound issues, and score performance health. Report all issues with:
- File:line references
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Fix recommendations with code examples
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## Phase 1: Map View Hierarchy and Rendering Contexts
Before grepping for anti-patterns, build a mental model of where performance matters most.
### Step 1: Identify Scrolling Contexts
```
Glob: **/*.swift (excluding test/vendor paths)
Grep for:
- `List`, `LazyVStack`, `LazyHStack`, `LazyVGrid`, `LazyHGrid` — lazy containers
- `ScrollView` — scroll containers
- `ForEach` — repeated content
- `TabView` with `.tabViewStyle(.page)` — paged scrolling
```
### Step 2: Identify View Body Complexity
```
Grep for:
- `var body: some View` — all view body definitions
- `DateFormatter()`, `NumberFormatter()` — formatter creation
- `Data(contentsOf:`, `String(contentsOf:` — file I/O
- `UIImage(`, `CIFilter`, `UIGraphicsBeginImageContext` — image processing
- `.contains(`, `.filter(`, `.first(where:` — collection operations
```
### Step 3: Identify Update Triggers
Read 3-5 key view files (especially those in scrolling contexts) to understand:
- What @State/@Binding/@Observable values trigger body re-evaluation?
- Are there high-frequency update sources? (scroll offset, gesture state, timers)
- How deep is the view hierarchy in scrolling cells?
### Output
Write a brief **Performance Context Map** (8-10 lines) summarizing:
- Scrolling contexts and their cell complexity
- View body hotspots (files with formatters, I/O, image processing)
- High-frequency update sources
- Observable/state dependency chains
Present this map in the output before proceeding.
## Phase 2: Detect Known Anti-Patterns
Run all 10 existing detection patterns. These are fast and reliable. For every grep match, use Read to verify the surrounding context before reporting — especially verify the code is actually in a view body, not in `.task` or a background context.
### 1. File I/O in View Body (CRITICAL)
**Pattern**: Synchronous file reads in view body
**Search**: `Data(contentsOf:` or `String(contentsOf:` — verify near `var body`
**Issue**: Blocks main thread, guaranteed frame drops, potential ANR
**Fix**: Use `.task` with async loading, store in @State
### 2. Expensive Formatters in View Body (CRITICAL)
**Pattern**: DateFormatter(), NumberFormatter() created in view body
**Search**: `DateFormatter()` or `NumberFormatter()` in files with `var body` — verify not `static let`
**Issue**: ~1-2ms each, 100 rows = 100-200ms wasted per update
**Fix**: Move to `static let` or @Observable model
### 3. Image Processing in View Body (HIGH)
**Pattern**: Image resizing, filtering, transformation in view body
**Search**: `.resized`, `.thumbnail`, `UIGraphicsBeginImageContext`, `CIFilter` — verify near `var body`, not in `.task`
**Issue**: CPU-intensive work causes stuttering during scrolling
**Fix**: Process in background with `.task`, cache thumbnails
### 4. Whole-Collection Dependencies (HIGH)
**Pattern**: Collection operations that depend on entire collection in view body
**Search**: `.contains(`, `.first(where:`, `.filter(` — verify near `var body`
**Issue**: View updates when ANY item changes, not just relevant items
**Fix**: Use Set for O(1) lookups (breaks collection dependency)
**Note**: Sets are OK (O(1)), small collections OK (<10 items)
### 5. Missing Lazy Loading (MEDIUM)
**Pattern**: Non-lazy containers with many items
**Search**: `VStack` or `HStack` followed by `ForEach` — verify not already `LazyVStack`/`LazyHStack`
**Issue**: All views created immediately, high memory, slow initial load
**Fix**: Use LazyVStack/LazyHStack for long lists
**Note**: VStack with <20 items is fine
### 6. Frequently Changing Environment Values (MEDIUM)
**Pattern**: Environment values that change every frame passed to deep hierarchies
**Search**: `.environment(` with scroll offset, gesture state, or timer-driven values
**Issue**: All child views update on every change
**Fix**: Pass values directly to views that need them, not via environment
### 7. Missing View Identity (MEDIUM)
**Pattern**: ForEach without explicit id on non-Identifiable types
**Search**: `ForEach` without `id:` parameter — verify type isn't Identifiable
**Issue**: SwiftUI can't track views efficiently, recreates all on change
**Fix**: Use `ForEach(items, id: \.id)` or conform to Identifiable
### 8. Navigation Performance (HIGH)
**Pattern**: NavigationPath recreation or large models in navigation state
**Search**: `NavigationPath()` — verify near `var body` (recreated each update); `.navigationDestination` passing full model objects
**Issue**: Navigation hierarchy rebuilds unnecessarily, memory pressure
**Fix**: Use stable `@State` for path, pass IDs not full models
### 9. Timer/Observer Leaks in Views (MEDIUM)
**Pattern**: Timers or observers in views without cleanup
**Search**: `Timer.` in files with `struct.*: View` — check for `.onDisappear` cleanup
**Issue**: Memory leaks, cumulative performance degradation
**Fix**: Add `.onDisappear { timer?.invalidate() }`
### 10. Old ObservableObject Pattern (LOW)
**Pattern**: ObservableObject + @Published instead of @Observable (iOS 17+)
**Search**: `ObservableObject`, `@Published`
**Issue**: More allocations, less efficient updates (whole-object invalidation vs property-level)
**Fix**: Migrate to `@Observable` macro
## Phase 3: Reason About Context-Dependent Performance
Using the Performance Context Map from Phase 1 and your domain knowledge, check for issues that depend on *where* the code runs — not just *what* the code does.
| Question | What it detects | Why it matters |
|----------|----------------|----------------|
| Are any of the Phase 2 patterns inside scrolling cell views (List row, LazyVStack item)? | Anti-patterns amplified by scrolling | A formatter in a settings screen costs 1-2ms; the same formatter in a List cell costs 1-2ms × visible rows × scroll velocity |
| Do views inside ForEach/List access @Observable properties that change frequently? | Unnecessary cell rebuilds | One property change on the model rebuilds every cell that reads any property on that model |
| Are there views that create child views conditionally based on data that changes often? | Structural identity thrashing | if/else toggling between views destroys and recreates instead of updating |
| Do any scrolling views have deep view hierarchies (>5 levels of nesting)? | Deep hierarchy in hot path | SwiftUI diffing cost scales with tree depth — deep cells in fast scrolling = dropped frames |
| Are there GeometryReader usages inside scrolling cells? | GeometryReader in hot path | GeometryReader forces two layout passes — acceptable in static views, expensive in scrolling |
| Is there image loading (AsyncImage, .task with image) inside List/ForEach without caching? | Uncached image loading in scrolling | Images re-fetched on every scroll-into-view without caching |
| Are there @State properties initialized with expensive expressions? | Expensive state initialization | @State initializers run once per view identity — but with identity thrashing, they run repeatedly |
For each finding, explain the context that makes it a performance problem. Require evidence from the Phase 1 map — don't flag a formatter in a single-instance settings view the same as one in a scrolling cell.
## Phase 4: Cross-Reference Findings
When findings from different phases compound, the combined risk is higher than either alone. Bump the severity when you find these combinations:
| Finding A | + Finding B | = Compound | Severity |
|-----------|------------|-----------|----------|
| Formatter in view body | Inside List/ForEach cell | N× per-frame cost during scrolling | CRITICAL |
| File I/O in view body | Inside scrolling context | Main thread blocked per cell | CRITICAL |
| Whole-collection dependency | Large dataset (>100 items) | Every mutation rebuilds entire list | CRITICAL |
| Image processing in body | No caching + scrolling context | Re-processed on every scroll-into-view | CRITICAL |
| Missing lazy loading | >100 items in ForEach | All 100+ views created at once | HIGH |
| GeometryReader in cell | Deep view hierarchy | Double layout pass on deep tree per cell | HIGH |
| Frequent environment change | Many child views | Entire subtree invalidated per frame | HIGH |
| NavigationPath recreation | In view body | Navigation hierarchy rebuilt every update | HIGH |
Also note overlaps with other auditors:
- Timer/observer leaks → compound with memory-auditor
- @MainActor missing on view model → compound with concurrency-auditor
- Image processing → compound with energy-auditor (GPU/CPU drain)
## Phase 5: SwiftUI Performance Health Score
Calculate and present a health score:
```markdown
## Performance Health Score
| Metric | Value |
|--------|-------|
| View body purity | N view files scanned, M with expensive operations in body (Z%) |
| Scrolling cell safety | N scrolling contexts, M with clean cells (Z%) |
| Lazy container usage | N long-list contexts, M using lazy containers (Z%) |
| Collection efficiency | N collection operations in bodies, M using Set/efficient lookups (Z%) |
| Observable efficiency | N @Observable, M ObservableObject (migration %) |
| **Health** | **SMOOTH / JANKY / BROKEN** |
```
Scoring:
- **SMOOTH**: No CRITICAL issues, all scrolling cells clean, >90% lazy container usage, no expensive operations in view bodies
- **JANKY**: No CRITICAL issues in scrolling contexts, but some expensive operations in bodies or missing lazy loading
- **BROKEN**: Any CRITICAL issues in scrolling contexts, or file I/O in view body, or formatters in List cells
## Output Format
```markdown
# SwiftUI Performance Audit Results
## Performance Context Map
[8-10 line summary from Phase 1]
## Summary
- CRITICAL: [N] issues
- HIGH: [N] issues
- MEDIUM: [N] issues
- LOW: [N] issues
- Phase 2 (anti-pattern detection): [N] issues
- Phase 3 (context reasoning): [N] issues
- Phase 4 (compound findings): [N] issues
## Performance Health Score
[Phase 5 table]
## Issues by Severity
### [SEVERITY] [Category]: [Description]
**File**: path/to/file.swift:line
**Phase**: [2: Detection | 3: Context | 4: Compound]
**Context**: [scrolling cell / static view / navigation — from Phase 1 map]
**Issue**: What's wrong or suboptimal
**Impact**: What users experience (frame drops, jank, slow load)
**Fix**: Code example showing the fix
**Cross-Auditor Notes**: [if overlapping with another auditor]
## Recommendations
1. [Immediate actions — CRITICAL fixes in scrolling contexts]
2. [Short-term — HIGH fixes (navigation, collection dependencies)]
3. [Long-term — architectural improvements from Phase 3 findings]
4. [Verification — profile with Instruments SwiftUI template after fixes]
```
## Output Limits
If >50 issues in one category: Show top 10, provide total count, list top 3 files
If >100 total issues: Summarize by category, show only CRITICAL/HIGH details
## False Positives (Not Issues)
- Formatters in @Observable classes or `static let`
- Small collections (<10 items) with .contains()
- Sets with .contains() (O(1) lookup)
- VStack with few items (<20)
- Image processing in `.task` or background queue
- File I/O in `.task` or async contexts
- ForEach on Identifiable types (automatic identity)
- GeometryReader in non-scrolling, single-instance views
- ObservableObject in iOS 16-only targets
## Related
For SwiftUI Instruments workflows: `axiom-swiftui-performance` skill
For view update debugging: `axiom-swiftui-debugging` skill
For memory lifecycle issues: `axiom-memory-debugging` skill

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Analyze SwiftUI Performance"
short_description: "The user mentions SwiftUI performance, janky scrolling, slow animations, or view update issues."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-analyze-test-failures",
"installedAt": "2026-04-12T08:05:41.291Z"
}

View File

@@ -0,0 +1,379 @@
---
name: axiom-analyze-test-failures
description: Use when the user mentions flaky tests, tests that pass locally but fail in CI, race conditions in tests, or needs to diagnose WHY a specific test fails.
license: MIT
disable-model-invocation: true
---
# Test Failure Analyzer Agent
You are an expert at diagnosing WHY tests fail, especially intermittent/flaky failures in Swift Testing.
## Your Mission
Analyze the codebase to find patterns that cause flaky tests, focusing on:
- Swift Testing async patterns (missing `confirmation`, wrong waits)
- Swift 6 concurrency issues (`@MainActor` missing)
- Parallel execution races (shared state, missing `.serialized`)
- Timing-dependent assertions
Report findings with:
- File:line references
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Root cause explanation
- Fix with code example
## Files to Scan
Include: `*Tests.swift`, `*Test.swift`, `**/*Tests/*.swift`
Skip: `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## Flaky Test Patterns (iOS 18+ / Swift Testing Focus)
### Pattern 1: Missing `await confirmation` (CRITICAL)
**Issue**: Async work without proper waiting
**Why flaky**: Test completes before async callback fires
**Detection**: Closures/callbacks without `confirmation {}`
```swift
// FLAKY - Test may complete before callback
@Test func fetchData() async {
var result: Data?
service.fetch { data in
result = data // May not run before assertion
}
#expect(result != nil) // FAILS intermittently
}
// CORRECT - Waits for callback
@Test func fetchData() async {
await confirmation { confirm in
service.fetch { data in
#expect(data != nil)
confirm()
}
}
}
```
### Pattern 2: `@MainActor` Missing on UI Tests (CRITICAL)
**Issue**: Swift 6 requires explicit actor isolation
**Why flaky**: Data races when accessing @MainActor types
**Detection**: Tests accessing UI types without @MainActor
```swift
// FLAKY - Data race accessing MainActor ViewModel
@Test func viewModelUpdates() async {
let vm = ContentViewModel() // @MainActor type
vm.load() // Data race!
}
// CORRECT - Proper isolation
@Test @MainActor func viewModelUpdates() async {
let vm = ContentViewModel()
await vm.load()
}
```
### Pattern 3: Shared Mutable State in `@Suite` (HIGH)
**Issue**: Static/class vars shared across parallel tests
**Why flaky**: Tests pass individually, fail together
**Detection**: `static var` in test suites
```swift
// FLAKY - Parallel tests mutate shared state
@Suite struct CacheTests {
static var sharedCache: [String: Data] = [:] // Shared!
@Test func storeItem() {
Self.sharedCache["key"] = Data() // Race condition
}
}
// CORRECT - Instance property, fresh per test
@Suite struct CacheTests {
var cache: [String: Data] = [:] // Fresh per test
@Test func storeItem() {
cache["key"] = Data()
}
}
```
### Pattern 4: `Task.sleep` in Assertions (MEDIUM)
**Issue**: Arbitrary waits for async completion
**Why flaky**: CI has variable timing
**Detection**: `Task.sleep` or `try await Task.sleep` in tests
```swift
// FLAKY - Timing-dependent
@Test func loadData() async throws {
viewModel.startLoading()
try await Task.sleep(for: .seconds(2)) // May not be enough
#expect(viewModel.isLoaded)
}
// CORRECT - Condition-based waiting
@Test func loadData() async {
await confirmation { confirm in
viewModel.$isLoaded
.filter { $0 }
.sink { _ in confirm() }
.store(in: &cancellables)
viewModel.startLoading()
}
}
```
### Pattern 5: Missing `.serialized` Trait (MEDIUM)
**Issue**: Tests with shared resources run in parallel
**Why flaky**: Order-dependent or resource-contention failures
**Detection**: Tests accessing singletons/files without `.serialized`
```swift
// FLAKY - Parallel tests compete for singleton
@Suite struct DatabaseTests {
@Test func writeData() { Database.shared.write("a") }
@Test func readData() { _ = Database.shared.read() }
}
// CORRECT - Force serial execution
@Suite(.serialized) struct DatabaseTests {
@Test func writeData() { Database.shared.write("a") }
@Test func readData() { _ = Database.shared.read() }
}
```
### Pattern 6: `#expect` with Date Comparisons (LOW)
**Issue**: Date assertions drift across timezones/DST
**Why flaky**: Passes in one timezone, fails in CI (UTC)
**Detection**: `#expect` with `Date()` or date comparisons
```swift
// FLAKY - Timezone-dependent
@Test func expirationDate() {
let item = CacheItem()
#expect(item.expiresAt > Date()) // May fail near midnight
}
// CORRECT - Use fixed dates or tolerances
@Test func expirationDate() {
let now = Date()
let item = CacheItem(createdAt: now)
#expect(item.expiresAt.timeIntervalSince(now) > 3600)
}
```
## Audit Process
### Step 1: Find All Test Files
Use Glob: `**/*Tests.swift`, `**/*Test.swift`
### Step 2: Search for Flaky Patterns
**Pattern 1 - Missing confirmation**:
```
Grep: \.sink\s*\{|completion\s*:|\.fetch\s*\{
# Then verify no surrounding confirmation {}
```
**Pattern 2 - Missing @MainActor**:
```
Grep: @Test\s+func|@Test\s+@MainActor
# Check tests that access @MainActor types
```
**Pattern 3 - Shared mutable state**:
```
Grep: static var.*=|class var.*=
# In files matching *Tests.swift
```
**Pattern 4 - Task.sleep in tests**:
```
Grep: Task\.sleep|try await Task\.sleep
```
**Pattern 5 - Missing .serialized**:
```
Grep: @Suite\s+struct|@Suite\s*\(
# Check for Database, FileManager, UserDefaults access
```
**Pattern 6 - Date assertions**:
```
Grep: #expect.*Date\(\)|#expect.*\.date
```
### Step 3: Read Context and Verify
For each match:
1. Read surrounding context (20 lines)
2. Verify it's a real issue (not false positive)
3. Check if fix is already present
## Output Format
```markdown
# Test Failure Analysis Results
## Summary
- **CRITICAL Issues**: [count] (Will cause intermittent failures)
- **HIGH Issues**: [count] (Likely flaky in parallel execution)
- **MEDIUM Issues**: [count] (May cause timing issues)
- **LOW Issues**: [count] (Edge case failures)
## Flakiness Risk Score: HIGH / MEDIUM / LOW
## CRITICAL Issues
### Missing `await confirmation`
- `Tests/NetworkTests.swift:45`
```swift
@Test func fetchUser() async {
var user: User?
api.fetchUser { user = $0 }
#expect(user != nil) // FLAKY!
}
```
- **Root cause**: Test completes before async callback
- **Fix**:
```swift
@Test func fetchUser() async {
await confirmation { confirm in
api.fetchUser { user in
#expect(user != nil)
confirm()
}
}
}
```
### Missing `@MainActor`
- `Tests/ViewModelTests.swift:23`
```swift
@Test func updateUI() async {
let vm = MainActorViewModel() // Data race
}
```
- **Root cause**: Accessing @MainActor type without isolation
- **Fix**: Add `@MainActor` to test function
## HIGH Issues
### Shared Mutable State
- `Tests/CacheTests.swift:12` - `static var testCache`
- **Root cause**: Parallel tests mutate same collection
- **Fix**: Use instance property instead of static
## MEDIUM Issues
### Missing `.serialized` Trait
- `Tests/DatabaseTests.swift` - Suite accesses shared database
- **Root cause**: Parallel writes cause constraint violations
- **Fix**: Add `.serialized` trait to `@Suite`
## Verification Steps
After fixes, verify with:
```bash
# Run tests multiple times to detect flakiness
swift test --parallel --num-workers 8
# Run specific test repeatedly
swift test --filter "TestName" --iterations 100
# Xcode: Edit Scheme → Test → Options → "Repeat Until Failure"
```
## Swift Testing Best Practices
| Pattern | Use When |
|---------|----------|
| `confirmation {}` | Any callback/closure-based async |
| `@MainActor` | Test accesses UI types |
| `.serialized` | Tests share singleton/file/database |
| Instance properties | Any test data that changes |
```
## Severity Definitions
**CRITICAL**: Will definitely cause intermittent failures
- Missing `confirmation` for async callbacks
- Missing `@MainActor` for UI tests
**HIGH**: Likely to cause parallel execution failures
- Shared mutable state (`static var`)
- Order-dependent tests
**MEDIUM**: May cause timing-related failures
- `Task.sleep` for waiting
- Missing `.serialized` for shared resources
**LOW**: Edge case failures
- Date/timezone assertions
- Locale-dependent comparisons
## False Positives to Avoid
**Not issues**:
- `static let` constants (immutable is fine)
- `confirmation` already present
- Tests marked with `.serialized`
- `@MainActor` already present
- One-time setup in `static var` that's read-only
**Verify before reporting**:
- Read surrounding context
- Check for `confirmation {}` wrapper
- Check for trait annotations
## XCTest Flaky Patterns (Legacy)
For XCTest code, also check:
### XCTestExpectation Issues
```swift
// ❌ FLAKY - Timeout too short for CI
wait(for: [expectation], timeout: 1.0)
// ✅ BETTER - Generous timeout
wait(for: [expectation], timeout: 10.0)
```
### Missing waitForExistence
```swift
// FLAKY - Element may not exist yet
XCTAssertTrue(app.buttons["Submit"].exists)
// CORRECT - Wait for element
XCTAssertTrue(app.buttons["Submit"].waitForExistence(timeout: 5))
```
## When No Issues Found
Report:
```markdown
# Test Failure Analysis Results
## Summary
No flaky test patterns detected.
## Verified
- ✅ Async tests use `confirmation` properly
- ✅ UI tests have `@MainActor` isolation
- ✅ No shared mutable state in suites
- ✅ No timing-dependent assertions
## Recommendations
- Run tests with `--iterations 100` to verify stability
- Enable parallel testing to expose hidden races
- Use Xcode's "Repeat Until Failure" for suspect tests
```

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Analyze Test Failures"
short_description: "The user mentions flaky tests, tests that pass locally but fail in CI, race conditions in tests, or needs to diagnose..."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-app-attest",
"installedAt": "2026-04-12T08:05:42.610Z"
}

View File

@@ -0,0 +1,335 @@
---
name: axiom-app-attest
description: Use when implementing app integrity verification, preventing fraud with DCAppAttestService, validating requests from legitimate app instances, using DeviceCheck for promotional abuse prevention, or needing server-side attestation/assertion validation. Covers key generation, attestation, assertion, rollout strategy, and risk metrics.
license: MIT
---
# App Attest
Device-backed app integrity verification for fraud prevention. Proves three things to your server: the request came from a genuine Apple device, running your genuine app, with an untampered payload.
## When to Use This Skill
Use when you need to:
- Verify requests come from legitimate app instances (not modified/cloned apps)
- Prevent fraud in purchases, promotions, or competitive features
- Implement DCAppAttestService attestation or assertion flows
- Handle DeviceCheck 2-bit per-device state for promotional abuse
- Build server-side validation for attestation objects or assertion signatures
- Plan a gradual App Attest rollout for a large install base
## Example Prompts
"How do I verify my app hasn't been tampered with?"
"DCAppAttestService attestKey keeps failing with serverUnavailable"
"How do I prevent users from claiming a free trial multiple times?"
"What's the difference between attestation and assertion?"
"How do I validate an attestation object on my server?"
"isSupported returns false — should I block the user?"
"We have 2M DAU, how do I roll out App Attest safely?"
"How do I detect if someone is creating fake app instances?"
## Red Flags
Signs you're headed for trouble:
- **Validating app integrity on-device** — Modified apps control the runtime. Any local check can be patched out. Verification MUST happen server-side.
- **Not guarding with isSupported** — DCAppAttestService crashes on unsupported devices. Always check before calling any API.
- **Blocking users when isSupported returns false** — Some legitimate devices return false. Treat as risk signal, not hard block.
- **Reusing keys across multiple users on same device** — One key per user per device. Shared keys break account-level trust association.
- **Enabling App Attest for all users at once** — `attestKey` calls Apple's servers. At scale, rate limiting causes failures. Gradual rollout required (WWDC 2021-10244).
- **Using assertions for every API call** — Cryptographic cost per call. Reserve for sensitive operations (purchases, account changes), not routine fetches.
- **Discarding key on serverUnavailable error** — Transient Apple server issue. Retry with same key. Only discard on other errors.
- **Skipping counter validation on server** — Counter must be ever-increasing. Without this, replay attacks succeed.
## Three Properties Verified
App Attest proves three things about each request:
| Property | What It Proves | How |
|----------|---------------|-----|
| Genuine device | Request comes from real Apple hardware | Hardware-backed key in Secure Enclave |
| Genuine app | Your app binary, unmodified | App identity hash in attestation |
| Untampered payload | Request data hasn't been altered | Digest signing in assertions |
**Privacy design**: Anonymous. No hardware identifiers. Keys don't survive reinstall/migration/restore. Apple can't correlate across apps or users.
## Key Generation
```swift
import DeviceCheck
func generateAppAttestKey(for userId: String) async throws -> String {
let service = DCAppAttestService.shared
guard service.isSupported else {
// NOT an error use as risk signal, not blocker
reportUnattestedDevice()
throw AppAttestError.unsupported
}
let keyId = try await service.generateKey()
// Cache persistently one key per user per device
UserDefaults.standard.set(keyId, forKey: "appAttestKeyId_\(userId)")
return keyId
}
```
**Key lifecycle**: One key per user per device. Cache `keyId` persistently (Keychain or UserDefaults). Keys don't survive reinstall, migration, or restore. App Clips share identity with full app. Generate new key on sign-out.
## Attestation Flow
Attestation registers the key with Apple and your server. Happens once per key.
```dot
digraph attestation {
"Server issues\nchallenge" [shape=ellipse];
"SHA256 hash\nchallenge" [shape=box];
"attestKey API\n(Apple servers)" [shape=box];
"Send attestation\nto your server" [shape=box];
"Server validates\ncertificate chain" [shape=box];
"Store public key\n+ key association" [shape=doublecircle];
"Error?" [shape=diamond];
"serverUnavailable?" [shape=diamond];
"Retry same key" [shape=box];
"Discard key\ngenerate new" [shape=box];
"Server issues\nchallenge" -> "SHA256 hash\nchallenge";
"SHA256 hash\nchallenge" -> "attestKey API\n(Apple servers)";
"attestKey API\n(Apple servers)" -> "Error?" ;
"Error?" -> "Send attestation\nto your server" [label="success"];
"Error?" -> "serverUnavailable?" [label="error"];
"serverUnavailable?" -> "Retry same key" [label="yes"];
"serverUnavailable?" -> "Discard key\ngenerate new" [label="no"];
"Send attestation\nto your server" -> "Server validates\ncertificate chain";
"Server validates\ncertificate chain" -> "Store public key\n+ key association";
}
```
```swift
func attestKey(userId: String) async throws {
guard let keyId = storedKeyId(for: userId) else {
throw AppAttestError.noKey
}
// 1. Get one-time challenge from YOUR server (minimum 16 bytes)
let challenge = try await server.fetchAttestationChallenge()
// 2. Hash the challenge
let hash = Data(SHA256.hash(data: challenge))
// 3. Request attestation from Apple
do {
let attestation = try await service.attestKey(keyId, clientDataHash: hash)
// 4. Send attestation object to YOUR server for validation
try await server.verifyAttestation(attestation, keyId: keyId, challenge: challenge)
} catch DCError.serverUnavailable {
// Transient retry with SAME key later
scheduleAttestationRetry(keyId: keyId, userId: userId)
} catch {
// Other error key is compromised or invalid
// Discard and generate a new key
clearStoredKey(for: userId)
try await generateAndAttestNewKey(userId: userId)
}
}
```
**Challenge requirements**: Server-generated, single-use, minimum 16 bytes, short-lived (expire after minutes, not hours).
## Assertion Flow
Assertions prove ongoing request integrity. No Apple server involvement — on-device only.
```swift
func assertRequest(payload: Data, userId: String) async throws -> Data {
guard let keyId = storedKeyId(for: userId) else {
throw AppAttestError.noKey
}
// Hash the payload you want to protect
let hash = Data(SHA256.hash(data: payload))
// Generate assertion (on-device, no network)
let assertion = try await service.generateAssertion(keyId, clientDataHash: hash)
// Send assertion + original payload to server
// Server verifies signature and checks counter
return assertion
}
```
**When to assert**: Reserve for moments that cost you money or trust if faked.
| Assert | Don't Assert |
|--------|-------------|
| In-app purchases | Content fetches |
| Account changes (email, password) | Read-only API calls |
| Competitive actions (leaderboard scores) | Analytics events |
| Promotional claims (free trial) | UI configuration |
| Reward redemptions | Search queries |
**Performance**: Secure Enclave operations. Fast enough for individual actions, expensive on every request.
## Server-Side Validation
Your server does the actual trust verification. The app only generates cryptographic material.
### Attestation Validation (once per key)
1. **Certificate chain** — Verify roots to Apple's App Attest root CA (Apple Private PKI)
2. **Nonce** — Recompute SHA256(challenge || clientDataHash), match against credential certificate
3. **App identity hash** — SHA256(teamId + "." + bundleId) must match your app
4. **Counter** — Store initial value (assertions increment from here)
5. **Key association** — Extract and store public key, associate with user account
### Assertion Validation (per sensitive request)
1. **Signature** — Verify using stored public key from attestation
2. **App identity hash** — Must match attestation's hash (prevents cross-app replay)
3. **Counter** — Must be strictly greater than last seen value (replay protection)
4. **Client data hash** — Recompute from request payload, must match what was signed
**Counter is critical**: Without strictly-increasing counter validation, replay attacks succeed indefinitely.
## Rollout Strategy
From WWDC 2021-10244: `attestKey` makes a network call to Apple's servers. Apple rate-limits these calls per app.
| Install Base | Recommended Ramp Time |
|-------------|----------------------|
| <100K DAU | Days |
| ~1M DAU | ~1 day gradual ramp |
| ~100M DAU | Weeks |
| ~1B DAU | 1+ month gradual ramp |
### Gradual Enablement Pattern
```swift
func shouldEnableAppAttest(userId: String) -> Bool {
guard DCAppAttestService.shared.isSupported else { return false }
// Server controls rollout percentage start at 1%, ramp daily
return server.isAppAttestEnabled(for: userId)
}
```
**Rollout process**: Start at 1%. Monitor attestation success rate. If above 95%, double daily. If rate limiting errors spike, pause. Treat unattested requests as lower-trust during rollout (additional fraud signals), not blocked.
## DeviceCheck Integration
DeviceCheck stores 2 bits of state per device on Apple's servers. Different purpose from App Attest.
| Feature | App Attest | DeviceCheck |
|---------|-----------|-------------|
| Purpose | Verify app integrity | Track per-device state |
| Survives reinstall | No | Yes (tied to hardware) |
| Apple servers | Attestation only | Every query |
### Promotional Fraud Prevention
```swift
import DeviceCheck
func checkTrialEligibility() async throws -> Bool {
guard DCDevice.current.isSupported else { return true }
let token = try await DCDevice.current.generateToken()
// Server calls Apple: POST https://api.devicecheck.apple.com/v1/query_two_bits
let state = try await server.queryDeviceState(token: token)
return !state.bit0 // bit0 = has claimed trial
}
func markTrialClaimed() async throws {
let token = try await DCDevice.current.generateToken()
// Server calls Apple: POST https://api.devicecheck.apple.com/v1/update_two_bits
try await server.updateDeviceState(token: token, bit0: true)
}
```
**2 bits, your rules**: Apple stores bits + timestamp. Semantics are yours (e.g., bit0=trial claimed, bit1=abuse flagged). Reset on your schedule. Shared across all apps from the same developer team — coordinate meaning across your portfolio.
## Risk Metric Service
After attestation, redeem the receipt with Apple to get risk metrics:
**Server-side**: POST receipt to `https://data.appattest.apple.com/v1/attestationData` (use `data-development.appattest.apple.com` for sandbox). Response includes approximate key count for the device.
**How to use**: Most devices have 1-3 keys. High key counts signal an attacker creating many fake identities. Redeem periodically (Apple rate-limits), establish a baseline for your app, and combine with other fraud signals (velocity, behavioral analysis).
## Anti-Rationalization Table
| Rationalization | Why It Fails | What To Do Instead |
|----------------|-------------|-------------------|
| "We'll validate integrity on-device" | Modified apps control the runtime and can patch out any local check | All validation on your server. Device only generates crypto material. |
| "isSupported is always true on modern devices" | Some configurations and enterprise MDM setups return false | Always guard. Handle false as risk signal, not crash. |
| "One key per device is enough" | Multi-user devices need per-user keys for accurate account association | One key per user per device. New key on sign-out. |
| "We'll enable App Attest for everyone on launch day" | Apple rate-limits attestKey calls. Large install bases will see widespread failures. | Server-controlled gradual rollout. Monitor success rate. |
| "Assert every API call for maximum security" | Secure Enclave operations have real cost. Assertion latency on every request degrades UX. | Assert sensitive operations only. Use session tokens for routine calls. |
| "serverUnavailable means the key is bad" | It's a transient Apple server issue. Discarding the key forces re-attestation unnecessarily. | Retry with same key. Only discard on non-transient errors. |
| "We don't need counter validation" | Without strictly-increasing counters, replay attacks succeed indefinitely. | Store counter server-side. Reject assertions with counter <= last seen. |
| "DeviceCheck replaces App Attest" | DeviceCheck is 2-bit state storage, not integrity verification. Different threat models. | Use both: App Attest for integrity, DeviceCheck for per-device flags. |
## Pressure Scenarios
### Scenario 1: "Block users who fail attestation"
**Pressure**: "If they can't attest, they're probably running a modified app. Block them."
**Reality**: `isSupported` returns false on legitimate devices (older hardware, enterprise MDM, simulator). During rollout, most users simply haven't been enrolled yet. Blocking = blocking real customers.
**Correct action**: Trust tiers on server. Attested = high trust. Unattested = lower trust with additional fraud signals. Never hard-block on attestation failure alone.
**Push-back template**: "Some legitimate devices return isSupported=false. Let's use attestation as one signal in a risk score — high trust for attested, additional checks for unattested."
### Scenario 2: "Enable App Attest for everyone at once"
**Pressure**: "We've been building this for weeks. Ship it to everyone."
**Reality**: `attestKey` calls Apple's servers. Apple rate-limits per app. At 5M DAU, flipping the switch causes a thundering herd — mass failures, error floods, confused users. WWDC 2021-10244 explicitly recommends gradual rollout.
**Correct action**: Server-controlled rollout starting at 1%. At 5M DAU, expect ~1 week to full rollout.
**Push-back template**: "Apple rate-limits attestKey calls — their WWDC session recommends gradual rollout. I'll set up server-side percentage control starting at 1%, ramping to 100% over about a week."
## Checklist
Before shipping App Attest:
**Key Generation**:
- [ ] `isSupported` checked before any DCAppAttestService call
- [ ] Graceful handling when `isSupported` returns false (risk signal, not block)
- [ ] Key ID cached persistently per user
- [ ] One key per user per device (not shared)
**Attestation**:
- [ ] Challenge from server is single-use, minimum 16 bytes, short-lived
- [ ] `serverUnavailable` retries with same key
- [ ] Other errors discard key and generate new
- [ ] Attestation object sent to server for validation (not validated on-device)
**Assertion**:
- [ ] Used only for sensitive operations (not every API call)
- [ ] Payload hash covers the actual request data being protected
- [ ] Server validates signature with stored public key
- [ ] Server validates counter is strictly increasing
**Server**:
- [ ] Certificate chain validated against Apple's App Attest root CA
- [ ] App identity hash (teamId + bundleId) verified
- [ ] Counter stored and checked for strict increase
- [ ] Public key associated with user account
**Rollout**:
- [ ] Server-controlled percentage (not client-side)
- [ ] Gradual ramp with monitoring
- [ ] Unattested users handled gracefully (lower trust, not blocked)
- [ ] Rollback plan if attestation success rate drops
## Resources
**WWDC**: 2021-10244
**Docs**: /devicecheck, /devicecheck/establishing-your-app-s-integrity, /devicecheck/validating-apps-that-connect-to-your-server
**Skills**: axiom-cryptokit

View File

@@ -0,0 +1,3 @@
interface:
display_name: "App Attest"
short_description: "Implementing app integrity verification, preventing fraud with DCAppAttestService, validating requests from legitimat..."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-app-composition",
"installedAt": "2026-04-12T08:05:43.422Z"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
interface:
display_name: "App Composition"
short_description: "Structuring app entry points, managing authentication flows, switching root views, handling scene lifecycle, or askin..."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-app-discoverability",
"installedAt": "2026-04-12T08:05:44.178Z"
}

View File

@@ -0,0 +1,542 @@
---
name: axiom-app-discoverability
description: Use when making app surface in Spotlight search, Siri suggestions, or system experiences - covers the 6-step strategy combining App Intents, App Shortcuts, Core Spotlight, and NSUserActivity to feed the system metadata for iOS 16+
license: MIT
metadata:
version: "1.0.0"
---
# App Discoverability
## Overview
**Core principle** Feed the system metadata across multiple APIs, let the system decide when to surface your app.
iOS surfaces apps in Spotlight, Siri suggestions, and system experiences based on metadata you provide through App Intents, App Shortcuts, Core Spotlight, and NSUserActivity. The system learns from actual usage and boosts frequently-used actions. No single API is sufficient—comprehensive discoverability requires a multi-API strategy.
**Key insight** iOS boosts shortcuts and activities that users actually invoke. If nobody uses an intent, the system hides it. Provide clear, action-oriented metadata and the system does the heavy lifting.
---
## When to Use This Skill
Use this skill when:
- Making your app appear in Spotlight search results
- Enabling Siri to suggest your app in relevant contexts
- Adding app actions to Action Button (iPhone/Apple Watch Ultra)
- Making app content discoverable system-wide
- Planning discoverability architecture before implementation
- Troubleshooting "why isn't my app being suggested?"
Do NOT use this skill when:
- You need detailed API reference (use app-intents-ref, axiom-app-shortcuts-ref, axiom-core-spotlight-ref)
- You're implementing a specific API (use the reference skills)
- You just want to add a single App Intent (use app-intents-ref)
---
## The 6-Step Discoverability Strategy
This is a proven strategy from developers who've implemented discoverability across multiple production apps. **Implementation time: One evening for minimal viable discoverability.**
### Step 1: Add App Intents
App Intents power Spotlight search, Siri requests, and Shortcut suggestions. **Without AppIntents, your app will never surface meaningfully.**
```swift
struct OrderCoffeeIntent: AppIntent {
static var title: LocalizedStringResource = "Order Coffee"
static var description = IntentDescription("Orders coffee for pickup")
@Parameter(title: "Coffee Type")
var coffeeType: CoffeeType
@Parameter(title: "Size")
var size: CoffeeSize
func perform() async throws -> some IntentResult {
try await CoffeeService.shared.order(type: coffeeType, size: size)
return .result(dialog: "Your \(size) \(coffeeType) is ordered")
}
}
```
**Why this matters** App Intents are the foundation. Everything else builds on them.
See: **app-intents-ref** for complete API reference
---
### Step 2: Add App Shortcuts with Suggested Phrases
App Shortcuts make your intents **instantly available** after install. No configuration required.
```swift
struct CoffeeAppShortcuts: AppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: OrderCoffeeIntent(),
phrases: [
"Order coffee in \(.applicationName)",
"Get my usual coffee from \(.applicationName)"
],
shortTitle: "Order Coffee",
systemImageName: "cup.and.saucer.fill"
)
}
static var shortcutTileColor: ShortcutTileColor = .tangerine
}
```
**Why this matters** Without App Shortcuts, users must manually configure shortcuts. With them, your actions appear immediately in Siri, Spotlight, Action Button, and Control Center.
**Critical** Use `suggestedPhrase` patterns—this increases the chance that the system proposes them in Spotlight action suggestions and Siri's carousel.
See: **app-shortcuts-ref** for phrase patterns and best practices
---
### Step 3: Expose Searchable Content via Core Spotlight
Index content that matters. **The system will surface items that match user queries.**
```swift
import CoreSpotlight
import UniformTypeIdentifiers
func indexOrder(_ order: Order) {
let attributes = CSSearchableItemAttributeSet(contentType: .item)
attributes.title = order.coffeeName
attributes.contentDescription = "Order from \(order.date.formatted())"
attributes.keywords = ["coffee", "order", order.coffeeName]
let item = CSSearchableItem(
uniqueIdentifier: order.id.uuidString,
domainIdentifier: "orders",
attributeSet: attributes
)
CSSearchableIndex.default().indexSearchableItems([item]) { error in
if let error = error {
print("Indexing error: \(error)")
}
}
}
```
**Why this matters** Core Spotlight makes your app's content searchable. When users search for "latte" in Spotlight, your app's orders appear.
**Index only what matters** Don't index everything. Focus on user-facing content (orders, documents, notes, etc.).
See: **core-spotlight-ref** for batching, deletion patterns, and best practices
---
### Step 4: Use NSUserActivity for High-Value Screens
Mark important screens as eligible for search and prediction.
```swift
func viewOrder(_ order: Order) {
let activity = NSUserActivity(activityType: "com.coffeeapp.viewOrder")
activity.title = order.coffeeName
activity.isEligibleForSearch = true
activity.isEligibleForPrediction = true
activity.persistentIdentifier = order.id.uuidString
// Connect to App Intents
activity.appEntityIdentifier = order.id.uuidString
// Provide rich metadata
let attributes = CSSearchableItemAttributeSet(contentType: .item)
attributes.contentDescription = "Your \(order.coffeeName) order"
attributes.thumbnailData = order.imageData
activity.contentAttributeSet = attributes
activity.becomeCurrent()
// In your view controller or SwiftUI view
self.userActivity = activity
}
```
**Why this matters** The system learns which screens users visit frequently and suggests them proactively. Lock screen widgets, Siri suggestions, and Spotlight all benefit.
**Critical** Only mark screens that users would want to return to. Not settings, not onboarding, not error states.
See: **core-spotlight-ref** for eligibility patterns and activity continuation
---
### Step 5: Provide Correct Intent Metadata
Clear descriptions and titles are critical because **Spotlight displays them directly.**
#### ❌ DON'T: Generic or unclear
```swift
static var title: LocalizedStringResource = "Do Thing"
static var description = IntentDescription("Performs action")
```
#### ✅ DO: Specific, action-oriented
```swift
static var title: LocalizedStringResource = "Order Coffee"
static var description = IntentDescription("Orders coffee for pickup")
```
**Parameter summaries must be natural language:**
```swift
static var parameterSummary: some ParameterSummary {
Summary("Order \(\.$size) \(\.$coffeeType)")
}
// Siri: "Order large latte"
```
**Why this matters** Poor metadata means users won't understand what your intent does. Clear metadata = higher usage = system boosts it.
---
### Step 6: Usage-Based Boosting
**The system boosts shortcuts and activities that users actually invoke. If nobody uses an intent, the system hides it.**
This is automatic—you don't control it. What you control:
1. **Discoverability** — Make it easy to find (Steps 1-5)
2. **Utility** — Make it worth using (design good intents)
3. **Promotion** — Show users available shortcuts (SiriTipView)
```swift
// Promote your shortcuts in-app
SiriTipView(intent: OrderCoffeeIntent(), isVisible: $showTip)
.siriTipViewStyle(.dark)
```
**Why this matters** Even perfect metadata won't help if users don't know shortcuts exist. Educate users in your app's UI.
See: **app-shortcuts-ref** for SiriTipView and ShortcutsLink patterns
---
## Decision Tree: Which API for Which Use Case
```
┌─ Need to expose app functionality? ────────────────────────────────┐
│ │
│ ┌─ YES → App Intents (AppIntent protocol) │
│ │ └─ Want instant availability without user setup? │
│ │ └─ YES → App Shortcuts (AppShortcutsProvider) │
│ │ │
│ └─ NO → Exposing app CONTENT (not actions)? │
│ │ │
│ ├─ User-initiated activity (viewing screen)? │
│ │ └─ YES → NSUserActivity with isEligibleForSearch │
│ │ │
│ └─ Indexing all content (documents, orders, notes)? │
│ └─ YES → Core Spotlight (CSSearchableItem) │
│ │
│ ┌─ Already using App Intents? │
│ │ └─ Want automatic Spotlight search for entities? │
│ │ └─ YES → IndexedEntity protocol │
│ │ │
│ └─ Want to connect screen to App Intent entity? │
│ └─ YES → NSUserActivity.appEntityIdentifier │
└──────────────────────────────────────────────────────────────────┘
```
### Quick Reference Table
| Use Case | API | Example |
|----------|-----|---------|
| Expose action to Siri/Shortcuts | `AppIntent` | "Order coffee" |
| Make action available instantly | `AppShortcut` | Appear in Spotlight immediately |
| Index all app content | `CSSearchableItem` | All coffee orders searchable |
| Mark current screen important | `NSUserActivity` | User viewing order detail |
| Auto-generate Find actions | `IndexedEntity` | "Find orders where..." |
| Link screen to App Intent | `appEntityIdentifier` | Deep link to specific order |
---
## Quick Implementation Pattern ("One Evening" Approach)
For minimal viable discoverability:
### 1. Define 1-3 Core App Intents (30 minutes)
```swift
// Your app's most valuable actions
struct OrderCoffeeIntent: AppIntent { /* ... */ }
struct ReorderLastIntent: AppIntent { /* ... */ }
struct ViewOrdersIntent: AppIntent { /* ... */ }
```
### 2. Create AppShortcutsProvider (15 minutes)
```swift
struct CoffeeAppShortcuts: AppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: OrderCoffeeIntent(),
phrases: ["Order coffee in \(.applicationName)"],
shortTitle: "Order",
systemImageName: "cup.and.saucer.fill"
)
// Add 2-3 more shortcuts
}
}
```
### 3. Index Top-Level Content (30 minutes)
```swift
// Index most recent/important content only
func indexRecentOrders() {
let recentOrders = try await OrderService.shared.recent(limit: 20)
let items = recentOrders.map { createSearchableItem(from: $0) }
CSSearchableIndex.default().indexSearchableItems(items)
}
```
### 4. Add NSUserActivity to Detail Screens (30 minutes)
```swift
// In your detail view controllers/views
let activity = NSUserActivity(activityType: "com.app.viewOrder")
activity.isEligibleForSearch = true
activity.becomeCurrent()
self.userActivity = activity
```
### 5. Test in Spotlight and Shortcuts (15 minutes)
- Open Shortcuts app → Search for your app → Verify shortcuts appear
- Search Spotlight → Search for your content → Verify results
- Invoke Siri → "Order coffee in [YourApp]" → Verify works
**Total time: ~2 hours** for basic discoverability
---
## Batch Indexing for Large Content Libraries
When indexing 1,000+ items, index in batches to avoid launch slowdowns:
```swift
func indexAllContent() async {
let allItems = try await ContentService.shared.all()
let batchSize = 100
for batch in stride(from: 0, to: allItems.count, by: batchSize) {
let slice = Array(allItems[batch..<min(batch + batchSize, allItems.count)])
let searchableItems = slice.map { createSearchableItem(from: $0) }
CSSearchableIndex.default().indexSearchableItems(searchableItems) { error in
if let error { print("Batch index error: \(error)") }
}
// Yield between batches to avoid blocking
try? await Task.sleep(for: .milliseconds(50))
}
}
```
**Best practices**:
- Index in batches of 100 during background processing, not at launch
- Use `domainIdentifier` to group content for efficient bulk deletion
- Re-index incrementally when content changes (don't re-index everything)
- For 50,000+ items, use `CSSearchableIndex.beginBatch()` / `endBatch()` for atomic updates
## Spotlight Debugging
When indexed content doesn't appear in Spotlight:
### Verification Checklist
1. **Check indexing succeeded** — Add completion handler logging to `indexSearchableItems`
2. **Wait for processing** — Spotlight may take 10-30 seconds to process new items
3. **Search by exact title** — Spotlight may not match partial keywords initially
4. **Check `contentType`** — Use `.item` for general content; wrong type may affect ranking
### Common Indexing Mistakes
| Problem | Cause | Fix |
|---------|-------|-----|
| Content not appearing | Missing `title` attribute | Always set `attributeSet.title` |
| Low ranking | No keywords | Add relevant `keywords` array |
| Stale results | Not deleting removed items | Call `deleteSearchableItems(withIdentifiers:)` |
| Duplicate results | Unstable unique identifiers | Use persistent IDs (UUID, database primary key) |
| Quota exceeded | Indexing too many items | Limit to user-relevant content (recent, favorited) |
### Testing Spotlight Indexing
```swift
// Verify items are indexed
CSSearchableIndex.default().fetchLastClientState { state, error in
print("Last client state: \(String(describing: state))")
}
// Search programmatically to verify
let query = CSSearchQuery(queryString: "title == 'My Item'*", attributes: ["title"])
query.foundItemsHandler = { items in
print("Found \(items.count) items")
}
query.start()
```
---
## Anti-Patterns (What NOT to Do)
### ❌ ANTI-PATTERN 1: Implementing just App Intents without App Shortcuts
**Problem** Users must manually configure shortcuts. Your app won't appear in Spotlight/Siri automatically.
**Fix** Always create AppShortcutsProvider with suggested phrases.
---
### ❌ ANTI-PATTERN 2: Indexing everything in Core Spotlight
**Problem** Indexing thousands of items causes poor performance and quota issues. Users get overwhelmed.
```swift
// BAD: Index all 10,000 orders
let allOrders = try await OrderService.shared.all()
```
**Fix** Index selectively—recent items, favorites, frequently accessed.
```swift
// GOOD: Index recent orders only
let recentOrders = try await OrderService.shared.recent(limit: 50)
```
---
### ❌ ANTI-PATTERN 3: Generic intent titles and descriptions
**Problem** Spotlight displays these directly. Generic text confuses users.
```swift
// BAD
static var title: LocalizedStringResource = "Action"
static var description = IntentDescription("Does something")
```
**Fix** Use specific, action-oriented language.
```swift
// GOOD
static var title: LocalizedStringResource = "Order Coffee"
static var description = IntentDescription("Orders your favorite coffee for pickup")
```
---
### ❌ ANTI-PATTERN 4: Not educating users about shortcuts
**Problem** Perfect implementation means nothing if users don't know it exists.
**Fix** Use `SiriTipView` to promote shortcuts in your app's UI.
```swift
// Show tip after user places order
SiriTipView(intent: ReorderLastIntent(), isVisible: $showTip)
```
---
### ❌ ANTI-PATTERN 5: Marking every screen as eligible for search
**Problem** System gets confused about what's important. Low-quality suggestions.
```swift
// BAD: Settings screen marked for prediction
activity.isEligibleForPrediction = true // Don't predict Settings!
```
**Fix** Only mark screens users would want to return to (content, not chrome).
```swift
// GOOD: Mark content screens only
if order != nil {
activity.isEligibleForPrediction = true
}
```
---
### ❌ ANTI-PATTERN 6: Forgetting to connect NSUserActivity to App Intents
**Problem** NSUserActivity and App Intents remain siloed. Lost integration opportunities.
**Fix** Use `appEntityIdentifier` to connect them.
```swift
// GOOD: Connect activity to App Intent entity
activity.appEntityIdentifier = order.id.uuidString
```
---
## Code Review Checklist
When reviewing discoverability implementation, verify:
**App Intents:**
- [ ] Intents have clear, action-oriented titles
- [ ] Descriptions explain what the intent does
- [ ] Parameter summaries use natural language phrasing
- [ ] `isDiscoverable = true` for public intents
**App Shortcuts:**
- [ ] AppShortcutsProvider is implemented
- [ ] Suggested phrases include `\(.applicationName)`
- [ ] Phrases are short and action-oriented
- [ ] ShortcutTileColor matches app branding
- [ ] 3-5 core shortcuts defined (not too many)
**Core Spotlight:**
- [ ] Only valuable content is indexed (not everything)
- [ ] Unique identifiers are stable and persistent
- [ ] Domain identifiers group related content
- [ ] Attributes include title, description, keywords
- [ ] Deletion logic exists (when content removed)
**NSUserActivity:**
- [ ] Only high-value screens marked eligible
- [ ] `becomeCurrent()` called when screen appears
- [ ] `resignCurrent()` called when screen disappears
- [ ] `appEntityIdentifier` connects to App Intent entities
- [ ] `contentAttributeSet` provides rich metadata
**User Education:**
- [ ] SiriTipView used to promote shortcuts
- [ ] ShortcutsLink available in settings/help
- [ ] Onboarding mentions Siri/Spotlight support
**Testing:**
- [ ] Shortcuts appear in Shortcuts app
- [ ] Siri recognizes suggested phrases
- [ ] Spotlight returns app content
- [ ] Activity continuation works (tap Spotlight result)
---
## Related Skills
- **app-intents-ref** — Complete App Intents API reference
- **app-shortcuts-ref** — App Shortcuts implementation guide
- **core-spotlight-ref** — Core Spotlight and NSUserActivity reference
---
## Resources
**WWDC**: 260, 275, 2022-10170
**Docs**: /appintents/making-your-app-s-functionality-available-to-siri, /corespotlight
**Skills**: axiom-app-intents-ref, axiom-app-shortcuts-ref, axiom-core-spotlight-ref
---
**Remember** Discoverability isn't one API—it's a strategy. Feed the system metadata across App Intents, App Shortcuts, Core Spotlight, and NSUserActivity. Let iOS decide when to surface your app based on context and user behavior.

View File

@@ -0,0 +1,3 @@
interface:
display_name: "App Discoverability"
short_description: "Making app surface in Spotlight search, Siri suggestions, or system experiences"

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-app-intents-ref",
"installedAt": "2026-04-12T08:05:44.917Z"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
interface:
display_name: "App Intents Reference"
short_description: "Integrating App Intents for Siri, Apple Intelligence, Shortcuts, Spotlight, or system experiences"

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-app-shortcuts-ref",
"installedAt": "2026-04-12T08:05:45.553Z"
}

View File

@@ -0,0 +1,828 @@
---
name: axiom-app-shortcuts-ref
description: Use when implementing App Shortcuts for instant Siri/Spotlight availability, configuring AppShortcutsProvider, adding suggested phrases, or debugging shortcuts not appearing - covers complete App Shortcuts API for iOS 16+
license: MIT
metadata:
version: "1.0.0"
---
# App Shortcuts Reference
## Overview
Comprehensive guide to App Shortcuts framework for making your app's actions instantly available in Siri, Spotlight, Action Button, Control Center, and other system experiences. App Shortcuts are pre-configured App Intents that work immediately after app install—no user setup required.
**Key distinction** App Intents are the actions; App Shortcuts are the pre-configured "surface" that makes those actions instantly discoverable system-wide.
---
## When to Use This Skill
Use this skill when:
- Implementing AppShortcutsProvider for your app
- Adding suggested phrases for Siri invocation
- Configuring instant Spotlight availability
- Creating parameterized shortcuts (skip Siri clarification)
- Using NegativeAppShortcutPhrase to prevent false positives (iOS 17+)
- Promoting shortcuts with SiriTipView
- Updating shortcuts dynamically with updateAppShortcutParameters()
- Debugging shortcuts not appearing in Shortcuts app or Spotlight
- Choosing between App Intents and App Shortcuts
Do NOT use this skill for:
- General App Intents implementation (use app-intents-ref)
- Core Spotlight indexing (use core-spotlight-ref)
- Overall discoverability strategy (use app-discoverability)
---
## Related Skills
- **app-intents-ref** — Complete App Intents implementation reference
- **app-discoverability** — Strategic guide for making apps discoverable
- **core-spotlight-ref** — Core Spotlight and NSUserActivity integration
---
## App Shortcuts vs App Intents
| Aspect | App Intent | App Shortcut |
|--------|-----------|--------------|
| **Discovery** | Must be found in Shortcuts app | Instantly available after install |
| **Configuration** | User configures in Shortcuts | Pre-configured by developer |
| **Siri activation** | Requires custom phrase setup | Works immediately with provided phrases |
| **Spotlight** | Requires donation or IndexedEntity | Appears automatically |
| **Action button** | Not directly accessible | Can be assigned immediately |
| **Setup time** | Minutes per user | Zero |
**When to use App Shortcuts** Every app should provide App Shortcuts for core functionality. They dramatically improve discoverability with zero user effort.
---
## Core Concepts
### AppShortcutsProvider Protocol
**Required conformance** Your app must have exactly one type conforming to `AppShortcutsProvider`.
```swift
struct MyAppShortcuts: AppShortcutsProvider {
// Required: Define your shortcuts
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] { get }
// Optional: Branding color
static var shortcutTileColor: ShortcutTileColor { get }
// Optional: Dynamic updates
static func updateAppShortcutParameters()
// Optional: Negative phrases (iOS 17+)
static var negativePhrases: [NegativeAppShortcutPhrase] { get }
}
```
**Platform support** iOS 16+, iPadOS 16+, macOS 13+, tvOS 16+, watchOS 9+
---
### AppShortcut Structure
Associates an `AppIntent` with spoken phrases and metadata.
```swift
AppShortcut(
intent: StartMeditationIntent(),
phrases: [
"Start meditation in \(.applicationName)",
"Begin mindfulness with \(.applicationName)"
],
shortTitle: "Meditate",
systemImageName: "figure.mind.and.body"
)
```
**Components:**
- `intent` — The App Intent to execute
- `phrases` — Spoken/typed phrases for Siri/Spotlight
- `shortTitle` — Short label for Shortcuts app tiles
- `systemImageName` — SF Symbol for visual representation
---
### AppShortcutPhrase (Suggested Phrases)
**String interpolation** Phrases use `\(.applicationName)` to dynamically include your app's name.
```swift
phrases: [
"Start meditation in \(.applicationName)",
"Meditate with \(.applicationName)"
]
```
**User sees in Siri/Spotlight:**
- "Start meditation in Calm"
- "Meditate with Calm"
**Why this matters** The system uses these exact phrases to trigger your intent via Siri and show suggestions in Spotlight.
---
### @AppShortcutsBuilder
Result builder for defining shortcuts array.
```swift
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
AppShortcut(intent: OrderIntent(), /* ... */)
AppShortcut(intent: ReorderIntent(), /* ... */)
if UserDefaults.standard.bool(forKey: "premiumUser") {
AppShortcut(intent: CustomizeIntent(), /* ... */)
}
}
```
**Result builder features:**
- Conditional shortcuts (if/else)
- Loop-generated shortcuts (for-in)
- Inline array construction
---
## Phrase Template Patterns
### Basic Phrases (No Parameters)
```swift
AppShortcut(
intent: StartWorkoutIntent(),
phrases: [
"Start workout in \(.applicationName)",
"Begin exercise with \(.applicationName)",
"Work out in \(.applicationName)"
],
shortTitle: "Start Workout",
systemImageName: "figure.run"
)
```
**Benefits:**
- Simple, discoverable
- Works for all users
- No parameter ambiguity
**Use when** Intent has no required parameters or parameters have defaults.
---
### Parameterized Phrases (Skip Clarification)
Pre-configure intents with specific parameter values to skip Siri's clarification step.
```swift
// Intent with parameters
struct StartMeditationIntent: AppIntent {
static var title: LocalizedStringResource = "Start Meditation"
@Parameter(title: "Type")
var meditationType: MeditationType?
@Parameter(title: "Duration")
var duration: Int?
}
// Shortcuts with different parameter combinations
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// Generic version (will ask for parameters)
AppShortcut(
intent: StartMeditationIntent(),
phrases: ["Start meditation in \(.applicationName)"],
shortTitle: "Meditate",
systemImageName: "figure.mind.and.body"
)
// Specific versions (skip parameter step)
AppShortcut(
intent: StartMeditationIntent(
meditationType: .mindfulness,
duration: 10
),
phrases: [
"Start quick mindfulness in \(.applicationName)",
"10 minute mindfulness in \(.applicationName)"
],
shortTitle: "Quick Mindfulness",
systemImageName: "brain.head.profile"
)
AppShortcut(
intent: StartMeditationIntent(
meditationType: .sleep,
duration: 20
),
phrases: [
"Start sleep meditation in \(.applicationName)"
],
shortTitle: "Sleep Meditation",
systemImageName: "moon.stars.fill"
)
}
```
**Benefits:**
- One-phrase completion (no follow-up questions)
- Better user experience for common use cases
- Spotlight shows specific shortcuts
**Trade-off** More shortcuts = more visual clutter in Shortcuts app. Balance common cases (3-5 shortcuts) vs flexibility (generic shortcut with parameters).
---
## NegativeAppShortcutPhrase (iOS 17+)
Train the system to NOT invoke your app for certain phrases.
```swift
struct MeditationAppShortcuts: AppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: StartMeditationIntent(),
phrases: ["Start meditation in \(.applicationName)"],
shortTitle: "Meditate",
systemImageName: "figure.mind.and.body"
)
}
// Prevent false positives
static var negativePhrases: [NegativeAppShortcutPhrase] {
NegativeAppShortcutPhrases {
"Stop meditation"
"Cancel meditation"
"End session"
}
}
}
```
**When to use:**
- Phrases that sound similar to your shortcuts but mean the opposite
- Common phrases users might say that shouldn't trigger your app
- Disambiguation when multiple apps have similar capabilities
**Platform** iOS 17.0+, iPadOS 17.0+, macOS 14.0+, tvOS 17.0+, watchOS 10.0+
---
## Discovery UI Components
### SiriTipView — Promote Shortcuts In-App
Display the spoken phrase for a shortcut directly in your app's UI.
```swift
import AppIntents
import SwiftUI
struct OrderConfirmationView: View {
@State private var showSiriTip = true
var body: some View {
VStack {
Text("Order confirmed!")
// Show Siri tip after successful order
SiriTipView(intent: ReorderIntent(), isVisible: $showSiriTip)
.siriTipViewStyle(.dark)
}
}
}
```
**Requirements:**
- Intent must be used in an AppShortcut (otherwise shows empty view)
- isVisible binding controls display state
**Styles:**
- `.automatic` — Adapts to environment
- `.light` — Light background
- `.dark` — Dark background
**Best practice** Show after users complete actions, suggesting easier ways next time.
---
### ShortcutsLink — Link to Shortcuts App
Opens your app's page in the Shortcuts app, listing all available shortcuts.
```swift
import AppIntents
import SwiftUI
struct SettingsView: View {
var body: some View {
List {
Section("Siri & Shortcuts") {
ShortcutsLink()
// Displays "Shortcuts" with standard link styling
}
}
}
}
```
**When to use:**
- Settings screen
- Help/Support section
- Onboarding flow
**Benefits** Single tap takes users to see all your app's shortcuts, with suggested phrases visible.
---
### ShortcutTileColor — Branding
Set the color for your shortcuts in the Shortcuts app.
```swift
struct CoffeeAppShortcuts: AppShortcutsProvider {
static var shortcutTileColor: ShortcutTileColor = .tangerine
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// ...
}
}
```
**Available colors:**
| Color | Use Case |
|-------|----------|
| `.blue` | Default, professional |
| `.tangerine` | Energy, food/beverage |
| `.purple` | Creative, meditation |
| `.teal` | Health, wellness |
| `.red` | Urgent, important |
| `.pink` | Lifestyle, social |
| `.navy` | Business, finance |
| `.yellow` | Productivity, notes |
| `.lime` | Fitness, outdoor |
Full list: `.blue`, `.grape`, `.grayBlue`, `.grayBrown`, `.grayGreen`, `.lightBlue`, `.lime`, `.navy`, `.orange`, `.pink`, `.purple`, `.red`, `.tangerine`, `.teal`, `.yellow`
**Choose color** that matches your app icon or brand identity.
---
## Dynamic Updates
### updateAppShortcutParameters()
Call when parameter options change to refresh stored shortcuts.
```swift
struct MeditationAppShortcuts: AppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// Shortcuts can reference dynamic data
for session in MeditationData.favoriteSessions {
AppShortcut(
intent: StartSessionIntent(session: session),
phrases: ["Start \(session.name) in \(.applicationName)"],
shortTitle: session.name,
systemImageName: session.iconName
)
}
}
static func updateAppShortcutParameters() {
// Called automatically when needed
// Override only if you need custom behavior
}
}
// In your app, when data changes
extension MeditationData {
func markAsFavorite(_ session: Session) {
favoriteSessions.append(session)
// Update App Shortcuts to reflect new data
MeditationAppShortcuts.updateAppShortcutParameters()
}
}
```
**When to call:**
- User adds/removes favorites
- Available options change
- App data structure updates
**Automatic invocation** The system calls this periodically, but you can force updates when you know data changed.
---
## Complete Implementation Example
### Step 1: Define App Intents
```swift
import AppIntents
struct OrderCoffeeIntent: AppIntent {
static var title: LocalizedStringResource = "Order Coffee"
static var description = IntentDescription("Orders coffee for pickup")
@Parameter(title: "Coffee Type")
var coffeeType: CoffeeType
@Parameter(title: "Size")
var size: CoffeeSize
@Parameter(title: "Customizations")
var customizations: String?
static var parameterSummary: some ParameterSummary {
Summary("Order \(\.$size) \(\.$coffeeType)") {
\.$customizations
}
}
func perform() async throws -> some IntentResult {
let order = try await CoffeeService.shared.order(
type: coffeeType,
size: size,
customizations: customizations
)
return .result(
value: order,
dialog: "Your \(size) \(coffeeType) is ordered for pickup"
)
}
}
struct ReorderLastIntent: AppIntent {
static var title: LocalizedStringResource = "Reorder Last Coffee"
static var description = IntentDescription("Reorders your most recent coffee")
static var openAppWhenRun: Bool = false
func perform() async throws -> some IntentResult {
guard let lastOrder = try await CoffeeService.shared.lastOrder() else {
throw CoffeeError.noRecentOrders
}
try await CoffeeService.shared.reorder(lastOrder)
return .result(
dialog: "Reordering your \(lastOrder.coffeeName)"
)
}
}
enum CoffeeType: String, AppEnum {
case latte, cappuccino, americano, espresso
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Coffee"
static var caseDisplayRepresentations: [CoffeeType: DisplayRepresentation] = [
.latte: "Latte",
.cappuccino: "Cappuccino",
.americano: "Americano",
.espresso: "Espresso"
]
}
enum CoffeeSize: String, AppEnum {
case small, medium, large
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Size"
static var caseDisplayRepresentations: [CoffeeSize: DisplayRepresentation] = [
.small: "Small",
.medium: "Medium",
.large: "Large"
]
}
```
---
### Step 2: Create AppShortcutsProvider
```swift
import AppIntents
struct CoffeeAppShortcuts: AppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// Generic order (will ask for parameters)
AppShortcut(
intent: OrderCoffeeIntent(),
phrases: [
"Order coffee in \(.applicationName)",
"Get coffee from \(.applicationName)"
],
shortTitle: "Order",
systemImageName: "cup.and.saucer.fill"
)
// Common specific orders (skip parameter step)
AppShortcut(
intent: OrderCoffeeIntent(
coffeeType: .latte,
size: .medium
),
phrases: [
"Order my usual from \(.applicationName)",
"Get my regular coffee from \(.applicationName)"
],
shortTitle: "Usual Order",
systemImageName: "star.fill"
)
// Reorder last
AppShortcut(
intent: ReorderLastIntent(),
phrases: [
"Reorder coffee from \(.applicationName)",
"Order again from \(.applicationName)"
],
shortTitle: "Reorder",
systemImageName: "arrow.clockwise"
)
}
// Branding
static var shortcutTileColor: ShortcutTileColor = .tangerine
// Prevent false positives (iOS 17+)
static var negativePhrases: [NegativeAppShortcutPhrase] {
NegativeAppShortcutPhrases {
"Cancel coffee order"
"Stop coffee"
}
}
}
```
---
### Step 3: Promote in UI
```swift
import SwiftUI
import AppIntents
struct OrderConfirmationView: View {
@State private var showReorderTip = true
var body: some View {
VStack(spacing: 20) {
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 60))
.foregroundColor(.green)
Text("Order Placed!")
.font(.title)
Text("Your coffee will be ready in 10 minutes")
.foregroundColor(.secondary)
// Promote reorder shortcut
if showReorderTip {
SiriTipView(intent: ReorderLastIntent(), isVisible: $showReorderTip)
.siriTipViewStyle(.dark)
.padding(.top)
}
// Link to see all shortcuts
Section {
ShortcutsLink()
} header: {
Text("See all available shortcuts")
.font(.caption)
}
}
.padding()
}
}
```
---
## Where App Shortcuts Appear
Once implemented, your App Shortcuts are available in:
| Location | User Experience |
|----------|-----------------|
| **Siri** | Voice activation with provided phrases |
| **Spotlight** | Search for action or phrase → Instant execution |
| **Shortcuts app** | Pre-populated shortcuts, zero configuration |
| **Action Button** (iPhone 15 Pro) | Assignable to hardware button |
| **Apple Watch Ultra** | Action Button assignment |
| **Control Center** | Add shortcuts as controls |
| **Lock Screen widgets** | Quick actions without unlocking |
| **Apple Pencil Pro** | Squeeze gesture assignment |
| **Focus Filters** | Contextual filtering |
**Instant availability** All locations work immediately after app install. No user setup required.
---
## Testing & Debugging
### Verify Shortcuts Appear in Shortcuts App
1. Build and run your app on device
2. Open Shortcuts app
3. Tap "+" to create new shortcut
4. Search for your app name
5. Verify shortcuts appear with correct titles and icons
**If shortcuts don't appear:**
- Ensure AppShortcutsProvider is in your main app target
- Check that `isDiscoverable` is true for the AppIntents (default)
- Rebuild and reinstall app
- Check console for AppShortcuts errors
---
### Test Siri Invocation
1. Invoke Siri
2. Say one of your suggested phrases
3. Verify Siri executes the intent
**Example**:
- You: "Order coffee in CoffeeApp"
- Siri: "What size and type?"
- You: "Medium latte"
- Siri: "Your medium latte is ordered for pickup"
**If Siri doesn't recognize phrase:**
- Check phrase includes `\(.applicationName)`
- Verify phrase is in appShortcuts array
- Try simpler phrases (3-6 words ideal)
- Avoid complex grammar or rare words
---
### Test Spotlight Discovery
1. Swipe down to open Spotlight
2. Type your app name or shortcut phrase
3. Verify shortcut appears in results
4. Tap to execute
**If shortcut doesn't appear in Spotlight:**
- Wait a few minutes (indexing delay)
- Restart device
- Check System Settings → Siri & Search → [Your App] → Show App in Search
---
### Debug with Console Logs
```swift
#if DEBUG
struct CoffeeAppShortcuts: AppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
let shortcuts = [
AppShortcut(/* ... */),
// ...
]
print("📱 Registered \(shortcuts.count) App Shortcuts")
shortcuts.forEach { shortcut in
print(" - \(shortcut.shortTitle)")
}
return shortcuts
}
}
#endif
```
**Check Xcode console** after app launch to verify shortcuts are registered.
---
## Best Practices
### 1. Phrase Design
#### ❌ DON'T: Long, complex phrases
```swift
phrases: [
"I would like to order a coffee from \(.applicationName) please"
]
```
#### ✅ DO: Short, natural phrases
```swift
phrases: [
"Order coffee in \(.applicationName)",
"Get coffee from \(.applicationName)"
]
```
**Guidelines:**
- 3-6 words ideal
- Start with verb (Order, Start, Get, Show)
- Include `\(.applicationName)` for disambiguation
- Use natural language users would actually say
---
### 2. Shortcut Quantity
#### ❌ DON'T: Provide 20+ shortcuts
```swift
// Bad: Overwhelming
AppShortcut for every possible combination
```
#### ✅ DO: Focus on 3-5 core actions
```swift
// Good: Focused on common tasks
AppShortcut(intent: OrderIntent(), /* ... */)
AppShortcut(intent: ReorderIntent(), /* ... */)
AppShortcut(intent: ViewOrdersIntent(), /* ... */)
```
**Why** Too many shortcuts creates clutter. Focus on high-value, frequently-used actions.
---
### 3. Parameter Combinations
#### ❌ DON'T: Parameterize every variant
```swift
// Bad: Creates 12 shortcuts (3 sizes × 4 types)
for size in CoffeeSize.allCases {
for type in CoffeeType.allCases {
AppShortcut(intent: OrderIntent(type: type, size: size), /* ... */)
}
}
```
#### ✅ DO: Provide generic + top 2-3 common cases
```swift
// Good: Generic + common specific cases
AppShortcut(intent: OrderIntent(), /* ... */) // Generic
AppShortcut(intent: OrderIntent(type: .latte, size: .medium), /* ... */) // Usual
AppShortcut(intent: OrderIntent(type: .espresso, size: .small), /* ... */) // Quick
```
---
### 4. Short Titles
#### ❌ DON'T: Verbose or redundant
```swift
shortTitle: "Order Coffee from Coffee App"
```
#### ✅ DO: Concise and clear
```swift
shortTitle: "Order"
```
**Context** App name already appears in Shortcuts app, so no need to repeat.
---
### 5. System Images
#### ❌ DON'T: Use custom images
```swift
// Not supported
shortImage: UIImage(named: "custom")
```
#### ✅ DO: Use SF Symbols
```swift
systemImageName: "cup.and.saucer.fill"
```
**Why** SF Symbols scale properly, support dark mode, and integrate with system UI.
---
## Resources
**WWDC**: 2022-10170, 2022-10169, 260
**Docs**: /appintents/appshortcutsprovider, /appintents/appshortcut, /appintents/app-shortcuts
**Skills**: axiom-app-intents-ref, axiom-app-discoverability, axiom-core-spotlight-ref
---
**Remember** App Shortcuts make your app's functionality instantly available across iOS. Define 3-5 core shortcuts with natural phrases, promote them in your UI with SiriTipView, and users can invoke them immediately via Siri, Spotlight, Action Button, and more.

View File

@@ -0,0 +1,3 @@
interface:
display_name: "App Shortcuts Reference"
short_description: "Implementing App Shortcuts for instant Siri/Spotlight availability, configuring AppShortcutsProvider, adding suggeste..."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-app-store-connect-ref",
"installedAt": "2026-04-12T08:05:46.228Z"
}

View File

@@ -0,0 +1,347 @@
---
name: axiom-app-store-connect-ref
description: Use when navigating App Store Connect to find crash data, read TestFlight feedback, interpret metrics dashboards, or export diagnostic logs. Covers crash-free rates, dSYM symbolication, termination types, MetricKit.
license: MIT
metadata:
version: "1.0.0"
---
# App Store Connect Reference
## Overview
App Store Connect (ASC) provides crash reports, TestFlight feedback, and performance metrics for your apps. This reference covers how to navigate ASC to find and export crash data for analysis.
#### ASC vs Xcode Organizer
| Task | Best Tool |
|------|-----------|
| Quick crash triage during development | Xcode Organizer |
| Team-wide crash visibility | App Store Connect |
| TestFlight feedback with screenshots | App Store Connect |
| Historical metrics and trends | App Store Connect |
| Downloading crash logs for analysis | Either (ASC has better export) |
| Symbolication | Xcode Organizer |
---
## Navigating to Crash Data
### Path to Crashes
```
App Store Connect
└── My Apps
└── [Your App]
└── Analytics
└── Crashes
```
**Direct URL pattern:** `https://appstoreconnect.apple.com/analytics/app/[APP_ID]/crashes`
### Crashes Dashboard Sections
1. **Filters bar** — Platform, Version, Date Range, Compare
2. **Crash-Free Users graph** — Daily percentage trend line
3. **Crash Count by Version** — Bar chart comparing versions
4. **Top Crash Signatures** — Ranked by share percentage, shows exception type and function name
### Key Metrics Explained
| Metric | What It Means |
|--------|---------------|
| **Crash-Free Users** | Percentage of daily active users who didn't experience a crash |
| **Crash Count** | Total number of crash reports received |
| **Crash Rate** | Crashes per 1,000 sessions |
| **Affected Devices** | Number of unique devices that crashed |
| **Crash Signature** | Grouped crashes with same stack trace |
### Filtering Options
| Filter | Use Case |
|--------|----------|
| **Platform** | iOS, iPadOS, macOS, watchOS, tvOS |
| **Version** | Drill into specific app versions |
| **Date Range** | Last 7/30/90 days or custom range |
| **Compare** | Compare crash rates between versions |
| **Device** | Filter by iPhone model, iPad, etc. |
| **OS Version** | Find OS-specific crashes |
---
## Viewing Individual Crash Reports
### Crash Signature Detail
Each crash signature shows:
- **Header** — Exception type and affected device/crash share counts, first seen date
- **Exception Information** — Type (e.g., EXC_BAD_ACCESS), codes, address
- **Crashed Thread** — Stack frames with binary, function, and offset
- **Distribution** — Breakdown by iOS version and device model
### Downloading Crash Logs
1. Click on a crash signature
2. Look for **Download Logs** button (top right)
3. Select format:
- **.ips** (JSON format, iOS 15+)
- **.crash** (text format, legacy)
4. Use `crash-analyzer` agent to parse: `/axiom:analyze-crash`
---
## TestFlight Feedback
### Path to Feedback
```
App Store Connect
└── My Apps
└── [Your App]
└── TestFlight
└── Feedback
```
### Feedback Entry Contents
Each feedback submission includes:
| Field | Description |
|-------|-------------|
| **Screenshot** | What the tester saw (often most valuable) |
| **Comment** | Tester's description of the issue |
| **App Version** | Exact TestFlight build number |
| **Device Model** | iPhone 15 Pro Max, iPad Air, etc. |
| **OS Version** | iOS 17.2.1, etc. |
| **Battery Level** | Low battery can affect behavior |
| **Available Disk** | Low disk can cause write failures |
| **Network Type** | WiFi vs Cellular |
| **Locale** | Language and region settings |
| **Timestamp** | When submitted |
### Feedback Filtering
| Filter | Use Case |
|--------|----------|
| **Build** | Focus on specific TestFlight builds |
| **Date** | Recent feedback first |
| **Has Screenshot** | Find visual issues quickly |
### Limitation: No Reply
TestFlight feedback is **one-way**. You cannot respond to testers through ASC. For follow-up:
- Contact through TestFlight group email
- Add in-app feedback mechanism
- Include your email in TestFlight notes
---
## Metrics Dashboard
### Path to Metrics
```
App Store Connect
└── My Apps
└── [Your App]
└── Analytics
└── Metrics
```
### Available Metrics Categories
| Category | What It Shows |
|----------|---------------|
| **Crashes** | Crash-free users, crash count, top signatures |
| **Hang Rate** | Main thread hangs > 250ms |
| **Disk Writes** | Excessive disk I/O patterns |
| **Launch Time** | App startup performance |
| **Memory** | Peak memory usage, terminations |
| **Battery** | Energy usage during foreground/background |
| **Scrolling** | Scroll hitch rate |
### Terminations (Non-Crash Kills)
The Metrics dashboard shows terminations that don't produce crash reports:
| Termination Type | Cause |
|------------------|-------|
| **Memory Limit** | Jetsam killed app for memory pressure |
| **CPU Limit (Background)** | Exceeded background CPU quota |
| **Launch Timeout** | App took too long to launch |
| **Background Task Timeout** | Background task exceeded time limit |
### Comparing Versions
Use the **Compare** filter to see:
- Did crash rate improve or regress?
- Which version introduced a spike?
- Performance trends over releases
---
## Exporting Data
### Manual Export
1. Navigate to Crashes or Metrics
2. Use date range filter to select period
3. Click **Export** (if available) or download individual crash logs
### App Store Connect API
For automated export, use the [App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi):
```bash
# Get crash diagnostic insights
GET /v1/apps/{id}/perfPowerMetrics
# Authentication requires API key from ASC
# Users and Access → Keys → App Store Connect API
```
**API capabilities:**
| Endpoint | Data |
|----------|------|
| `perfPowerMetrics` | Performance and power metrics |
| `diagnosticSignatures` | Crash signature aggregates |
| `diagnosticLogs` | Individual crash logs |
| `betaTesters` | TestFlight tester info |
| `betaFeedback` | TestFlight feedback entries |
### MCP-Powered Access
If **asc-mcp** is configured, you can access ASC data programmatically from Claude Code:
| Manual ASC Action | asc-mcp Tool |
|-------------------|-------------|
| View crash metrics | `metrics_app_perf`, `metrics_build_diagnostics` |
| Download crash logs | `metrics_get_diagnostic_logs` |
| List TestFlight testers | `builds_get_beta_testers` |
| View app reviews | `reviews_list`, `reviews_stats` |
| Respond to reviews | `reviews_create_response` |
| Check build status | `builds_get_processing_state` |
| Export sales data | `analytics_sales_report` (requires vendor_number) |
**Setup and workflows**: `/skill axiom-asc-mcp`
### Xcode Cloud Integration
If using Xcode Cloud, crash data integrates with CI/CD:
- View crashes per workflow run
- Compare crash rates between branches
- Automated alerts on crash spikes
---
## Best Practices
### Daily Monitoring
1. Check crash-free users percentage
2. Review any new crash signatures
3. Monitor for version-to-version regressions
### Crash Triage Priority
| Priority | Criteria |
|----------|----------|
| **P0 - Critical** | >1% of users affected, data loss risk |
| **P1 - High** | >0.5% affected, user-facing impact |
| **P2 - Medium** | <0.5% affected, workaround exists |
| **P3 - Low** | Rare, edge case, no impact |
### Correlating with Releases
After each release:
1. Wait 24-48 hours for crash data to populate
2. Compare crash-free rate to previous version
3. Investigate any new top crash signatures
4. Check TestFlight feedback for user reports
---
## Common Questions
### Why don't I see crashes in ASC?
| Cause | Fix |
|-------|-----|
| Too recent | Wait 24 hours for processing |
| No users yet | Need active installs to report |
| User opted out | Requires device analytics sharing |
| Build not distributed | Must be TestFlight or App Store |
### Why are crashes unsymbolicated?
ASC crashes should auto-symbolicate if you uploaded dSYMs during distribution. **dSYM files** contain the debug symbols that map memory addresses back to function names and line numbers.
**Verify dSYMs were uploaded:**
1. Xcode → Window → Organizer → Archives → select build
2. Right-click → "Show in Finder" → right-click `.xcarchive` → "Show Package Contents"
3. Check `dSYMs/` folder contains `.dSYM` bundles
**Manual symbolication workflow:**
```bash
# 1. Download .ips file from ASC (Crashes → signature → Download Logs)
# 2. Find the binary UUID from the crash report
grep --after-context=2 "Binary Images" crash.ips
# Look for: 0x100000000 - 0x100ffffff MyApp arm64 <UUID>
# 3. Locate matching dSYM on your machine
mdfind "com_apple_xcode_dsym_uuids == <UUID>"
# 4. Symbolicate an address
atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
-l 0x100000000 0x100045abc
# Output: -[UserManager currentUser] (UserManager.m:42)
```
**Common symbolication failures:**
| Symptom | Cause | Fix |
|---------|-------|-----|
| All addresses unsymbolicated | dSYMs not uploaded | Re-upload from Xcode Organizer |
| Only your code unsymbolicated | dSYM UUID mismatch | Rebuild from same commit |
| System frameworks unsymbolicated | Normal for device-specific | Use `atos` with device support files |
| Bitcode builds unsymbolicated | Apple recompiled binary | Download dSYMs from ASC: Xcode → Organizer → Download Debug Symbols |
See `crash-analyzer` agent for automated parsing: `/axiom:analyze-crash`
### ASC vs Organizer: Which stack trace is better?
Both show the same data, but:
- **Organizer** integrates with Xcode projects (click to jump to code)
- **ASC** better for team-wide visibility and historical trends
---
## Field Diagnostics with MetricKit
For device-level crash diagnostics, hang call stacks, and custom telemetry beyond ASC's aggregated dashboards, see `axiom-metrickit-ref`.
**Key difference**: ASC shows aggregated trends for team visibility. MetricKit provides per-device diagnostics you can correlate with your own telemetry.
---
## Related
**Skills**: axiom-testflight-triage (Xcode Organizer workflows), axiom-asc-mcp (programmatic ASC access via MCP)
**Agents**: crash-analyzer (automated crash log parsing)
**Commands**: `/axiom:analyze-crash`
---
## Resources
**WWDC:** 2020-10076, 2020-10078, 2021-10203, 2021-10258
**Docs:** /app-store-connect/api, /xcode/diagnosing-issues-using-crash-reports-and-device-logs

View File

@@ -0,0 +1,3 @@
interface:
display_name: "App Store Connect Reference"
short_description: "Navigating App Store Connect to find crash data, read TestFlight feedback, interpret metrics dashboards, or export di..."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-app-store-diag",
"installedAt": "2026-04-12T08:05:46.842Z"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
interface:
display_name: "App Store Diagnostics"
short_description: "App is rejected by App Review, submission blocked, or appeal needed"

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-app-store-ref",
"installedAt": "2026-04-12T08:05:47.497Z"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
interface:
display_name: "App Store Reference"
short_description: "Looking up ANY App Store metadata field requirement, privacy manifest schema, age rating tier, export compliance deci..."

View File

@@ -0,0 +1,133 @@
# App Review Guidelines Index
Verified against Apple's published guidelines (February 6, 2026 revision).
## Section 1: Safety
| Guideline | Topic |
|-----------|-------|
| 1.1 | Objectionable Content |
| 1.1.1 | Defamatory, discriminatory, or mean-spirited content |
| 1.1.2 | Realistic portrayals of people or animals being killed/maimed/tortured/abused |
| 1.1.3 | Depictions encouraging weapons use against people/animals |
| 1.1.4 | Pornographic material (immediate removal) |
| 1.1.5 | Religious/cultural/ethnic commentary that fosters prejudice |
| 1.1.6 | False information, fake functionality ("for entertainment" does NOT excuse this) |
| 1.1.7 | Capitalizing on recent events (tragedies, conflicts, epidemics) |
| 1.2 | User-Generated Content — must have filtering, reporting, blocking, contact info, age verification |
| 1.3 | Kids Category — no third-party analytics/advertising, COPPA/GDPR-Kids compliance |
| 1.4 | Physical Harm |
| 1.4.1 | Medical apps: disclose limitations, link to real medical help |
| 1.4.2 | Drug dosage calculators: recognized institutions only |
| 1.4.3 | Tobacco, e-cigarettes, vape, illegal drug use encouragement |
| 1.4.4 | DUI/checkpoint apps that encourage reckless behavior |
| 1.4.5 | Activities that risk physical harm (bets, dares, body modification) |
| 1.5 | Developer Information — program membership must be current |
| 1.6 | Data Security — ATS required, justified exceptions only |
## Section 2: Performance
| Guideline | Topic |
|-----------|-------|
| 2.1 | App Completeness — no crashes, broken links, placeholders, missing demo accounts |
| 2.2 | Beta/Demo/Trial — use TestFlight, not "beta" in app name or bundle ID |
| 2.3 | Accurate Metadata |
| 2.3.1 | No hidden/undocumented features; no misleading descriptions |
| 2.3.2 | No concealed features |
| 2.3.3 | Screenshots must reflect actual app experience on correct device |
| 2.3.5 | Use accurate App Store category |
| 2.3.6 | Age rating must match actual content |
| 2.3.7 | App name max 30 chars; no keyword stuffing in name/subtitle |
| 2.3.8 | Metadata must be age-appropriate; "For Kids"/"For Children" reserved for Kids category |
| 2.4 | Hardware Compatibility — must work with current OS |
| 2.5 | Software Requirements |
| 2.5.1 | Only public APIs |
| 2.5.2 | Self-contained; no code downloads that change functionality |
| 2.5.3 | No viruses, malware, code injection (immediate removal) |
| 2.5.4 | Multitasking must use proper background modes |
| 2.5.5 | Must be fully functional on IPv6-only networks |
| 2.5.6 | Web browsing must use WebKit (alternative engine entitlement available) |
| 2.5.9 | Request only necessary permissions |
| 2.5.11 | SiriKit/HealthKit must actually use the declared feature |
| 2.5.17 | Matter integration must use Apple's framework; third-party components CSA-certified |
| 2.5.18 | No display advertising in extensions, App Clips, widgets, notifications, keyboards, watchOS |
## Section 3: Business
| Guideline | Topic |
|-----------|-------|
| 3.1.1 | In-App Purchase required for digital goods/services. Loot box odds must be disclosed before purchase. NFTs: may sell via IAP, ownership must not unlock features. |
| 3.1.2 | Subscriptions: ongoing value, 7-day minimum period, cross-device, transparent terms (price, duration, auto-renewal, cancellation). Schedule 2 of DPLA requires ToS/PP on purchase screen. |
| 3.1.3(a-e) | External payments: reader apps, multiplatform, enterprise, person-to-person, physical goods |
| 3.1.4 | No artificial barriers between IAP and web purchase options |
| 3.1.5 | Cryptocurrency: wallets require organization enrollment, exchanges need licensing, no on-device mining, no crypto rewards for tasks |
| 3.2.2(viii) | Binary options trading apps prohibited |
| 3.2.2(ix) | Loan apps: max 36% APR including fees, no full repayment required within 60 days |
## Section 4: Design
| Guideline | Topic |
|-----------|-------|
| 4.0 | General design standards (HIG compliance) |
| 4.1 | Copycats — apps confusingly similar to existing apps (4.1(b): impersonation = removal from Developer Program) |
| 4.2 | Minimum Functionality — no web wrappers, no single-media apps, must have lasting value |
| 4.2.6 | Template/app-generation-service apps rejected unless submitted by content provider |
| 4.3 | Spam — no duplicate apps from same developer |
| 4.4.1 | Keyboard extensions must include next-keyboard switching |
| 4.5.4 | Push notifications: no advertising, marketing, or spam |
| 4.7 | Mini apps, streaming games, chatbots, emulators: must provide universal link index, age restrictions, content filtering |
| 4.8 | Sign in with Apple required when ANY third-party/social login offered (exceptions: company-internal, education, government, client apps for specific services) |
| 4.10 | Cannot monetize built-in capabilities (push, camera, gyroscope, Apple Music, iCloud storage, Screen Time APIs) |
## Section 5: Legal
| Guideline | Topic |
|-----------|-------|
| 5.1.1(i) | Privacy policy required in App Store Connect AND within app |
| 5.1.1(ii) | Permission requests must explain purpose with benefit to user |
| 5.1.1(iii) | Don't require unnecessary personal info |
| 5.1.1(v) | Account deletion must be offered if account creation supported |
| 5.1.1(vi) | Surreptitiously discovering passwords (removal from Developer Program) |
| 5.1.2(i) | No sharing with third parties without consent; ATT required for tracking |
| 5.1.3 | Health data must not be stored in iCloud; no false HealthKit data |
| 5.1.4 | Kids Category requirements (COPPA) |
| 5.1.5 | Location Services must have clear purpose |
| 5.2 | Intellectual Property — no unauthorized copyrighted material |
| 5.3 | Gaming/Gambling — real-money gambling requires licensing |
| 5.4 | VPN Apps — must use NEVPNManager API |
| 5.5 | Developer Code of Conduct |
| 5.6 | Telecommunications |
## Zero-Tolerance Guidelines (Immediate Removal Risk)
| Guideline | Consequence |
|-----------|-------------|
| 1.1.4 | Pornographic content → immediate removal |
| 2.5.3 | Viruses/malware → immediate removal |
| 4.1(b) | App impersonation → removal from Developer Program |
| 5.1.1(vi) | Surreptitious password discovery → removal from Developer Program |
## Top 10 Rejection Causes
| Rank | Guideline | Issue | % of Rejections |
|------|-----------|-------|-----------------|
| 1 | 2.1 | App Completeness (crashes, placeholders, broken flows) | ~40% |
| 2 | 5.1.1(i) | Privacy policy missing/inadequate | — |
| 3 | 2.1 | Incomplete review info (missing demo accounts) | — |
| 4 | 2.3.3 | Screenshots don't match app | — |
| 5 | 4.0 | Substandard UI / HIG violations | — |
| 6 | 4.2 | Web wrapper / insufficient functionality | — |
| 7 | 2.3.1 | Misleading metadata | — |
| 8 | 4.2 | Insufficient lasting value | — |
| 9 | 4.1 | Copycat app | — |
| 10 | 4.3 | Repeated similar apps | — |
## Sensitive App Types Requiring Extra Documentation
| Type | Requirements |
|------|-------------|
| Kids apps with third-party ads | Links to ad policies, proof of human review |
| Medical hardware integration | Regulatory clearance for all regions |
| Third-party content/trademarks | Authorization documentation |
| Gambling, VPN, real money gaming | Licensing documentation |
| Banking, crypto, healthcare, air travel | Must be submitted by legal entity (not individuals) |

View File

@@ -0,0 +1,95 @@
# Expert Review Checklist
Comprehensive 9-section submission checklist. For the discipline-focused pre-flight workflow, see `app-store-submission`.
## Build
- [ ] Built with required SDK version (currently Xcode 16, iOS 18 SDK)
- [ ] Export compliance answered (`ITSAppUsesNonExemptEncryption`)
- [ ] Encryption documentation uploaded (if custom encryption)
- [ ] IPv6-only network compatible
- [ ] Signed with distribution certificate and provisioning profile
- [ ] Correct bundle ID for target environment (production, not development)
- [ ] Build string unique for this version
- [ ] Binary under 200 MB OTA cellular limit (or warn users)
- [ ] All required architectures included (arm64)
- [ ] No private API usage
## Privacy
- [ ] `PrivacyInfo.xcprivacy` present and complete
- [ ] Privacy policy URL set in App Store Connect
- [ ] Privacy policy accessible within the app
- [ ] All purpose strings (`NS*UsageDescription`) present for requested permissions
- [ ] ATT implemented if app tracks users
- [ ] Required Reason APIs declared with approved reasons
- [ ] Privacy Nutrition Labels match actual data collection
- [ ] Third-party SDK privacy manifests included
- [ ] Privacy report generated and reviewed (`Product > Archive > Generate Privacy Report`)
## Metadata
- [ ] App name unique, max 30 characters
- [ ] Description complete, max 4000 characters, plain text
- [ ] Keywords set, max 100 bytes, no trademarked terms
- [ ] Screenshots provided for all supported device sizes
- [ ] Screenshots show app in actual use (not title art or splash screens)
- [ ] What's New text updated for this version
- [ ] Copyright field current year
- [ ] Support URL links to real contact information
- [ ] Privacy Policy URL is HTTPS and publicly accessible
- [ ] Promotional Text set (editable without submission)
- [ ] App category accurate
- [ ] All metadata localized for target markets
## Account
- [ ] Account deletion implemented and easy to find
- [ ] SIWA token revocation on account deletion
- [ ] Sign in with Apple offered if any third-party login exists
- [ ] SIWA given equal visual prominence to other login options
- [ ] Demo credentials provided in App Review Information (if login required)
- [ ] Demo credentials will not expire during review period
## Content
- [ ] No placeholder content ("Lorem ipsum", "Coming Soon", etc.)
- [ ] All links functional and leading to real content
- [ ] Final production assets (not development/staging URLs)
- [ ] No test data visible in screenshots or app
- [ ] No references to other mobile platforms in metadata
## Age Rating
- [ ] Age rating questionnaire completed
- [ ] New capability declarations answered (messaging, UGC, advertising, parental, age assurance)
- [ ] UGC moderation implemented if applicable
- [ ] Content filtering in place for web views (or accept 16+ minimum)
- [ ] Loot box odds disclosed if applicable
## Monetization
- [ ] All IAPs configured and in "Ready to Submit" status
- [ ] IAP screenshots uploaded
- [ ] Subscription terms clear (price, duration, auto-renewal, cancellation)
- [ ] Loot box odds displayed before purchase
- [ ] Restore Purchases functionality working
- [ ] No removing paid features to force new purchases
- [ ] Subscription grace period supported
- [ ] Offer codes configured if planned
## EU Compliance
- [ ] DSA trader status declared for all EU-distributed apps
- [ ] Trader email verified via 2FA
- [ ] Trader phone verified via 2FA
- [ ] Contact information accurate and current
- [ ] Labels and markings complete (if applicable for product category)
## App Review
- [ ] Contact information complete (name, email, phone)
- [ ] Demo account credentials provided (if login required)
- [ ] Notes for Review explain any non-obvious features
- [ ] Attachment uploaded for features requiring special hardware or setup
- [ ] Review contact email actively monitored

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-app-store-submission",
"installedAt": "2026-04-12T08:05:48.151Z"
}

View File

@@ -0,0 +1,753 @@
---
name: axiom-app-store-submission
description: Use when preparing ANY app for App Store submission, responding to App Review rejections, or running a pre-submission audit. Covers privacy manifests, metadata requirements, IAP review, account deletion, SIWA, age ratings, export compliance, first-time developer setup.
license: MIT
metadata:
version: "1.0.0"
---
# App Store Submission
## Overview
Systematic pre-flight checklist that catches 90% of App Store rejection causes before submission. **Core principle**: Ship once, ship right. Over 40% of App Store rejections cite Guideline 2.1 (App Completeness) — crashes, placeholders, broken links. Another 30% are metadata and privacy issues. A disciplined pre-flight process eliminates these preventable rejections.
**Key insight**: Apple rejected nearly 1.93 million submissions in 2024. Most rejections are not policy disagreements — they are checklist failures. A 30-minute pre-flight saves 3-7 days of rejection-fix-resubmit cycles.
## When to Use This Skill
**Use this skill when**:
- Preparing to submit an app or update to the App Store
- Submitting your first app as a new developer
- Responding to an App Review rejection
- Running a pre-submission audit before TestFlight or production
- Updating an existing app after a long gap (new requirements may apply)
- Wondering "is my app ready to submit?"
**Do NOT use this skill for**:
- Code signing and provisioning profiles (use build-debugging)
- CI/CD pipeline setup
- Performance optimization (use ios-performance)
- UI testing automation (use ui-testing)
- In-app purchase implementation (use storekit-ref)
- Privacy manifest details (use privacy-ux for deep implementation)
## Example Prompts
Real questions developers ask that this skill answers:
#### 1. "Is my app ready to submit to the App Store?"
> The skill provides a complete pre-flight checklist covering build, privacy, metadata, accounts, review info, content, and regional requirements
#### 2. "What do I need before submitting my first iOS app?"
> The skill walks through every requirement from scratch — privacy manifest, metadata fields, screenshots, demo credentials, age rating
#### 3. "I keep getting rejected, what am I missing?"
> The skill provides anti-patterns with specific rejection causes and the decision tree to identify gaps
#### 4. "What's the pre-submission checklist for App Store?"
> The skill provides a categorized mandatory checklist with every item that triggers rejection if missing
#### 5. "Do I need a privacy manifest?"
> Yes. Since May 2024, missing privacy manifests cause automatic rejection. The skill explains when and how.
#### 6. "My app update was rejected for metadata issues"
> The skill covers metadata completeness requirements and the common gaps that trigger Guideline 2.3 rejections
---
## Anti-Patterns
### 1. Submitting without device testing
**Time cost**: 3-7 days (rejection + fix + resubmit wait)
**Symptom**: Rejection for Guideline 2.1 — App Completeness. Crashes, broken flows, or missing functionality discovered by App Review.
**BAD**: Test only in Simulator, submit when build succeeds
```
"It works in Simulator, ship it"
→ Rejection: App crashes on launch on iPhone 15 Pro (memory limit)
→ 3-7 day delay
```
**GOOD**: Test on physical device with latest shipping OS, exercise all user flows
```bash
# Build for device
xcodebuild -scheme YourApp \
-destination 'platform=iOS,name=Your iPhone'
# Test critical paths:
# - Launch → main screen loads
# - All tabs/screens accessible
# - Core user flows complete without crash
# - Edge cases: no network, low storage, interruptions
```
**Why it works**: Simulator hides real-device constraints — memory limits, cellular networking behavior, hardware-specific APIs, thermal throttling. App Review tests on physical devices.
---
### 2. Missing or inadequate privacy policy
**Time cost**: 2-5 days (rejection + policy creation + resubmit)
**Symptom**: Rejection for Guideline 5.1.1(i) — Data Collection and Storage. Privacy policy missing, inaccessible, or inconsistent with actual data practices.
**BAD**: No privacy policy URL, or a generic template that doesn't match actual data collection
```
Privacy Policy URL: (empty)
— or —
Privacy Policy: "We respect your privacy" (generic, no specifics)
```
**GOOD**: Privacy policy accessible in two places, specific to your app's data practices
```
1. App Store Connect → App Information → Privacy Policy URL
2. In-app → Settings/About screen → Privacy Policy link
3. Policy content lists:
- All collected data types
- How each type is used
- Third-party sharing (who and why)
- Data retention period
- How to request deletion
```
**Three-way consistency**: Apple compares (a) your app's actual behavior, (b) your privacy policy content, and (c) your Privacy Nutrition Labels in ASC. All three must agree. If any of these three disagree, you get a 5.1.1 rejection. Check each SDK's documentation for its privacy manifest and data collection disclosure — your app's total data collection is your code PLUS all SDK data collection.
**Why it works**: Guideline 5.1.1(i) requires privacy policy accessible BOTH in ASC metadata AND within the app. The policy must specifically describe your app's data practices, not a generic template.
---
### 3. Placeholder content left in build
**Time cost**: 3-5 days (rejection + content replacement + resubmit)
**Symptom**: Rejection for Guideline 2.1 — App Completeness. Reviewers find placeholder text, empty screens, or TODO artifacts.
**BAD**: Ship with development artifacts visible to users
```
- "Lorem ipsum" text in onboarding
- Empty tab that shows "Coming Soon"
- Button that opens alert "Not implemented yet"
- Default app icon (white grid)
```
**GOOD**: Every screen has final content and production assets
```
Pre-submission content audit:
- [ ] Every screen has real content (no lorem ipsum)
- [ ] All images are final production assets
- [ ] No "Coming Soon" or "Under Construction" screens
- [ ] All buttons perform their intended action
- [ ] Default/empty states have proper messaging
- [ ] App icon is final and meets spec (1024x1024, no alpha)
```
**Why it works**: App Review tests every screen and tab, including states you might consider edge cases. They open every menu, tap every button, and switch every tab.
---
### 4. Ignoring privacy manifest
**Time cost**: 1-3 days (automatic rejection + manifest creation + resubmit)
**Symptom**: Automatic rejection before human review. Missing `PrivacyInfo.xcprivacy` or undeclared Required Reason APIs.
**BAD**: No privacy manifest, or missing Required Reason API declarations
```
No PrivacyInfo.xcprivacy in project
— or —
Using UserDefaults, file timestamps, disk space APIs
without declaring approved reasons
```
**GOOD**: Privacy manifest present with all Required Reason APIs declared
```
Project must contain:
├── PrivacyInfo.xcprivacy
│ ├── NSPrivacyTracking (true/false)
│ ├── NSPrivacyTrackingDomains (if tracking)
│ ├── NSPrivacyCollectedDataTypes (all types)
│ └── NSPrivacyAccessedAPITypes (all Required Reason APIs)
└── Third-party SDK manifests (each SDK includes its own)
```
Common Required Reason APIs that need declaration:
- `UserDefaults` → Reason `CA92.1`
- File timestamp APIs → Reason `C617.1`
- Disk space APIs → Reason `E174.1`
- System boot time → Reason `35F9.1`
**Why it works**: Since May 2024, this is an automated gate. No human reviewer involved — the build processing system rejects submissions missing required privacy declarations.
---
### 5. Missing Sign in with Apple
**Time cost**: 3-7 days (SIWA implementation + resubmit)
**Symptom**: Rejection for Guideline 4.8. App offers third-party login (Google, Facebook, email) but no Sign in with Apple option.
**BAD**: Third-party login without SIWA
```swift
// Login screen offers:
// - Sign in with Google
// - Sign in with Facebook
// - Email/password
// Missing: Sign in with Apple
```
**GOOD**: SIWA offered as equivalent option alongside any third-party login
```swift
// Login screen offers:
// - Sign in with Apple Required if others exist
// - Sign in with Google
// - Sign in with Facebook
// - Email/password
```
**Exceptions** (SIWA not required):
- Company-internal or employee-only apps
- Education apps using existing school accounts
- Government/tax/banking apps requiring government ID
- Apps that are a client for a specific third-party service
- Apps using only the company's own authentication system
**Why it works**: Guideline 4.8 requires SIWA as an option whenever ANY third-party or social login is offered. Apple enforces this strictly.
---
### 6. No account deletion flow
**Time cost**: 5-10 days (implementation + testing + resubmit)
**Symptom**: Rejection for Guideline 5.1.1(v). App allows account creation but provides no way to delete the account.
**BAD**: Account creation without deletion capability
```
- Sign up button exists
- No "Delete Account" anywhere in app
- "Contact support to delete" (not sufficient)
- "Deactivate account" (not the same as delete)
```
**GOOD**: Full account deletion flow accessible in-app
```
Account deletion requirements:
1. Discoverable in Settings/Profile (not hidden)
2. Clearly labeled "Delete Account" (not "Deactivate")
3. Explains what deletion means (data removed, timeline)
4. Confirms completion to user
5. If Sign in with Apple used → revoke SIWA token
6. If active subscriptions → inform user to cancel first
7. Deletion completes within reasonable timeframe (days, not months)
```
```swift
// Revoking Sign in with Apple token (required)
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
// After user confirms deletion:
// POST to Apple's revoke endpoint with the user's token
// Then delete server-side account data
```
**Why it works**: Required since June 2022. Must be actual deletion (not deactivation), must be in-app (not just email/website), and must revoke SIWA tokens if used. Apple tests this flow specifically.
---
### 7. Wrong age rating
**Time cost**: 2-4 days (re-answer questionnaire + possible content changes + resubmit)
**Symptom**: Rejection for Guideline 2.3.6 — Inaccurate metadata. Age rating doesn't reflect actual app content or capabilities.
**BAD**: Understate content to get lower rating
```
App has user-generated content (chat, posts)
but age rating questionnaire answered "No UGC"
App has cartoon violence in gameplay
but answered "No violence"
```
**GOOD**: Answer age rating questionnaire accurately
```
Declare honestly:
- User-generated content (chat, forums, social features)
- Violence (even cartoon/fantasy)
- Mature themes
- Profanity / crude humor
- Gambling (simulated or real)
- Horror / fear themes
- Medical / treatment information
- Unrestricted web access (WebView with open URLs)
```
**Updated age ratings (effective January 31, 2026)**: Apple expanded from 4+/9+/12+/17+ to 5 tiers (4+/9+/13+/16+/18+) with new capability declarations for messaging, UGC, advertising, and parental controls. All developers must have completed the updated questionnaire — app updates are blocked without it.
**Why it works**: Mismatched ratings violate Guideline 2.3.6. Apple compares your questionnaire answers against observed app behavior. UGC and web access are the most commonly missed declarations.
---
### 8. Missing demo credentials
**Time cost**: 3-5 days (rejection + credential creation + resubmit wait)
**Symptom**: Rejection for Guideline 2.1. Reviewer unable to test app because login is required and no test account was provided.
**BAD**: App requires login, but no demo account in review notes
```
App Review Information:
Notes: (empty)
Demo Account: (empty)
Demo Password: (empty)
→ Reviewer sees login screen, can't proceed, rejects
```
**GOOD**: Working demo credentials with clear instructions
```
App Review Information:
Demo Account: demo@yourapp.com
Demo Password: AppReview2025!
Notes: "Log in with the demo account above.
The account has sample data pre-loaded.
To test [feature X], navigate to Tab 2 > Settings.
If 2FA is required, use code: 123456"
Requirements:
- Account must not expire during review (1-2 weeks minimum)
- Account must have representative data
- Include any special setup steps
- If hardware required, explain workarounds
- If location-specific, provide test coordinates
```
**Why it works**: Reviewers cannot test what they cannot access. They will not create their own account. If your app requires any form of authentication, demo credentials are mandatory. This is one of the most common rejection reasons for apps with login flows.
---
## Decision Tree
```
Is my app ready to submit?
├─ Does it crash on a real device?
│ ├─ YES → STOP. Fix crashes first (Guideline 2.1)
│ └─ NO → Continue
├─ Privacy manifest (PrivacyInfo.xcprivacy) present?
│ ├─ NO → Add privacy manifest with Required Reason APIs
│ └─ YES → Continue
├─ Privacy policy URL set in App Store Connect?
│ ├─ NO → Add privacy policy URL in ASC
│ └─ YES → Is it also accessible in-app?
│ ├─ NO → Add in-app privacy policy link
│ └─ YES → Continue
├─ All screenshots final and matching current app?
│ ├─ NO → Update screenshots for all required device sizes
│ └─ YES → Continue
├─ Does app create user accounts?
│ ├─ YES → Account deletion implemented and discoverable?
│ │ ├─ NO → Implement account deletion flow
│ │ └─ YES → Continue
│ └─ NO → Continue
├─ Does app offer third-party login (Google, Facebook, etc.)?
│ ├─ YES → Sign in with Apple offered?
│ │ ├─ NO → Add SIWA (unless exemption applies)
│ │ └─ YES → Continue
│ └─ NO → Continue
├─ Does app have in-app purchases or subscriptions?
│ ├─ YES → IAP items submitted for review in ASC?
│ │ ├─ NO → Submit IAP for review (can be reviewed separately)
│ │ └─ YES → Restore Purchases button implemented?
│ │ ├─ NO → Add Restore Purchases functionality
│ │ └─ YES → Continue
│ └─ NO → Continue
├─ Does app use encryption beyond standard HTTPS?
│ ├─ YES → Export compliance documentation uploaded?
│ │ ├─ NO → Add ITSAppUsesNonExemptEncryption to Info.plist
│ │ │ and upload compliance documentation
│ │ └─ YES → Continue
│ └─ NO → Set ITSAppUsesNonExemptEncryption = NO in Info.plist
├─ Distributing in EU?
│ ├─ YES → DSA trader status verified in ASC?
│ │ ├─ NO → Complete trader verification in ASC
│ │ └─ YES → Continue
│ └─ NO → Continue
├─ Does app require login to function?
│ ├─ YES → Demo credentials in App Review notes?
│ │ ├─ NO → Add working demo account + password + instructions
│ │ └─ YES → Continue
│ └─ NO → Continue
├─ Age rating questionnaire completed honestly?
│ ├─ NO → Complete updated questionnaire (new 13+/16+/18+ ratings)
│ └─ YES → Continue
├─ Any placeholder content remaining?
│ ├─ YES → Replace all placeholders with final content
│ └─ NO → Continue
└─ All checks passed → READY TO SUBMIT
```
---
## Mandatory Pre-Flight Checklist
Run this entire checklist before every submission. Check every item, not just the ones you think apply.
### 1. Build Configuration
- [ ] Built with current required SDK (iOS 18 SDK / Xcode 16 as of 2025; iOS 26 SDK / Xcode 26 required starting April 28, 2026)
- [ ] `ITSAppUsesNonExemptEncryption` set in Info.plist (`NO` if only HTTPS)
- [ ] App tested on physical device with latest shipping iOS version
- [ ] App works over IPv6-only network (Apple review network is IPv6)
- [ ] No private/undocumented API usage
- [ ] No references to pre-release/beta OS features unless targeting that OS
- [ ] Minimum deployment target is reasonable (not unnecessarily high)
- [ ] Release build tested (not just Debug configuration)
### 2. Privacy
- [ ] `PrivacyInfo.xcprivacy` file present in app target
- [ ] All Required Reason APIs declared with approved reason codes
- [ ] Third-party SDKs each include their own privacy manifests
- [ ] Privacy policy URL set in App Store Connect
- [ ] Privacy policy accessible in-app (Settings/About screen)
- [ ] Privacy policy content matches actual data collection practices
- [ ] All `NS*UsageDescription` purpose strings present (Camera, Location, etc.)
- [ ] Purpose strings explain user benefit (not just "we need access")
- [ ] App Tracking Transparency implemented if tracking users (ATT)
- [ ] Privacy Nutrition Labels completed in ASC matching manifest
### 3. Metadata
- [ ] App name (30 character limit, no keyword stuffing)
- [ ] Subtitle (30 character limit)
- [ ] Description (accurate, no misleading claims)
- [ ] Keywords (100 character limit, comma-separated)
- [ ] Category and secondary category set
- [ ] Screenshots for all required device sizes (6.9", 6.7", 6.5", 5.5" for iPhone; 13" for iPad if universal)
- [ ] Screenshots reflect current app UI (not outdated)
- [ ] App Preview videos current (if using)
- [ ] Support URL valid and accessible
- [ ] Marketing URL valid (if set)
- [ ] Copyright string current year
- [ ] Version number and build number incremented
- [ ] "What's New" text written (for updates)
### 4. Account and Authentication
- [ ] Account deletion flow implemented (if account creation exists)
- [ ] Account deletion is actual deletion (not just deactivation)
- [ ] SIWA token revocation on account deletion (if SIWA used)
- [ ] Sign in with Apple offered (if any third-party login exists)
- [ ] Active subscriptions handled during account deletion
- [ ] Restore Purchases button works (if IAP exists)
- [ ] IAP items submitted for review in ASC (if new/changed)
- [ ] Subscription terms clearly communicated before purchase
### 5. App Review Information
- [ ] Contact information (name, phone, email) provided
- [ ] Demo account username provided (if login required)
- [ ] Demo account password provided (if login required)
- [ ] Demo account won't expire during review period (1-2 weeks)
- [ ] Demo account has representative sample data
- [ ] Special instructions for hardware-dependent features
- [ ] Notes explain any non-obvious features or flows
- [ ] If app uses location, provide test coordinates
### 6. Content Completeness
- [ ] No placeholder text (lorem ipsum, TODO, "Coming Soon")
- [ ] No broken links or dead-end screens
- [ ] All images are production assets (no stock watermarks)
- [ ] App icon meets spec (1024x1024, no alpha channel, no rounded corners)
- [ ] All tabs/screens have functional content
- [ ] Error states and empty states have proper messaging
- [ ] Onboarding/tutorial flows complete and accurate
- [ ] All deep links and universal links resolve correctly
### 7. Regional and Compliance
- [ ] EU DSA trader status verified (if distributing in EU)
- [ ] Age rating questionnaire completed with updated categories (13+/16+/18+)
- [ ] Age rating reflects actual content (UGC, violence, web access declared)
- [ ] Export compliance documentation uploaded (if non-exempt encryption)
- [ ] Content complies with local laws for each distribution territory
- [ ] GDPR compliance (if distributing in EU)
### 8. New for 2025-2026
- [ ] Updated age rating questionnaire completed (required since January 31, 2026)
- [ ] Accessibility Nutrition Labels declared (becoming required for new submissions)
- [ ] External AI service consent modal (if app sends personal data to external AI)
- [ ] SDK minimum version met (Xcode 16/iOS 18 SDK now; Xcode 26/iOS 26 SDK starting April 28, 2026)
---
## Pressure Scenarios
### Scenario 1: "Ship by end of day"
**Setup**: PM says the app must be submitted today for a marketing launch next week.
**Pressure**: Deadline + executive visibility
**Rationalization traps**:
- "We'll fix the privacy policy after approval"
- "The placeholder is only on one screen, they won't notice"
- "We'll add account deletion in the next update"
- "It passed internal testing, no need for device testing"
**MANDATORY**: Run the full pre-flight checklist. Every item. Missing items cause rejection, which costs 3-7 MORE days — far worse than the 30 minutes the checklist takes.
Skipping the checklist to save 30 minutes costs 3-7 days when it causes rejection.
**Communication template**: "The pre-flight check found [N] issues that will cause rejection. Fixing them takes [X hours]. Submitting without fixing guarantees rejection, which costs 3-7 days minimum. Let me fix these now — it's the fastest path to being live."
---
### Scenario 2: "Third rejection, just make it work"
**Setup**: App rejected 3 times for different issues each time. Developer is frustrated and tempted to cut corners or argue with Apple.
**Pressure**: Frustration + sunk cost + temptation to appeal instead of fix
**Rationalization traps**:
- "They keep finding new issues — they're being unfair"
- "I'll appeal this one, it's unreasonable"
- "I'll just hide that screen from reviewers"
**MANDATORY**: Read the FULL text of every rejection message. Run the complete pre-flight checklist from scratch. Reviewers often find new issues on subsequent reviews because they test deeper each pass — they explore screens they didn't reach before, test flows they skipped, and review with stricter attention.
Each rejection is a signal that the pre-submission process has gaps. Do not fight the feedback — absorb it and close the gaps systematically.
**Communication template**: "Multiple rejections mean we have systematic gaps, not bad luck. I'm running the complete pre-flight checklist — this takes 30 minutes but prevents the 3-7 day cycle of partial fixes followed by new rejections."
---
### Scenario 3: "It's just a bug fix update"
**Setup**: Simple one-line bug fix. Developer assumes the update will sail through because the app was already approved.
**Pressure**: Complacency + false confidence from prior approval
**Rationalization traps**:
- "It was approved last time with the same metadata"
- "I only changed one file, they don't need to re-review everything"
- "They won't re-check the privacy stuff, it hasn't changed"
**MANDATORY**: Updates are reviewed against CURRENT guidelines. Requirements change between releases. Privacy manifests became mandatory mid-cycle. Age rating questionnaire was overhauled. SDK minimums increase annually. A bug fix update can be rejected for issues that didn't exist when the previous version was approved.
Run the pre-flight checklist every time. Requirements that didn't exist when your app was last reviewed may now be enforced.
**Communication template**: "Even for a bug fix, App Review applies current guidelines — not the ones from when we were last approved. The privacy manifest requirement and age rating overhaul both came mid-cycle. Running the 30-minute pre-flight now prevents a surprise rejection."
---
## Screenshot Requirements
Screenshots must match current app UI. See `app-store-ref` Part 1 for required sizes, dimensions, and rules.
---
## Handling Rejections
### Reading the Rejection Message
Every rejection includes:
1. **Guideline number** — Specific section violated
2. **Description** — What the reviewer found
3. **Screenshots/recordings** — Visual evidence (if applicable)
4. **Suggestions** — Sometimes included with fixes
### Response Strategy
```
1. Read the FULL rejection message (every word)
2. Identify ALL guidelines cited (may be multiple)
3. Fix EVERY cited issue (not just the first one)
4. Run the complete pre-flight checklist
5. In your resubmission notes, explain what you fixed
6. Do NOT argue or explain why you think the rejection is wrong
```
### When to Appeal
Appeals are appropriate when:
- You believe the reviewer misunderstood your app's functionality
- Your app clearly complies with the cited guideline
- You have evidence supporting your position
Appeals are NOT appropriate when:
- You disagree with the guideline itself
- The rejection is technically correct but feels unfair
- You want to delay fixing the issue
### Appeal Process
```
App Store Connect → Resolution Center → Reply
- Explain clearly why you believe the rejection is incorrect
- Provide specific evidence (screenshots, documentation)
- Remain professional and factual
- Apple's App Review Board will re-review
- One appeal per rejection
- Must respond to all information requests BEFORE appealing
```
### Expedited Review
Two eligible scenarios:
1. **Critical Bug Fix** — include steps to reproduce the bug on the current live version
2. **Event-Related App** — include event name, date, and your association with the event
### Communication Options (Often Overlooked)
- **"Meet with App Review" sessions** — Apple periodically offers 30-minute appointments; check developer.apple.com/events for availability (not always open)
- **Bug fix leniency** — Apple may sometimes approve a bug fix submission with informational notes about additional non-legal/safety issues to address in the next update, rather than blocking the release. This is not guaranteed but happens in practice.
- **App Store Connect mobile app** for status tracking on the go
### Metadata Rejected vs Binary Rejected
| Type | What it means | What to do |
|------|--------------|------------|
| Metadata Rejected | Screenshots, description, or ASC fields need fixing | Fix in ASC, resubmit (no new build needed) |
| Binary Rejected | Code/app issue needs fixing | Fix code, create new archive, upload new build |
---
## In-App Purchase Review
IAP items require separate review. Missing or broken IAP is a top rejection cause under Guideline 3.1.1.
### IAP Submission Checklist
- [ ] All IAP products created in ASC with complete metadata
- [ ] **IAP review screenshot uploaded** for each product (shows what user sees when purchasing — review-only, not displayed on App Store)
- [ ] **IAP products attached to THIS version** (first submission only: App Version page → In-App Purchases section → Select → checkbox each product). After first approval, subsequent IAPs can be submitted independently.
- [ ] IAP products submitted for review
- [ ] Restore Purchases button visible and functional
- [ ] Subscription terms displayed before purchase confirmation (price, period, auto-renewal)
- [ ] **Terms of Use and Privacy Policy links visible on the purchase screen** (required by Schedule 2 of the DPLA, referenced by Guideline 3.1.2(c)). `SubscriptionStoreView` handles this automatically from ASC metadata — if using custom paywall UI, add links manually.
- [ ] Free trial terms clearly communicated
- [ ] Pricing displayed matches ASC configuration
- [ ] No external purchase links (Guideline 3.1.1) unless eligible for entitlement
- [ ] StoreKit testing completed in sandbox environment
### Common IAP Rejection Patterns
```
❌ "Buy Premium" button that does nothing → Guideline 3.1.1
❌ No Restore Purchases option → Guideline 3.1.1
❌ Subscription auto-renews without clear disclosure → Guideline 3.1.2
❌ Free trial duration not shown before purchase → Guideline 3.1.2
❌ External purchase link without entitlement → Guideline 3.1.1
❌ IAP products not attached to submission version → Guideline 2.1 (reviewer can't see them)
❌ No review screenshot on IAP product → Guideline 2.1 (reviewer can't verify purchase UI)
❌ Terms of Use / Privacy Policy missing from paywall → DPLA Schedule 2 (use SubscriptionStoreView to avoid)
```
---
## Common Rejection Reasons Quick Reference
| Guideline | Issue | Prevention |
|-----------|-------|------------|
| 2.1 | Crashes, broken features, incomplete | Device testing, content audit |
| 2.1 | IAP not visible to reviewer | Attach IAP to version (checkbox in ASC), upload review screenshots |
| 2.3 | Inaccurate metadata, wrong screenshots | Screenshot audit, metadata review |
| 2.3.6 | Incorrect age rating | Honest questionnaire, declare UGC |
| 3.1.1 | IAP issues, missing Restore Purchases | Test all IAP flows, add restore |
| 4.0 | Design: poor UI, non-standard patterns | Follow HIG, test on all sizes |
| 4.8 | Missing Sign in with Apple | Add SIWA with any third-party login |
| 5.1.1(i) | Privacy policy missing/inadequate | Both ASC and in-app, specific content |
| 5.1.1(v) | No account deletion | In-app deletion, not just deactivation |
| 5.1.2 | Missing Required Reason APIs | Complete privacy manifest |
---
## App Store Connect Submission Workflow
See `app-store-ref` Part 9 for the ASC upload workflow and build processing details.
---
## Encryption Export Compliance
Most apps need `ITSAppUsesNonExemptEncryption = false`. See `app-store-ref` Part 5 for the full decision tree.
---
## Accessibility Nutrition Labels (New 2025)
Accessibility Nutrition Labels are becoming required for new submissions. See `app-store-ref` Part 10 for the full label list and declaration rules. Run `axiom-accessibility-diag` before declaring.
---
## First-Time Developer Checklist
For developers submitting their first app, these are additional items often missed. If you need to create a privacy policy from scratch, use a privacy policy generator (many free options exist) and customize it to match your app's actual data practices — a generic template will be rejected.
### Apple Developer Program
- [ ] Enrolled in Apple Developer Program ($99/year)
- [ ] Accepted latest Apple Developer Program License Agreement
- [ ] Tax and banking information completed in ASC (for paid apps/IAP)
- [ ] Distribution certificate created
- [ ] App ID registered with correct bundle identifier
- [ ] Provisioning profile created for App Store distribution
### App Store Connect Setup
- [ ] New app record created in ASC
- [ ] Bundle ID matches Xcode project exactly
- [ ] Primary language set
- [ ] Content rights declared (original content or licensed)
- [ ] Pricing and availability configured
- [ ] Territory selection (worldwide or specific countries)
### Common First-Time Mistakes
| Mistake | Result | Fix |
|---------|--------|-----|
| Bundle ID mismatch between Xcode and ASC | Upload rejected | Match exactly, including case |
| Distribution cert expired/missing | Archive fails to upload | Create new cert in Developer Portal |
| License agreement not accepted | Upload blocked | Accept in developer.apple.com |
| Tax forms incomplete | Paid app not distributed | Complete in ASC → Agreements, Tax, and Banking |
| Wrong team selected in Xcode | Signing errors | Select correct team in Signing & Capabilities |
---
## Real-World Impact
**Before**: Developer submits without checklist → rejected for missing privacy manifest (3 days) → fixes, resubmits → rejected for missing SIWA (5 days) → fixes, resubmits → rejected for placeholder content (3 days) → 11 days lost to preventable issues
**After**: Developer runs 30-minute pre-flight → catches all three issues → fixes in 4 hours → approved on first submission
**Key insight**: The checklist takes 30 minutes. Each rejection cycle takes 3-7 days. The math is simple.
---
## Resources
**WWDC**: 2022-10166, 2025-328, 2025-224, 2025-241
**Docs**: /app-store/review/guidelines, /app-store/submitting, /app-store/app-privacy-details, /support/offering-account-deletion-in-your-app, /documentation/security/complying-with-encryption-export-regulations
**Skills**: axiom-privacy-ux, axiom-storekit-ref, axiom-accessibility-diag, axiom-testflight-triage, axiom-app-store-connect-ref

View File

@@ -0,0 +1,3 @@
interface:
display_name: "App Store Submission"
short_description: "Preparing ANY app for App Store submission, responding to App Review rejections, or running a pre-submission audit"

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-apple-docs-research",
"installedAt": "2026-04-12T08:05:48.717Z"
}

View File

@@ -0,0 +1,327 @@
---
name: axiom-apple-docs-research
description: Use when researching Apple frameworks, APIs, or WWDC sessions - provides techniques for retrieving full transcripts, code samples, and documentation using Chrome browser and sosumi.ai
license: MIT
metadata:
version: "1.0.0"
---
# Apple Documentation Research
## When to Use This Skill
**Use this skill when**:
- Researching Apple frameworks or APIs (WidgetKit, SwiftUI, etc.)
- Need full WWDC session transcripts with code samples
- Looking for Apple Developer documentation
- Want to extract code examples from WWDC presentations
- Building comprehensive skills based on Apple technologies
**Do NOT use this skill for**:
- Third-party framework documentation
- General web research
- Questions already answered in existing skills
- Basic Swift language questions (use Swift documentation)
## Related Skills
- Use **superpowers-chrome:browsing** for interactive browser control
- Use **writing-skills** when creating new skills from Apple documentation
- Use **reviewing-reference-skills** to validate Apple documentation skills
## Core Philosophy
> Apple Developer video pages contain full verbatim transcripts with timestamps and complete code samples. Chrome's auto-capture feature makes this content instantly accessible without manual copying.
**Key insight**: Don't manually transcribe or copy code from WWDC videos. The transcripts are already on the page, fully timestamped and formatted.
## WWDC Session Transcripts via Chrome
### The Technique
Apple Developer video pages (`developer.apple.com/videos/play/wwdc20XX/XXXXX/`) contain complete transcripts that Chrome auto-captures.
#### Step-by-Step Process
1. **Navigate** using Chrome browser MCP tool:
```json
{
"action": "navigate",
"payload": "https://developer.apple.com/videos/play/wwdc2025/278/"
}
```
Tool name: `mcp__plugin_superpowers-chrome_chrome__use_browser`
**Complete invocation**:
```
Use the mcp__plugin_superpowers-chrome_chrome__use_browser tool with:
- action: "navigate"
- payload: "https://developer.apple.com/videos/play/wwdc2025/278/"
```
2. **Locate** the auto-captured file:
- Chrome saves to: `~/.../superpowers/browser/YYYY-MM-DD/session-TIMESTAMP/`
- Session directory uses Unix timestamp in milliseconds (e.g., `session-1765217804099`)
- Filename pattern: `NNN-navigate.md` (e.g., `001-navigate.md`)
**Finding the latest session**:
```bash
# List sessions sorted by modification time (newest first)
ls -lt ~/Library/Caches/superpowers/browser/*/session-* | head -5
```
3. **Read** the captured transcript:
- Full spoken content with timestamps (e.g., `[0:07]`, `[1:23]`)
- Descriptions of code and API usage (spoken, not formatted)
- Chapter markers and resource links
### What You Get
**✅ WWDC transcripts contain:**
- Full spoken content with timestamps (e.g., `[0:07]`, `[1:23]`)
- API names mentioned by speakers (e.g., `widgetRenderingMode`, `supportedMountingStyles`)
- Descriptions of what code does ("I'll add the widgetRenderingMode environment variable")
- Step-by-step explanations of implementations
- Chapter markers and resource links
**❌ WWDC transcripts do NOT contain:**
- Formatted Swift code blocks ready to copy-paste
- Complete implementations
- Structured code examples
**Critical Understanding**: Transcripts are **spoken word, not code**. You'll read sentences like "I'll add the widgetRenderingMode environment variable to my widget view" and need to **reconstruct the code yourself** from these descriptions.
### When Code Isn't Clear from Transcript
If the transcript's code descriptions aren't detailed enough, follow this fallback workflow:
1. **Check Resources Tab**
- Navigate back to the WWDC session page
- Click "Resources" tab
- Look for "Download Sample Code" or "View on GitHub"
- Download Xcode project with complete working implementation
2. **Use sosumi.ai for API Details**
- Look up specific APIs mentioned in transcript
- Example: Transcript says "widgetAccentedRenderingMode" → look up `sosumi.ai/documentation/swiftui/widgetaccentedrenderingmode`
- Get exact signature, parameters, usage
3. **Jump to Timestamp in Video**
- Use transcript timestamp to jump directly to code explanation in video
- Example: Transcript says code at `[4:23]` → watch that specific 30-second segment
- Faster than watching entire 45-minute session
4. **Combine Sources**
- Transcript = conceptual understanding + workflow
- Resources = complete code
- sosumi.ai = API details
- Result: Full picture without manually reconstructing everything
**Example transcript structure**:
```markdown
# Session Title - WWDC## - Videos - Apple Developer
## Chapters
- 0:00 - Introduction
- 1:23 - Key Topic 1
## Transcript
0:00
Speaker: Welcome to this session...
[timestamp]
Now I'll add the widgetAccentedRenderingMode modifier...
```
### Example Session
**WWDC 2025-278** "What's new in widgets":
- Navigate: `https://developer.apple.com/videos/play/wwdc2025/278/`
- Captured: `001-navigate.md`
- Contains: ~15 minutes of full transcript with API references and code concepts
## Apple Documentation via sosumi.ai
### Why sosumi.ai
Developer.apple.com documentation is HTML-heavy and difficult to parse. sosumi.ai provides the same content in clean markdown format.
### URL Pattern
**Instead of**:
```
https://developer.apple.com/documentation/widgetkit
```
**Use**:
```
https://sosumi.ai/documentation/widgetkit
```
### URL Pattern Rules
**Format**: `https://sosumi.ai/documentation/[framework]`
**Rules for framework name**:
1. **Lowercase** - Use lowercase even if framework is capitalized (SwiftUI → swiftui)
2. **No spaces** - Remove all spaces (Core Data → coredata)
3. **No hyphens** - Remove all hyphens (App Intents → appintents, NOT app-intents)
4. **Case-insensitive** - Both `SwiftUI` and `swiftui` work, but lowercase is recommended
**Common mistakes**:
- ❌ `app-intents` → ✅ `appintents`
- ❌ `axiom-core-data` → ✅ `coredata`
- ❌ `AVFoundation` → ✅ `avfoundation`
**Examples**:
| Framework Name | sosumi.ai URL |
|----------------|---------------|
| SwiftUI | `sosumi.ai/documentation/swiftui` |
| App Intents | `sosumi.ai/documentation/appintents` |
| Core Data | `sosumi.ai/documentation/coredata` |
| AVFoundation | `sosumi.ai/documentation/avfoundation` |
| UIKit | `sosumi.ai/documentation/uikit` |
### Using with WebFetch or Read Tools
```
WebFetch:
url: https://sosumi.ai/documentation/widgetkit/widget
prompt: "Extract information about Widget protocol"
Result: Clean markdown with API signatures, descriptions, examples
```
### Framework Examples
| Framework | sosumi.ai URL |
|-----------|---------------|
| WidgetKit | `https://sosumi.ai/documentation/widgetkit` |
| SwiftUI | `https://sosumi.ai/documentation/swiftui` |
| ActivityKit | `https://sosumi.ai/documentation/activitykit` |
| App Intents | `https://sosumi.ai/documentation/appintents` |
| Foundation | `https://sosumi.ai/documentation/foundation` |
## Common Research Workflows
### Workflow 1: New iOS Feature Research
**Goal**: Create a comprehensive skill for a new iOS 26 feature.
1. **Find WWDC sessions** — Search "WWDC 2025 [feature name]"
2. **Get transcripts** — Navigate with Chrome to each session
3. **Read transcripts** — Extract key concepts, code patterns, gotchas
4. **Get API docs** — Use sosumi.ai for framework reference
5. **Cross-reference** — Verify code samples match documentation
6. **Create skill** — Combine transcript insights + API reference
**Time saved**: 3-4 hours vs. watching videos and manual transcription
### Workflow 2: API Deep Dive
**Goal**: Understand a specific API or protocol.
1. **sosumi.ai docs** — Get protocol/class definition
2. **WWDC sessions** — Search for sessions mentioning the API
3. **Code samples** — Extract from transcript code blocks
4. **Verify patterns** — Ensure examples match latest API
### Workflow 3: Multiple Sessions Research
**Goal**: Comprehensive coverage across multiple years (e.g., widgets evolution).
1. **Parallel navigation** — Use Chrome to visit 3-6 sessions
2. **Read all transcripts** — Compare how APIs evolved
3. **Extract timeline** — iOS 14 → 17 → 18 → 26 changes
4. **Consolidate** — Create unified skill with version annotations
**Example**: Extensions & Widgets skill used 6 WWDC sessions (2023-2025)
## Anti-Patterns
### ❌ DON'T: Manual Video Watching
```
BAD:
1. Play WWDC video
2. Pause and take notes
3. Rewind to capture code
4. Type out examples manually
Result: 45 minutes per session
```
### ✅ DO: Chrome Auto-Capture
```
GOOD:
1. Navigate with Chrome
2. Read captured .md file
3. Copy code blocks directly
4. Reference timestamps for context
Result: 5 minutes per session
```
### ❌ DON'T: Scrape developer.apple.com HTML
```
BAD:
Use WebFetch on developer.apple.com/documentation
Result: Complex HTML parsing required
```
### ✅ DO: Use sosumi.ai
```
GOOD:
Use WebFetch on sosumi.ai/documentation
Result: Clean markdown, instant access
```
## Troubleshooting
### Chrome Session Directory Not Found
**Symptom**: Can't locate `001-navigate.md` file
**Solution**:
1. Check Chrome actually navigated (look for URL confirmation)
2. Find latest session: `ls -lt ~/Library/Caches/superpowers/browser/*/`
3. Session directory format: `YYYY-MM-DD/session-TIMESTAMP/`
### Transcript Incomplete
**Symptom**: File exists but missing transcript
**Solution**:
1. Page may still be loading - wait 2-3 seconds
2. Try navigating again
3. Some sessions require scrolling to load full content
### sosumi.ai Returns Error
**Symptom**: 404 or invalid URL
**Solution**:
1. Verify framework name spelling
2. Check sosumi.ai format: `/documentation/[frameworkname]`
3. Fallback: Use developer.apple.com but expect HTML
## Verification Checklist
Before using captured content:
- ☐ Transcript includes timestamps
- ☐ Code samples are complete (not truncated)
- ☐ Speaker names and chapter markers present
- ☐ Multiple speakers properly attributed
- ☐ Code syntax highlighting preserved
## Resources
**Skills**: superpowers-chrome:browsing, writing-skills, reviewing-reference-skills
---
**Time Saved**: Using this technique saves 30-40 minutes per WWDC session vs. manual video watching and transcription. For comprehensive research spanning multiple sessions, savings compound to 3-4 hours per skill.

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Apple Docs Research"
short_description: "Researching Apple frameworks, APIs, or WWDC sessions"

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": ".claude-plugin/plugins/axiom/skills/axiom-apple-docs",
"installedAt": "2026-04-12T08:05:35.591Z"
}

View File

@@ -0,0 +1,156 @@
---
name: axiom-apple-docs
description: Use when ANY question involves Apple framework APIs, Swift compiler errors, or Xcode-bundled documentation. Covers Liquid Glass, Swift 6.2 concurrency, Foundation Models, SwiftData, StoreKit, 32 Swift compiler diagnostics.
license: MIT
---
# Apple Documentation Router
Apple bundles for-LLM markdown documentation inside Xcode. These are authoritative, up-to-date guides and diagnostics written by Apple engineers. Use them alongside Axiom skills for the most accurate information.
## When to Use
Use Apple's bundled docs when:
- You need the exact API signature or behavior from Apple
- Axiom skills reference an Apple framework and you want the official source
- A Swift compiler diagnostic needs explanation
- The user asks about a specific Apple framework feature
**Priority**: Axiom skills provide opinionated guidance (decision trees, anti-patterns, pressure scenarios). Apple docs provide authoritative API details. Use both together.
## Guide Topics (AdditionalDocumentation)
Read these with the MCP `axiom_read_skill` tool using the skill name.
### UI & Design
| Topic | Skill Name |
|-------|-----------|
| Liquid Glass in SwiftUI | `apple-guide-swiftui-implementing-liquid-glass-design` |
| Liquid Glass in UIKit | `apple-guide-uikit-implementing-liquid-glass-design` |
| Liquid Glass in AppKit | `apple-guide-appkit-implementing-liquid-glass-design` |
| Liquid Glass in WidgetKit | `apple-guide-widgetkit-implementing-liquid-glass-design` |
| SwiftUI toolbar features | `apple-guide-swiftui-new-toolbar-features` |
| SwiftUI styled text editing | `apple-guide-swiftui-styled-text-editing` |
| SwiftUI WebKit integration | `apple-guide-swiftui-webkit-integration` |
| SwiftUI AlarmKit integration | `apple-guide-swiftui-alarmkit-integration` |
| Swift Charts 3D visualization | `apple-guide-swift-charts-3d-visualization` |
| Foundation AttributedString | `apple-guide-foundation-attributedstring-updates` |
### Data & Persistence
| Topic | Skill Name |
|-------|-----------|
| SwiftData class inheritance | `apple-guide-swiftdata-class-inheritance` |
### Concurrency & Performance
| Topic | Skill Name |
|-------|-----------|
| Swift concurrency updates | `apple-guide-swift-concurrency-updates` |
| InlineArray and Span | `apple-guide-swift-inlinearray-span` |
### Apple Intelligence
| Topic | Skill Name |
|-------|-----------|
| Foundation Models (on-device LLM) | `apple-guide-foundationmodels-using-on-device-llm-in-your-app` |
### System Integration
| Topic | Skill Name |
|-------|-----------|
| App Intents updates | `apple-guide-appintents-updates` |
| StoreKit updates | `apple-guide-storekit-updates` |
| MapKit GeoToolbox | `apple-guide-mapkit-geotoolbox-placedescriptors` |
| Widgets for visionOS | `apple-guide-widgets-for-visionos` |
### Accessibility
| Topic | Skill Name |
|-------|-----------|
| Assistive Access in iOS | `apple-guide-implementing-assistive-access-in-ios` |
### Computer Vision
| Topic | Skill Name |
|-------|-----------|
| Visual Intelligence in iOS | `apple-guide-implementing-visual-intelligence-in-ios` |
## Swift Compiler Diagnostics
These explain specific Swift compiler errors and warnings with examples and fixes.
### Concurrency Diagnostics
| Diagnostic | Skill Name |
|-----------|-----------|
| Actor-isolated call from nonisolated context | `apple-diag-actor-isolated-call` |
| Conformance isolation | `apple-diag-conformance-isolation` |
| Isolated conformances | `apple-diag-isolated-conformances` |
| Nonisolated nonsending by default | `apple-diag-nonisolated-nonsending-by-default` |
| Sendable closure captures | `apple-diag-sendable-closure-captures` |
| Sendable metatypes | `apple-diag-sendable-metatypes` |
| Sending closure risks data race | `apple-diag-sending-closure-risks-data-race` |
| Sending risks data race | `apple-diag-sending-risks-data-race` |
| Mutable global variable | `apple-diag-mutable-global-variable` |
| Preconcurrency import | `apple-diag-preconcurrency-import` |
### Type System Diagnostics
| Diagnostic | Skill Name |
|-----------|-----------|
| Existential any | `apple-diag-existential-any` |
| Existential member access limitations | `apple-diag-existential-member-access-limitations` |
| Nominal types | `apple-diag-nominal-types` |
| Multiple inheritance | `apple-diag-multiple-inheritance` |
| Protocol type non-conformance | `apple-diag-protocol-type-non-conformance` |
| Opaque type inference | `apple-diag-opaque-type-inference` |
### Build & Migration Diagnostics
| Diagnostic | Skill Name |
|-----------|-----------|
| Deprecated declaration | `apple-diag-deprecated-declaration` |
| Error in future Swift version | `apple-diag-error-in-future-swift-version` |
| Strict language features | `apple-diag-strict-language-features` |
| Strict memory safety | `apple-diag-strict-memory-safety` |
| Implementation only deprecated | `apple-diag-implementation-only-deprecated` |
| Member import visibility | `apple-diag-member-import-visibility` |
| Missing module on known paths | `apple-diag-missing-module-on-known-paths` |
| Clang declaration import | `apple-diag-clang-declaration-import` |
| Availability unrecognized name | `apple-diag-availability-unrecognized-name` |
| Unknown warning group | `apple-diag-unknown-warning-group` |
### Swift Language Diagnostics
| Diagnostic | Skill Name |
|-----------|-----------|
| Dynamic callable requirements | `apple-diag-dynamic-callable-requirements` |
| Property wrapper requirements | `apple-diag-property-wrapper-requirements` |
| Result builder methods | `apple-diag-result-builder-methods` |
| String interpolation conformance | `apple-diag-string-interpolation-conformance` |
| Trailing closure matching | `apple-diag-trailing-closure-matching` |
| Temporary pointers | `apple-diag-temporary-pointers` |
## Routing Decision Tree
```
User question about Apple API/framework?
├── Specific compiler error/warning → Read matching apple-diag-* skill
├── Liquid Glass implementation → Read apple-guide-*-liquid-glass-design (SwiftUI/UIKit/AppKit)
├── Swift concurrency patterns → Read apple-guide-swift-concurrency-updates
├── Foundation Models / on-device AI → Read apple-guide-foundationmodels-*
├── SwiftData features → Read apple-guide-swiftdata-*
├── StoreKit / IAP → Read apple-guide-storekit-updates
├── App Intents / Siri → Read apple-guide-appintents-updates
├── Charts / visualization → Read apple-guide-swift-charts-3d-visualization
├── Text editing / AttributedString → Read apple-guide-swiftui-styled-text-editing or apple-guide-foundation-attributedstring-updates
├── WebKit in SwiftUI → Read apple-guide-swiftui-webkit-integration
├── Toolbar features → Read apple-guide-swiftui-new-toolbar-features
└── Other → Search with axiom_search_skills using source filter "apple"
```
## Resources
**Skills**: axiom-ios-ui, axiom-ios-concurrency, axiom-ios-data, axiom-ios-ai, axiom-ios-integration

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-asc-mcp",
"installedAt": "2026-04-12T08:05:49.297Z"
}

View File

@@ -0,0 +1,303 @@
---
name: axiom-asc-mcp
description: Use when automating App Store Connect via MCP — submit builds, manage TestFlight, respond to reviews, triage feedback programmatically
license: MIT
---
# App Store Connect MCP Integration
**Core principle**: When asc-mcp is configured, you can manage the entire App Store Connect workflow without leaving Claude Code — submit builds, distribute to TestFlight, respond to reviews, and monitor metrics programmatically.
## Setup
### Install
```bash
brew install mint
mint install zelentsov-dev/asc-mcp@1.4.0
```
### Create API Key
1. Open [App Store Connect → Users and Access → Integrations → API](https://appstoreconnect.apple.com/access/integrations/api)
2. Generate key with **Admin** or **App Manager** role
3. Download the `.p8` file (one-time download — save it securely)
4. Note the **Key ID** and **Issuer ID**
### Add to Claude Code
```bash
claude mcp add asc-mcp \
-e ASC_KEY_ID=YOUR_KEY_ID \
-e ASC_ISSUER_ID=YOUR_ISSUER_ID \
-e ASC_PRIVATE_KEY_PATH=/path/to/AuthKey.p8 \
-- ~/.mint/bin/asc-mcp
```
### Verify
Ask Claude to call `company_current`. If it returns your team name, you're connected.
---
## Worker Filtering
asc-mcp has 25 workers (~208 tools). Loading all of them wastes context. Use `--workers` to load only what you need.
### Presets
| Preset | Workers | Tools | Use Case |
|--------|---------|-------|----------|
| **TestFlight** | `apps,builds,beta_groups,beta_testers` | ~34 | Beta distribution |
| **Release** | `apps,builds,versions,reviews` | ~40 | App Store submission |
| **Monetization** | `apps,iap,subscriptions,offer_codes,pricing` | ~55 | IAP and subscriptions |
| **Full** | (default, all workers) | ~208 | Everything |
To use a preset, add `--workers` when registering the server:
```bash
claude mcp add asc-mcp \
-e ASC_KEY_ID=... \
-e ASC_ISSUER_ID=... \
-e ASC_PRIVATE_KEY_PATH=... \
-- ~/.mint/bin/asc-mcp --workers apps,builds,versions,reviews
```
**Note**: `company` and `auth` workers always load regardless of `--workers`. When `builds` is enabled, `build_processing` and `build_beta` are included automatically.
### Worker Selection Decision Tree
```dot
digraph workers {
"What are you doing?" [shape=diamond];
"TestFlight preset" [shape=box, label="--workers apps,builds,\nbeta_groups,beta_testers"];
"Release preset" [shape=box, label="--workers apps,builds,\nversions,reviews"];
"Monetization preset" [shape=box, label="--workers apps,iap,\nsubscriptions,offer_codes,pricing"];
"Full (no flag)" [shape=box, label="No --workers flag\n(all 208 tools)"];
"What are you doing?" -> "TestFlight preset" [label="distributing beta builds"];
"What are you doing?" -> "Release preset" [label="submitting to App Store"];
"What are you doing?" -> "Monetization preset" [label="managing IAP/subscriptions"];
"What are you doing?" -> "Full (no flag)" [label="multiple tasks or unsure"];
}
```
---
## Workflow: Release Pipeline
Submit a new version to the App Store.
```
1. apps_search(query: "MyApp") → get app ID
2. builds_list(appId, limit: 5) → find latest processed build
3. app_versions_create(appId, platform: "IOS", versionString: "2.1.0")
4. app_versions_attach_build(versionId, buildId)
5. app_versions_set_review_details(versionId, { contactEmail, notes, ... })
6. app_versions_submit_for_review(versionId)
7. (After approval) app_versions_create_phased_release(versionId)
```
**Before step 3**: Version string must not already exist. Check with `app_versions_list`.
**Before step 4**: Build must be in `VALID` processing state. Check with `builds_get_processing_state`.
**Before step 6**: Version must be in `PREPARE_FOR_SUBMISSION` state. Attaching a build and setting review details are prerequisites.
---
## Workflow: TestFlight Distribution
Distribute a build to beta testers.
```
1. apps_search(query: "MyApp") → get app ID
2. builds_list(appId, limit: 5) → find latest build
3. builds_set_beta_localization(buildId, locale: "en-US", whatsNew: "Bug fixes")
4. beta_groups_list(appId) → find or create group
OR beta_groups_create(appId, name: "Internal Testers", isInternal: true)
5. beta_groups_add_builds(groupId, [buildId])
6. builds_send_beta_notification(buildId) → notify testers (optional)
```
**Tip**: Internal testers (up to 100) get builds immediately. External testers (up to 10,000) require Beta App Review for the first build of each version.
---
## Workflow: Review Management
Monitor and respond to App Store reviews.
```
1. apps_search(query: "MyApp") → get app ID
2. reviews_list(appId, sort: "-createdDate", limit: 20)
OR reviews_list(appId, filterRating: "1,2") → negative reviews only
3. reviews_stats(appId) → rating distribution summary
4. reviews_create_response(reviewId, responseBody: "Thank you for...")
```
**Response guidelines**:
- Respond to negative reviews within 24-48 hours
- Be professional and offer specific help
- Updating a response replaces the previous one (users see "Developer Response")
---
## Workflow: Feedback Triage
Correlate TestFlight feedback with build diagnostics.
```
1. builds_list(appId, limit: 10) → recent builds
2. builds_get_beta_testers(buildId) → who tested this build
3. metrics_build_diagnostics(buildId) → crash signatures for this build
4. metrics_get_diagnostic_logs(signatureId) → individual crash logs
```
**Limitation**: TestFlight text feedback and screenshots are NOT available via the App Store Connect API. Use Xcode Organizer or the ASC web dashboard for feedback content.
---
## Workflow: Multi-Company
Switch between App Store Connect teams.
```
1. company_list → see configured accounts
2. company_switch(companyId: "client-a") → switch active account
3. (All subsequent calls use client-a's credentials)
```
### Multi-Company Setup
Add to `~/.config/asc-mcp/companies.json`:
```json
{
"companies": [
{
"id": "my-company",
"name": "My Company",
"key_id": "KEY_ID_1",
"issuer_id": "ISSUER_1",
"key_path": "/Users/you/.keys/AuthKey1.p8"
},
{
"id": "client-a",
"name": "Client A",
"key_id": "KEY_ID_2",
"issuer_id": "ISSUER_2",
"key_path": "/Users/you/.keys/AuthKey2.p8"
}
]
}
```
Or use numbered environment variables: `ASC_COMPANY_1_NAME`, `ASC_COMPANY_1_KEY_ID`, etc.
---
## Key Tool Quick Reference
### Apps & Builds
| Tool | Parameters | Returns |
|------|-----------|---------|
| `apps_search` | `query` | App ID, name, bundleId, platform |
| `apps_list` | `limit` | All apps in account |
| `builds_list` | `appId`, `limit`, `sort` | Build number, version, processing state |
| `builds_find_by_number` | `appId`, `buildNumber` | Specific build details |
| `builds_get_processing_state` | `buildId` | PROCESSING, VALID, INVALID, etc. |
| `builds_check_readiness` | `buildId` | Whether build is ready for distribution |
### Versions & Submission
| Tool | Parameters | Returns |
|------|-----------|---------|
| `app_versions_create` | `appId`, `platform`, `versionString` | New version in PREPARE_FOR_SUBMISSION |
| `app_versions_attach_build` | `versionId`, `buildId` | Attached build |
| `app_versions_set_review_details` | `versionId`, `contactEmail`, `notes`, etc. | Review details |
| `app_versions_submit_for_review` | `versionId` | Submitted for review |
| `app_versions_cancel_review` | `versionId` | Cancelled review |
| `app_versions_release` | `versionId` | Manual release |
| `app_versions_create_phased_release` | `versionId` | 7-day phased rollout |
### TestFlight
| Tool | Parameters | Returns |
|------|-----------|---------|
| `beta_groups_create` | `appId`, `name`, `isInternal` | New group |
| `beta_groups_add_testers` | `groupId`, `testerIds` | Updated group |
| `beta_groups_add_builds` | `groupId`, `buildIds` | Build distributed |
| `builds_set_beta_localization` | `buildId`, `locale`, `whatsNew` | "What to Test" text |
| `builds_send_beta_notification` | `buildId` | Testers notified |
### Reviews & Metrics
| Tool | Parameters | Returns |
|------|-----------|---------|
| `reviews_list` | `appId`, `sort`, `filterRating`, `limit` | Review entries |
| `reviews_stats` | `appId` | Rating distribution |
| `reviews_create_response` | `reviewId`, `responseBody` | Developer response |
| `metrics_app_perf` | `appId` | App-level performance metrics |
| `metrics_build_diagnostics` | `buildId` | Crash signatures per build |
---
## API Constraints
| Constraint | Details |
|-----------|---------|
| **No emoji in metadata** | Version "What's New", descriptions, keywords — use words, not emoji |
| **Version state** | Only `PREPARE_FOR_SUBMISSION` versions are editable. Once submitted, create a new version to make changes. |
| **JWT lifetime** | 20-minute tokens, auto-refreshed by asc-mcp |
| **Rate limits** | Apple enforces per-account limits. asc-mcp retries with exponential backoff on 429s. |
| **Locale format** | Standard codes: `en-US`, `ja`, `de-DE`, `zh-Hans`, `ru` |
| **Build processing** | Newly uploaded builds take 15-30 minutes to process. Poll `builds_get_processing_state` before attaching. |
| **Phased release** | Only available after approval. Can pause/resume with `app_versions_update_phased_release`. |
---
## Gotchas
| Gotcha | Details |
|--------|---------|
| **Build not found** | Build may still be processing. Check `builds_get_processing_state` — must be `VALID`. |
| **"Version already exists"** | Can't create duplicate version strings. Use `app_versions_list` to check first. |
| **Attach fails** | Build must be processed AND the version must be in `PREPARE_FOR_SUBMISSION`. |
| **Review details rejected** | Contact info fields have format requirements. Email must be valid, phone must include country code. |
| **Wrong app** | `apps_search` is fuzzy. Verify the returned bundleId matches your target app. |
| **Multi-company confusion** | Always call `company_current` first to confirm which account is active before making changes. |
| **Beta App Review** | First external TestFlight build per version requires review. Subsequent builds to the same version are auto-approved. |
| **Missing analytics** | `analytics_*` tools require `vendor_number` in company config. Without it, sales/financial reports fail silently. |
---
## Anti-Rationalization
| Thought | Reality |
|---------|---------|
| "I'll just use the ASC web dashboard" | MCP tools are faster for repetitive tasks — respond to 20 reviews, distribute to 5 groups, create versions across apps. |
| "I don't need worker filtering" | 208 tools consume ~30K tokens of context. Filter to what you need. |
| "I'll submit without review details" | Submission will fail. `app_versions_set_review_details` is required before `app_versions_submit_for_review`. |
| "I'll skip `company_current` — I only have one account" | Multi-company configs persist between sessions. Always verify. |
| "Feedback triage is the same via API" | Text feedback and screenshots are NOT in the ASC API. Use Organizer for feedback content, MCP for crash diagnostics. |
---
## When asc-mcp is NOT Available
If asc-mcp is not configured, fall back to manual workflows:
- **Crash analysis**: Use Xcode Organizer (see `axiom-testflight-triage`) or App Store Connect web dashboard (see `axiom-app-store-connect-ref`)
- **TestFlight distribution**: Use Xcode → Product → Archive → Distribute, or `xcodebuild` + `altool`
- **Review management**: Use App Store Connect web dashboard
- **Submission**: Use Xcode → Product → Archive → Distribute to App Store
---
## Resources
**Skills**: axiom-app-store-submission, axiom-app-store-ref, axiom-app-store-connect-ref, axiom-testflight-triage
**Agents**: crash-analyzer, security-privacy-scanner, iap-auditor

View File

@@ -0,0 +1,3 @@
interface:
display_name: "ASC MCP"
short_description: "Automating App Store Connect via MCP"

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-assume-isolated",
"installedAt": "2026-04-12T08:05:49.835Z"
}

View File

@@ -0,0 +1,233 @@
---
name: axiom-assume-isolated
description: Use when needing synchronous actor access in tests, legacy delegate callbacks, or performance-critical code. Covers MainActor.assumeIsolated, @preconcurrency protocol conformances, crash behavior, Task vs assumeIsolated.
license: MIT
metadata:
version: "1.0.0"
---
# assumeIsolated — Synchronous Actor Access
Synchronously access actor-isolated state when you **know** you're already on the correct isolation domain.
## When to Use
**Use when:**
- Testing MainActor code synchronously (avoiding Task overhead)
- Legacy delegate callbacks documented to run on main thread
- Performance-critical code avoiding async hop overhead
- Protocol conformances where callbacks are guaranteed on specific actor
**Don't use when:**
- Uncertain about current isolation (use `await` instead)
- Already in async context (you have isolation)
- Cross-actor calls needed (use async)
- Callback origin is unknown or untrusted
## API Reference
### MainActor.assumeIsolated
```swift
static func assumeIsolated<T>(
_ operation: @MainActor () throws -> T,
file: StaticString = #fileID,
line: UInt = #line
) rethrows -> T where T: Sendable
```
**Behavior**: Executes synchronously. **Crashes** if not on MainActor's serial executor.
### Custom Actor assumeIsolated
```swift
func assumeIsolated<T>(
_ operation: (isolated Self) throws -> T,
file: StaticString = #fileID,
line: UInt = #line
) rethrows -> T where T: Sendable
```
## Task vs assumeIsolated
| Aspect | `Task { @MainActor in }` | `MainActor.assumeIsolated` |
|--------|--------------------------|---------------------------|
| Timing | Deferred (next run loop) | Synchronous (inline) |
| Async support | Yes (can await) | No (sync only) |
| Context | From any context | Must be sync function |
| Failure mode | Runs anyway | **Crashes** if wrong isolation |
| Use case | Start async work | Verify + access isolated state |
## Patterns
### Pattern 1: Testing MainActor Code
```swift
@Test func viewModelUpdates() {
MainActor.assumeIsolated {
let vm = ViewModel()
vm.update()
#expect(vm.state == .updated)
}
}
```
### Pattern 2: Legacy Delegate Callbacks
From WWDC 2024-10169 — When documentation guarantees main thread delivery:
```swift
@MainActor
class LocationDelegate: NSObject, CLLocationManagerDelegate {
var location: CLLocation?
// CLLocationManager created on main thread delivers callbacks on main thread
nonisolated func locationManager(
_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]
) {
MainActor.assumeIsolated {
self.location = locations.last
}
}
}
```
### Pattern 3: @preconcurrency Shorthand
`@preconcurrency` is equivalent shorthand — wraps in `assumeIsolated` automatically:
```swift
// Manual approach (verbose)
extension MyClass: SomeDelegate {
nonisolated func callback() {
MainActor.assumeIsolated {
self.updateUI()
}
}
}
// Using @preconcurrency (equivalent, cleaner)
extension MyClass: @preconcurrency SomeDelegate {
func callback() {
self.updateUI() // Compiler wraps in assumeIsolated
}
}
```
**When protocol adds isolation**: `@preconcurrency` becomes unnecessary and compiler warns.
### Pattern 4: Thread Check Before assumeIsolated
When caller context is unknown (e.g., library code):
```swift
func getView() -> UIView {
if Thread.isMainThread {
return createHostingViewOnMain()
} else {
return DispatchQueue.main.sync {
createHostingViewOnMain()
}
}
}
private func createHostingViewOnMain() -> UIView {
MainActor.assumeIsolated {
let hosting = UIHostingController(rootView: MyView())
return hosting.view
}
}
```
### Pattern 5: Custom Actor Access
```swift
actor DataStore {
var cache: [String: Data] = [:]
nonisolated func synchronousRead(key: String) -> Data? {
// Only safe if called from DataStore's executor
assumeIsolated { isolated in
isolated.cache[key]
}
}
}
```
## Common Mistakes
### Mistake 1: Silencing Compiler Errors
```swift
// DANGEROUS: Using assumeIsolated to silence warnings
func unknownContext() {
MainActor.assumeIsolated {
updateUI() // Crashes if not actually on main actor!
}
}
// When uncertain, use proper async
func unknownContext() async {
await MainActor.run {
updateUI()
}
}
```
### Mistake 2: Assuming GCD Main Queue == MainActor
They're **usually** the same, but not guaranteed. Check documentation or use async.
### Mistake 3: Using in Async Context
```swift
// Unnecessary you already have isolation
@MainActor
func updateState() async {
MainActor.assumeIsolated { // Pointless
self.state = .ready
}
}
// Direct access
@MainActor
func updateState() async {
self.state = .ready
}
```
## When @preconcurrency Becomes Unnecessary
If the protocol later adds MainActor isolation:
```swift
// Library update:
@MainActor
protocol CaffeineThresholdDelegate: AnyObject {
func caffeineLevel(at level: Double)
}
// Your code @preconcurrency now warns:
// "@preconcurrency attribute on conformance has no effect"
extension Recaffeinater: CaffeineThresholdDelegate {
func caffeineLevel(at level: Double) {
// Direct access, no wrapper needed
}
}
```
## Crash Behavior
Per Apple documentation:
> "If the current context is not running on the actor's serial executor... this method will crash with a fatal error."
**Trapping is intentional**: Better to crash than corrupt user data with a race condition.
## Resources
**WWDC**: 2024-10169
**Docs**: /swift/mainactor/assumeisolated, /swift/actor/assumeisolated
**Skills**: axiom-swift-concurrency

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Assume Isolated"
short_description: "Needing synchronous actor access in tests, legacy delegate callbacks, or performance-critical code"

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-accessibility",
"installedAt": "2026-04-12T08:05:49.836Z"
}

View File

@@ -0,0 +1,252 @@
---
name: axiom-audit-accessibility
description: Use when the user mentions accessibility checking, App Store submission, code review, or WCAG compliance.
license: MIT
disable-model-invocation: true
---
# Accessibility Auditor Agent
You are an expert at detecting accessibility violations — both known anti-patterns AND missing/incomplete assistive technology support that prevents users with disabilities from using the app and causes App Store rejections.
## Your Mission
Run a comprehensive accessibility audit using 5 phases: map the UI hierarchy and assistive technology surface, detect known violations, reason about what's unreachable or incomplete, correlate compound issues, and score accessibility health. Report all issues with:
- File:line references with confidence levels
- WCAG compliance levels
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Fix recommendations with code examples
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## Phase 1: Map UI Hierarchy and Assistive Technology Surface
Before grepping for violations, build a mental model of the app's UI and how assistive technologies would experience it.
### Step 1: Identify Interactive Surfaces
```
Glob: **/*.swift (excluding test/vendor paths)
Grep for:
- `Button`, `NavigationLink`, `Toggle`, `Picker`, `Slider` — standard interactive elements
- `.onTapGesture`, `.onLongPressGesture`, `DragGesture`, `MagnificationGesture` — gesture-based interactions
- `.swipeActions` — swipe actions (automatically VoiceOver-accessible)
- `UIButton`, `UISwitch`, `UISlider`, `addTarget` — UIKit interactive elements
```
### Step 2: Identify Content Surfaces
```
Grep for:
- `Image("` — custom images (need labels or accessibilityHidden)
- `AsyncImage(` — network images (need labels or accessibilityHidden)
- `Image(systemName:` — SF Symbols (auto-labeled, usually safe)
- `.font(.system(size:`, `UIFont.systemFont(ofSize:` — explicit font sizing
- `.custom(` — custom fonts
```
### Step 3: Identify Accessibility Configuration
Read 3-5 key view files to understand:
- Is there a consistent accessibility pattern? (labels, traits, hints)
- Are there custom controls? (custom gestures, drawn content)
- Is Dynamic Type supported? (@ScaledMetric, preferredFont, relativeTo)
- Are there accessibility-specific modifiers? (accessibilityElement, accessibilityChildren)
### Output
Write a brief **Accessibility Surface Map** (8-12 lines) summarizing:
- Interactive element types and count
- Gesture-based interactions (require manual accessibility support)
- Custom image count (need labels or hidden)
- Font sizing strategy (semantic vs fixed vs mixed)
- Existing accessibility configuration patterns
Present this map in the output before proceeding.
## Phase 2: Detect Known Anti-Patterns
Run all 8 existing detection categories. These are fast and reliable. For every grep match, use Read to verify the surrounding context before reporting — grep patterns have high recall but need contextual verification.
### 1. Missing VoiceOver Labels (CRITICAL — App Store Rejection Risk)
**Pattern**: Interactive elements and images without accessibility labels
**Search**: `Image("` without `accessibilityLabel` or `accessibilityHidden` in nearby lines; `Button` with only `systemName` without `accessibilityLabel`; `AsyncImage(` without `accessibilityLabel` or `accessibilityHidden`; `accessibilityLabel("Button")` or `accessibilityLabel("Image")` (generic labels)
**Issue**: VoiceOver users can't identify or interact with elements
**Fix**: Add descriptive `.accessibilityLabel("Add to cart")`
**Note**: `Image(systemName:)` auto-generates VoiceOver labels — don't flag
### 2. Fixed Font Sizes — Dynamic Type (HIGH)
**Pattern**: Hardcoded font sizes that won't scale with Dynamic Type
**Search**: `.font(.system(size:` without `relativeTo:`; `UIFont.systemFont(ofSize:` without UIFontMetrics; `UIFont(name:` without UIFontMetrics; `.withSize(` without UIFontMetrics
**Issue**: Text stays tiny when user enables larger text (WCAG 1.4.4)
**Fix**: Use `.font(.body)` or `.font(.system(size: 17, design: .default).relativeTo(.body))`
**Note**: Before flagging `.system(size: variable)`, check if the variable is `@ScaledMetric` — already scales
### 3. Custom Font Scaling (HIGH)
**Pattern**: Custom fonts without scaling support
**Search**: `UIFont(name:` without UIFontMetrics; `UIFont(descriptor:` without UIFontMetrics; `.custom(` without `relativeTo:`
**Issue**: Custom fonts ignore Dynamic Type settings (WCAG 1.4.4)
**Fix**: UIKit: `UIFontMetrics(forTextStyle: .body).scaledFont(for: customFont)`. SwiftUI: `.custom("FontName", size: X, relativeTo: .body)`
### 4. Layout Scaling (MEDIUM)
**Pattern**: Fixed padding/spacing that doesn't scale with Dynamic Type
**Search**: Check for `@ScaledMetric` usage, `scaledValue` usage. Absence of both with fixed padding constants indicates issue.
**Issue**: Layout doesn't adapt to larger text sizes (WCAG 1.4.4)
**Fix**: SwiftUI: `@ScaledMetric(relativeTo: .body) var spacing: CGFloat = 20`. UIKit: `UIFontMetrics(forTextStyle: .body).scaledValue(for: 20.0)`
### 5. Color Contrast (HIGH)
**Pattern**: Low contrast text/background combinations
**Search**: `.foregroundColor(.gray)`, `.foregroundStyle(.secondary)` on small text; custom color definitions with low contrast pairs; missing `accessibilityDifferentiateWithoutColor`
**Issue**: Text unreadable for low vision users (WCAG 1.4.3 — 4.5:1 for text, 3:1 for large text)
**Fix**: Use semantic colors, verify contrast ratios, add differentiation without color
### 6. Touch Target Sizes (MEDIUM)
**Pattern**: Interactive elements smaller than 44x44pt
**Search**: `.frame(` with width or height under 44 on buttons/tappable elements
**Issue**: Hard to tap for users with motor impairments (WCAG 2.5.5)
**Fix**: Use `.frame(minWidth: 44, minHeight: 44)` or increase contentShape
### 7. Reduce Motion Support (MEDIUM)
**Pattern**: Animations without Reduce Motion check
**Search**: `withAnimation` without `isReduceMotionEnabled` check; `.animation(` without motion check
**Issue**: Causes discomfort for users with vestibular disorders (WCAG 2.3.3)
**Fix**: Check `UIAccessibility.isReduceMotionEnabled` or use `.animation(.default, value:)` which respects Reduce Motion
### 8. Keyboard Navigation (MEDIUM — iPadOS/macOS)
**Pattern**: Missing keyboard shortcuts and focus management
**Search**: Missing `.keyboardShortcut` on primary actions; non-focusable interactive elements; missing `.focusable()` on custom controls
**Issue**: Keyboard-only users can't navigate (iPadOS with external keyboard, macOS)
**Fix**: Add keyboard shortcuts for primary actions, ensure focus traversal
## Phase 3: Reason About Accessibility Completeness
Using the Accessibility Surface Map from Phase 1 and your domain knowledge, check for what's *missing* — not just what's wrong.
| Question | What it detects | Why it matters |
|----------|----------------|----------------|
| Are there flows that are completely inaccessible via VoiceOver? (gesture-only interactions without accessibility equivalents) | Inaccessible critical paths | VoiceOver users can't complete core tasks — App Store rejection risk |
| Are there screens where the only way to perform an action is via a gesture (drag, long press, pinch) with no button alternative? | Gesture-only paths | Users who can't perform gestures (motor impairments, Switch Control) are blocked |
| Do custom-drawn views (Canvas, UIView with drawRect) expose their content to assistive technologies? | Hidden custom content | Custom rendering is invisible to VoiceOver unless manually exposed |
| Is there a consistent accessibility pattern across the app, or do some views have labels while others don't? | Inconsistent coverage | Partial accessibility is worse than none — users start trusting VoiceOver then hit a wall |
| Do modal flows (sheets, alerts, full-screen covers) properly manage VoiceOver focus? | Focus management gaps | VoiceOver focus stays on the background view instead of the presented modal |
| Are there information-conveying images that are marked as decorative (accessibilityHidden)? | Over-hidden content | Meaningful images hidden from VoiceOver users lose information |
| Does the app support the full range of Dynamic Type sizes (up to AX5) without layout breakage? | Partial Dynamic Type support | Users at accessibility text sizes get clipped/overlapping content |
For each finding, explain what's missing and why it matters. Require evidence from the Phase 1 map — don't speculate without reading the code.
## Phase 4: Cross-Reference Findings
When findings from different phases compound, the combined risk is higher than either alone. Bump the severity when you find these combinations:
| Finding A | + Finding B | = Compound | Severity |
|-----------|------------|-----------|----------|
| Gesture-only interaction | No accessibilityAction | Feature completely inaccessible | CRITICAL |
| Missing labels on buttons | In critical flow (purchase, auth) | Core transaction inaccessible | CRITICAL |
| Fixed font sizes | No @ScaledMetric for spacing | Completely ignores Dynamic Type | CRITICAL |
| Custom font without scaling | In main content area | Primary text doesn't scale | HIGH |
| Missing Reduce Motion | Looping/auto-play animation | Persistent discomfort trigger | HIGH |
| Small touch targets | In frequently used controls | Repeated frustration for motor-impaired users | HIGH |
| Missing labels | In list cells (repeated N times) | Entire list unusable for VoiceOver | HIGH |
| Inconsistent labeling | Some views labeled, others not | Users can't predict what's accessible | MEDIUM |
Also note overlaps with other auditors:
- Gesture-only + no accessibilityAction → compound with ux-flow-auditor
- Missing labels in navigation destinations → compound with swiftui-nav-auditor
- Dynamic Type + layout issues → compound with swiftui-layout-auditor
## Phase 5: Accessibility Health Score
Calculate and present a health score:
```markdown
## Accessibility Health Score
| Metric | Value |
|--------|-------|
| VoiceOver label coverage | N interactive elements, M with labels (Z%) |
| Dynamic Type support | Semantic fonts: N, Fixed fonts: M, Scaling coverage: Z% |
| Gesture accessibility | N gesture-based interactions, M with accessibilityAction equivalents (Z%) |
| WCAG Level A | N violations |
| WCAG Level AA | N violations |
| WCAG Level AAA | N violations |
| **Health** | **COMPLIANT / GAPS / NON-COMPLIANT** |
```
Scoring:
- **COMPLIANT**: No CRITICAL issues, 0 Level A violations, >90% VoiceOver label coverage, all gestures have accessibility equivalents
- **GAPS**: No CRITICAL issues, but Level A or AA violations present, or 70-90% label coverage, or some gesture-only paths
- **NON-COMPLIANT**: Any CRITICAL issues, or multiple Level A violations, or <70% label coverage, or critical flows inaccessible
## Output Format
```markdown
# Accessibility Audit Results
## Accessibility Surface Map
[8-12 line summary from Phase 1]
## Summary
- CRITICAL: [N] issues (App Store rejection risk)
- HIGH: [N] issues (Major usability impact)
- MEDIUM: [N] issues (Moderate usability impact)
- LOW: [N] issues (Best practices)
- Phase 2 (anti-pattern detection): [N] issues
- Phase 3 (completeness reasoning): [N] issues
- Phase 4 (compound findings): [N] issues
## Accessibility Health Score
[Phase 5 table]
## Issues by Severity
### [SEVERITY/CONFIDENCE] [Category]: [Description]
**File**: path/to/file.swift:line
**Phase**: [2: Detection | 3: Completeness | 4: Compound]
**WCAG**: [guideline number and level]
**Issue**: What's wrong or missing
**Impact**: What users with disabilities experience
**Fix**: Code example showing the fix
**Cross-Auditor Notes**: [if overlapping with another auditor]
## Recommendations
1. [Immediate actions — CRITICAL fixes (App Store rejection risk)]
2. [Short-term — HIGH fixes (WCAG Level A/AA compliance)]
3. [Long-term — accessibility improvements from Phase 3 findings]
## Testing Checklist
- [ ] Test with VoiceOver (Cmd+F5 on simulator)
- [ ] Test with Dynamic Type at AX5 (Settings → Accessibility → Display & Text Size → Larger Text)
- [ ] Test with Reduce Motion (Settings → Accessibility → Motion → Reduce Motion)
- [ ] Test with external keyboard on iPad (Tab, arrow keys, Enter)
```
## Output Limits
If >50 issues in one category: Show top 10, provide total count, list top 3 files
If >100 total issues: Summarize by category, show only CRITICAL/HIGH details
## False Positives (Not Issues)
- Decorative images with `.accessibilityHidden(true)`
- Spacer views without labels
- Background images marked as decorative
- `.swipeActions` on List rows — automatically exposed via VoiceOver Actions rotor
- `.font(.system(size: variable))` where the variable is `@ScaledMetric`
- `Image(systemName:)` — auto-generates VoiceOver labels
- Static/singleton formatters (not in view body)
- `.animation(.default, value:)` — already respects Reduce Motion system setting
## Related
For comprehensive accessibility debugging: `axiom-accessibility-diag` skill
For Dynamic Type and typography: `axiom-typography-ref` skill
For UX flow accessibility: `axiom-ux-flow-audit` skill

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit Accessibility"
short_description: "The user mentions accessibility checking, App Store submission, code review, or WCAG compliance."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-camera",
"installedAt": "2026-04-12T08:05:49.837Z"
}

View File

@@ -0,0 +1,235 @@
---
name: axiom-audit-camera
description: Use this agent to scan Swift code for camera, video, and audio capture issues including deprecated APIs, missing interruption handlers, threading violations, and permission anti-patterns.
license: MIT
disable-model-invocation: true
---
# Camera & Capture Auditor Agent
You are an expert at detecting camera, video, and audio capture issues in iOS apps that cause freezes, poor UX, App Store rejections, and reliability problems.
## Your Mission
Run a comprehensive camera/capture audit and report all issues with:
- File:line references with confidence levels
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Specific fix recommendations
- Links to relevant skill patterns
## Files to Scan
Look for capture code in:
- `**/*.swift` - All Swift files
- Focus on files containing: `AVCaptureSession`, `AVCaptureDevice`, `AVCapturePhotoOutput`, `AVAudioSession`
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## What You Check
### 1. Main Thread Session Work (CRITICAL - UI Freezes)
**Pattern to find**:
```swift
// BAD: startRunning on main thread
session.startRunning() // Without being on session queue
```
**What to look for**:
- `startRunning()` or `stopRunning()` not wrapped in `DispatchQueue` async
- Missing `let sessionQueue = DispatchQueue(label:` pattern
- Session configuration without dedicated queue
**Fix**: Move all session work to dedicated serial queue
### 2. Deprecated videoOrientation API (HIGH - iOS 17+ Issues)
**Pattern to find**:
```swift
// DEPRECATED
connection.videoOrientation = .portrait
AVCaptureConnection.videoOrientation
```
**What to look for**:
- Any use of `videoOrientation` property
- Manual device orientation observation for camera
- Missing `RotationCoordinator`
**Fix**: Use `AVCaptureDevice.RotationCoordinator` (iOS 17+)
### 3. Missing Interruption Handling (HIGH - Camera Freezes)
**Pattern to find**:
```swift
// Missing observer for:
.AVCaptureSessionWasInterrupted
AVCaptureSession.interruptionEndedNotification
```
**What to look for**:
- Files with `AVCaptureSession` but no interruption notification observers
- No handling for phone calls, multitasking
- No UI feedback for interrupted state
**Fix**: Add observers for session interruption notifications
### 4. UIImagePickerController for Photo Selection (MEDIUM - Deprecated)
**Pattern to find**:
```swift
// DEPRECATED for photo selection
UIImagePickerController()
.sourceType = .photoLibrary
```
**What to look for**:
- `UIImagePickerController` with `photoLibrary` source type
- Should use `PHPickerViewController` or `PhotosPicker` instead
**Fix**: Replace with PHPicker (UIKit) or PhotosPicker (SwiftUI)
### 5. Over-Requesting Photo Library Access (MEDIUM - Privacy Issue)
**Pattern to find**:
```swift
// BAD: Requesting access just to pick photos
PHPhotoLibrary.requestAuthorization
PHPhotoLibrary.authorizationStatus
// Before showing PHPicker or PhotosPicker
```
**What to look for**:
- Permission requests when only using system pickers
- PHPicker/PhotosPicker don't need library permission
- Unnecessary privacy prompts
**Fix**: Remove permission requests if only using system pickers
### 6. Missing Photo Quality Settings (MEDIUM - Slow Capture)
**Pattern to find**:
```swift
// Missing quality prioritization
AVCapturePhotoSettings()
// Without setting photoQualityPrioritization
```
**What to look for**:
- `AVCapturePhotoSettings` without `photoQualityPrioritization`
- Default is often `.quality` which is slow
- Social/sharing apps should use `.speed` or `.balanced`
**Fix**: Set appropriate `photoQualityPrioritization`
### 7. AVAudioSession Category Mismatch (MEDIUM - Audio Issues)
**Pattern to find**:
```swift
// BAD: Wrong category for recording
.setCategory(.playback) // Can't record with this
.setCategory(.ambient) // Can't record with this
```
**What to look for**:
- Video recording code with non-recording audio category
- Should use `.playAndRecord` for video with audio
- Missing category configuration before recording
**Fix**: Set appropriate AVAudioSession category (`.playAndRecord` or `.record`)
### 8. Missing Purpose Strings (CRITICAL - App Store Rejection)
**What to check**:
- Look for camera/audio usage without corresponding Info.plist keys
- Required keys:
- `NSCameraUsageDescription` - For camera access
- `NSMicrophoneUsageDescription` - For audio recording
- `NSPhotoLibraryUsageDescription` - For photo library access
- `NSPhotoLibraryAddUsageDescription` - For saving photos
**Note**: You may not be able to check Info.plist directly, but flag when camera/audio code exists
### 9. Configuration Without Block (LOW - Race Conditions)
**Pattern to find**:
```swift
// BAD: Modifying session without configuration block
session.addInput(input)
session.addOutput(output)
// Without beginConfiguration/commitConfiguration
```
**What to look for**:
- `addInput` or `addOutput` without surrounding `beginConfiguration`/`commitConfiguration`
- Session modifications that could cause race conditions
**Fix**: Wrap session changes in `beginConfiguration()`/`commitConfiguration()`
### 10. Synchronous Photo Loading (LOW - UI Blocking)
**Pattern to find**:
```swift
// BAD: Blocking main thread
try! item.loadTransferable(type:) // Force try, no async
```
**What to look for**:
- Non-async Transferable loading
- `PHImageManager.requestImage` without async handling
- Image loading on main thread
**Fix**: Use async/await for all image loading
## Output Format
For each issue found:
```
## [SEVERITY] Issue Title
**File**: `path/to/File.swift:123`
**Confidence**: HIGH/MEDIUM/LOW
**What was found**:
```swift
// The problematic code
```
**Why it's a problem**:
Brief explanation of the issue
**Fix**:
```swift
// The corrected code
```
**See**: camera-capture skill, Pattern X
```
## Summary Section
After listing all issues, provide a summary:
```
## Audit Summary
- **CRITICAL**: X issues
- **HIGH**: X issues
- **MEDIUM**: X issues
- **LOW**: X issues
**Top priority fixes**:
1. [Most important issue]
2. [Second most important]
3. [Third most important]
```
## Related Skills
For detailed patterns and solutions, refer developers to:
- `axiom-camera-capture` - Session setup, rotation, interruption handling
- `axiom-camera-capture-diag` - Troubleshooting decision trees
- `axiom-camera-capture-ref` - API reference
- `axiom-photo-library` - Photo picker and library patterns

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit Camera"
short_description: "Use this agent to scan Swift code for camera, video, and audio capture issues including deprecated APIs, missing inte..."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-codable",
"installedAt": "2026-04-12T08:05:49.838Z"
}

View File

@@ -0,0 +1,362 @@
---
name: axiom-audit-codable
description: Use when the user mentions Codable review, JSON encoding/decoding issues, data serialization audit, or modernizing legacy code.
license: MIT
disable-model-invocation: true
---
# Codable Auditor Agent
You are an expert at detecting Codable anti-patterns and JSON serialization issues that cause silent data loss and production bugs.
## Your Mission
Run a comprehensive Codable audit and report all issues with:
- File:line references for easy fixing
- Severity ratings (HIGH/MEDIUM/LOW)
- Specific issue types (anti-patterns vs configuration issues)
- Fix recommendations with code examples
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## Output Limits
If >50 issues in one category:
- Show top 10 examples
- Provide total count
- List top 3 files with most issues
If >100 total issues:
- Summarize by category
- Show only HIGH details
- Always show: Severity counts, top 3 files by issue count
## What You Check
### High-Severity Anti-Patterns
#### 1. Manual JSON String Building (HIGH)
**Patterns to detect**:
```swift
// String interpolation with JSON
"\"{" or "'{\""
"\\\"" in string literals
// Common examples:
let json = "{\"key\": \"\(value)\"}"
let json = "{ \"name\": \"\(name)\", \"age\": \(age) }"
```
**Why it's bad**: Injection vulnerabilities, escaping bugs, no type safety
**Impact**: Production crashes, security vulnerabilities, data corruption
**Fix recommendation**:
```swift
// Manual string building
let json = "{\"name\": \"\(user.name)\", \"id\": \(user.id)}"
// Use JSONEncoder
struct UserPayload: Codable {
let name: String
let id: Int
}
let data = try JSONEncoder().encode(UserPayload(name: user.name, id: user.id))
```
#### 2. try? Swallowing DecodingError (HIGH)
**Patterns to detect**:
```swift
"try? JSONDecoder"
"try? decoder.decode"
"try? JSONEncoder"
"try? encoder.encode"
```
**Why it's bad**: Silent failures, debugging nightmares, data loss
**Impact**: Users lose data without knowing, impossible to debug in production
**Fix recommendation**:
```swift
// Silent failure
let user = try? JSONDecoder().decode(User.self, from: data)
// Explicit error handling
do {
let user = try JSONDecoder().decode(User.self, from: data)
} catch DecodingError.keyNotFound(let key, let context) {
logger.error("Missing key '\(key)' at path: \(context.codingPath)")
} catch {
logger.error("Failed to decode User: \(error)")
}
```
#### 3. String Interpolation in JSON (HIGH)
**Patterns to detect**:
```swift
// String interpolation with \(
"\\\(.*\)" in context with { or }
// Common patterns:
"\\(variable)"
```
**Why it's bad**: Escaping issues, injection, breaks on special characters
**Impact**: Production crashes when names contain quotes or backslashes
**Fix recommendation**: Use Codable types with JSONEncoder
### Medium-Severity Issues
#### 4. JSONSerialization Instead of Codable (MEDIUM)
**Patterns to detect**:
```swift
"JSONSerialization.jsonObject"
"JSONSerialization.data"
"NSJSONSerialization"
```
**Why it's bad**: Legacy pattern, manual type casting, error-prone
**Impact**: 3x more boilerplate, no type safety, harder to maintain
**Fix recommendation**:
```swift
// JSONSerialization
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let name = json?["name"] as? String
// Codable
struct User: Codable {
let name: String
}
let user = try JSONDecoder().decode(User.self, from: data)
```
#### 5. Date Without Explicit Strategy (MEDIUM)
**Patterns to detect**:
```swift
// Date property in Codable type
struct.*:.*Codable.*\n.*Date
// But no dateDecodingStrategy configuration in the file
// (check if file contains JSONDecoder but no dateDecodingStrategy)
```
**Why it's bad**: Timezone bugs, intermittent failures across regions
**Impact**: Data corruption, bugs only appear for users in different timezones
**Fix recommendation**:
```swift
// No strategy configured
let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: data)
// Explicit strategy
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601 // Or .secondsSince1970, etc.
let user = try decoder.decode(User.self, from: data)
```
#### 6. DateFormatter Without Locale/Timezone (MEDIUM)
**Patterns to detect**:
```swift
"DateFormatter()" without "locale" or "timeZone" in nearby lines
"DateFormatter.dateFormat" without "locale"
```
**Why it's bad**: Locale-dependent parsing failures
**Impact**: App breaks for users with non-US locale settings
**Fix recommendation**:
```swift
// No locale/timezone
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
// With locale and timezone
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
```
#### 7. Optional Properties to Avoid Decode Errors (MEDIUM)
**Pattern**: Look for optional properties with comments mentioning "decode", "fail", "error", "crash"
**Why it's bad**: Masks structural problems, runtime crashes, nil checks everywhere
**Impact**: Field is required but marked optional, leads to crashes later
**Fix recommendation**:
```swift
// Optional to avoid decode errors
struct User: Codable {
let id: UUID
let email: String? // Made optional because decoding was failing
}
// Fix root cause
// 1. Check if API structure changed (nested? renamed?)
// 2. Use CodingKeys to map to correct key
// 3. Use DecodableWithConfiguration if data comes from elsewhere
```
### Low-Severity Issues
#### 8. No Error Context in Catch Blocks (LOW)
**Patterns to detect**:
```swift
catch {
print("Failed") // No error variable
}
```
**Why it's bad**: No debugging information when things fail
**Impact**: Cannot diagnose production issues
**Fix recommendation**:
```swift
// No context
catch {
print("Failed to decode")
}
// Include error
catch {
print("Failed to decode: \(error)")
// Or use structured logging
logger.error("Decode failed", error: error)
}
```
## Audit Workflow
### Step 1: Find Swift Files
```
Use Glob: **/*.swift (apply Skip exclusions above)
```
### Step 2: Scan for Anti-Patterns
For each severity level:
**HIGH severity (fail fast)**:
1. Manual JSON building: `"\"{"`
2. try? with decoder: `"try? JSONDecoder"`, `"try? decoder.decode"`
3. String interpolation in JSON context
**MEDIUM severity**:
1. JSONSerialization: `"JSONSerialization"`, `"NSJSONSerialization"`
2. Date properties without strategy
3. DateFormatter without locale
4. Suspicious optionals (grep for comments mentioning decode/fail/error near optional Date/String properties)
**LOW severity**:
1. Empty catch blocks or print-only error handling
### Step 3: Read Context
For each match:
1. Read the file with context (-B 5 -A 5)
2. Determine if it's a true positive
3. Identify the specific issue type
4. Formulate fix recommendation
### Step 4: Generate Report
Format output as:
```markdown
# Codable Audit Results
## Summary
- Files scanned: [X]
- Total issues: [Y]
- HIGH: [Z]
- MEDIUM: [A]
- LOW: [B]
## 🔴 High Priority Issues ([count])
### Manual JSON String Building
- **file/path.swift:45** - Building JSON with string interpolation
```swift
let json = "{\"key\": \"\(value)\"}"
```
**Fix**: Use JSONEncoder with Codable type
**Impact**: Injection vulnerabilities, escaping bugs
### try? Swallowing Errors
- **file/path.swift:89** - Silent decode failure with try?
```swift
let user = try? decoder.decode(User.self, from: data)
```
**Fix**: Handle DecodingError cases explicitly
**Impact**: Silent data loss, impossible to debug
## 🟡 Medium Priority Issues ([count])
### JSONSerialization Usage
- **file/path.swift:112** - Using legacy JSONSerialization
**Fix**: Migrate to Codable
**Time saved**: Reduce boilerplate by 60%
### Date Handling
- **file/path.swift:134** - Date property without explicit strategy
**Fix**: Set decoder.dateDecodingStrategy = .iso8601
**Impact**: Prevents timezone bugs
## 🟢 Low Priority Issues ([count])
[List issues with file:line and brief description]
## Recommendations
1. **Immediate**: Fix all HIGH severity issues (silent failures, injection risks)
2. **This sprint**: Address MEDIUM severity (technical debt, potential bugs)
3. **Backlog**: Clean up LOW severity (code quality improvements)
## Quick Wins
[List 2-3 most impactful fixes that take <10 minutes each]
```
## Audit Guidelines
1. Focus on true positives - explain why including/excluding patterns in comments or tests
2. Provide context by showing surrounding code in reports
3. Give actionable fixes - show the correct pattern, not just "fix this"
4. Prioritize HIGH severity issues first - these cause production data loss
5. Be helpful with try? - suggest which DecodingError cases to handle
## Common False Positives
1. **String interpolation in logging**: `logger.debug("{...}")` - OK, not building actual JSON
2. **JSON in comments or documentation**: Ignore
3. **Test fixtures**: String JSON for test data is acceptable (but note it)
4. **try? for optional decoding**: If the optional is intentional, it's OK (but verify)
## If No Issues Found
```markdown
# Codable Audit Results
**No issues found**
Your codebase follows Codable best practices:
- No manual JSON string building
- Proper error handling (no try? swallowing errors)
- Using Codable instead of JSONSerialization
- [Any other positive findings]
Keep up the good work!
```
## Your Tone
- **Direct but helpful**: "This pattern causes silent data loss" not "This might be a problem"
- **Evidence-based**: Show the code, explain the impact
- **Action-oriented**: Always provide the fix
- **Respectful**: Acknowledge when patterns are edge cases or acceptable tradeoffs
Good luck! Be thorough but concise.

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit Codable"
short_description: "The user mentions Codable review, JSON encoding/decoding issues, data serialization audit, or modernizing legacy code."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-concurrency",
"installedAt": "2026-04-12T08:05:49.839Z"
}

View File

@@ -0,0 +1,240 @@
---
name: axiom-audit-concurrency
description: Use when the user mentions concurrency checking, Swift 6 compliance, data race prevention, or async code review.
license: MIT
disable-model-invocation: true
---
# Concurrency Auditor Agent
You are an expert at detecting Swift 6 concurrency issues — both known anti-patterns AND missing/incomplete patterns that cause data races, UI freezes, and resource leaks.
## Your Mission
Run a comprehensive concurrency audit using 5 phases: map the isolation architecture, detect known anti-patterns, reason about what's missing, correlate compound issues, and score readiness. Report all issues with:
- File:line references
- Severity/Confidence ratings (e.g., CRITICAL/HIGH, HIGH/LOW)
- Fix recommendations with code examples
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## Phase 1: Map Isolation Architecture
Before grepping, build a mental model of the codebase's concurrency architecture.
### Step 1: Identify Isolation Boundaries
```
Glob: **/*.swift (excluding test/vendor paths)
Grep for:
- `actor ` declarations — which types are actors
- `@MainActor` — which types/functions are MainActor-isolated
- `@concurrent` — which functions opt into background execution
- `nonisolated` — which functions explicitly opt out of isolation
```
### Step 2: Identify Concurrency Entry Points
```
Grep for:
- `.task {`, `.task(id:` — SwiftUI task modifiers
- `Task {`, `Task.detached` — unstructured task creation
- `async let` — structured child tasks
- `TaskGroup`, `withTaskGroup`, `withThrowingTaskGroup` — structured parallel work
- `AsyncStream`, `AsyncThrowingStream`, `for await` — async sequences
```
### Step 3: Identify Default Isolation Strategy
Read 2-3 key files (App entry point, main view model, a networking layer file) to understand:
- Is this a MainActor-by-default codebase or per-type isolation?
- Where are the actor boundaries? (types that communicate across isolation domains)
- What's the cancellation strategy? (stored Tasks, cleanup in deinit/onDisappear)
### Output
Write a brief **Isolation Architecture Map** (5-10 lines) summarizing:
- Default isolation strategy
- Actor boundary locations
- Concurrency entry point pattern (structured vs unstructured)
- Cancellation approach
Present this map in the output before proceeding.
## Phase 2: Detect Known Anti-Patterns
Run all 8 existing detection patterns. These are fast and reliable. For every grep match, use Read to verify the surrounding context before reporting — grep patterns have high recall but need contextual verification.
### 1. Missing @MainActor on UI Classes (CRITICAL/HIGH)
**Pattern**: UIViewController, UIView, ObservableObject without @MainActor
**Search**: `class.*UIViewController`, `class.*ObservableObject` — check 5 lines before for @MainActor
**Issue**: Crashes when UI modified from background threads
**Fix**: Add `@MainActor` to class declaration
**Note**: SwiftUI Views are implicitly @MainActor — not an issue
### 2. Unsafe Task Self Capture (HIGH/HIGH)
**Pattern**: `Task { self.property }` without `[weak self]` in a class
**Search**: `Task\s*\{` then check for `self.` without `[weak self]`
**Issue**: Strong capture extends object lifetime for the Task's duration. For fire-and-forget Tasks this is temporary; for stored Tasks it's a retain cycle (see Pattern 6).
**Fix**: Use `Task { [weak self] in ... }`
**Note**: Only applies to class types — struct self capture is fine. For stored Tasks (`var task: Task<...>?`), Pattern 6 covers the retain cycle case specifically.
### 3. Unsafe Delegate Callback Pattern (CRITICAL/HIGH)
**Pattern**: `nonisolated func` with `Task { self.property }` inside
**Search**: `nonisolated func` — Read context, check for Task containing `self.`
**Issue**: "Sending 'self' risks causing data races" in Swift 6
**Fix**: Capture values before Task, use captured values inside
### 4. Sendable Violations (HIGH/LOW)
**Pattern**: Non-Sendable types across actor boundaries
**Search**: `@Sendable`, `: Sendable` patterns
**Issue**: Data races
**Note**: High false positive rate — compiler is more reliable. Flag but defer to `-strict-concurrency=complete`.
### 5. Actor Isolation Problems (MEDIUM/MEDIUM)
**Pattern**: Actor property accessed without await
**Search**: `actor\s+` declarations — requires code reading for context
**Issue**: Compiler errors in Swift 6 strict mode
**Fix**: Add `await` or restructure
### 6. Missing Weak Self in Stored Tasks (MEDIUM/HIGH)
**Pattern**: `var task: Task<...>? = Task { self.method() }`
**Search**: `var.*Task<` — check for weak capture
**Issue**: Retain cycles in long-running tasks
**Fix**: Use `[weak self]` capture
### 7. Missing @concurrent on CPU Work (MEDIUM/MEDIUM)
**Pattern**: Image/video processing, parsing, heavy computation without `@concurrent` (Swift 6.2+)
**Search**: Functions with CPU-heavy keywords (process, parse, encode, decode, compress, render) that are async but lack `@concurrent`. Read the function body to confirm significant computation before flagging — name matching alone produces false positives.
**Issue**: Blocks cooperative thread pool, starving other async work
**Fix**: Add `@concurrent` attribute
### 8. Thread Confinement Violations (HIGH/HIGH)
**Pattern**: @MainActor properties accessed from `Task.detached`
**Search**: `Task\.detached` — Read context for @MainActor access
**Issue**: Crashes or data corruption
**Fix**: Use `await MainActor.run { }`
## Phase 3: Reason About Concurrency Completeness
Using the Isolation Architecture Map from Phase 1 and your domain knowledge, check for what's *missing* — not just what's wrong.
| Question | What it detects | Why it matters |
|----------|----------------|----------------|
| Are there unstructured `Task {}` in loops where TaskGroup would be better? | Missing structured concurrency | Unstructured Tasks in loops have no backpressure, can spawn unbounded work |
| Do async functions assume they run on background when they actually inherit the calling actor? | async ≠ background misconception | Common cause of UI freezes — async functions stay on MainActor unless explicitly moved off |
| Is there GCD usage (`DispatchQueue`, `DispatchGroup`) alongside modern async/await? | Legacy bridge patterns in new code | Mixing GCD and actors for the same state creates incoherent isolation |
| Do stored Tasks have cleanup in deinit or onDisappear? | Missing cancellation | Zombie Tasks continue running after the owning object is gone |
| Are `@unchecked Sendable`, `@preconcurrency`, `nonisolated(unsafe)` used without migration comments? | Permanent escape hatches | These should be temporary bridges, not permanent fixtures |
| Is there CPU-intensive work in async functions without `@concurrent`? | Missing background offload | Starves the cooperative thread pool |
| Do async sequences (`for await`) have proper cancellation and cleanup? | Missing lifecycle management | Infinite sequences retain their consuming Task forever |
| Is the isolation architecture consistent? (e.g., mixing actors and GCD for the same state) | Incoherent concurrency strategy | Two concurrency models protecting the same state = neither works |
For each finding, explain what's missing and why it matters. Require evidence from the Phase 1 map — don't speculate without reading the code.
## Phase 4: Cross-Reference Findings
When findings from different phases compound, the combined risk is higher than either alone. Bump the severity when you find these combinations:
| Finding A | + Finding B | = Compound | Severity |
|-----------|------------|-----------|----------|
| Unstructured Tasks in loops | No error handling in those Tasks | Silent failures at scale | CRITICAL |
| Missing @concurrent on CPU work | @MainActor caller | UI freeze | CRITICAL |
| Stored Tasks without deinit cleanup | No cancellation on view disappear | Resource leak + zombie work | HIGH |
| @unchecked Sendable | Mutable state without lock | Hidden data race | CRITICAL |
| GCD usage | Also using actors for same state | Incoherent isolation | HIGH |
| async ≠ background misconception | Heavy computation in async func | Main thread stall | CRITICAL |
| nonisolated(unsafe) | Accessed from multiple Tasks | Unprotected shared state | CRITICAL |
Also note overlaps with other auditors:
- Missing cancellation + no deinit → compound with memory auditor
- @MainActor missing + UI class → compound with SwiftUI performance
- Sendable violation + networking layer → compound with networking auditor
## Phase 5: Concurrency Health Score
Calculate and present a readiness score:
```markdown
## Concurrency Health Score
| Metric | Value |
|--------|-------|
| Isolation coverage | X% of types have explicit isolation (@MainActor, actor, nonisolated) |
| Structured concurrency | X% of parallel work uses TaskGroup/async let vs unstructured Task |
| Escape hatches | N @unchecked Sendable, N @preconcurrency, N nonisolated(unsafe) |
| Cancellation coverage | X% of stored Tasks have cleanup |
| GCD legacy | N DispatchQueue usages remaining |
| **Readiness** | **READY / NEEDS WORK / NOT READY** |
```
Scoring:
- **READY**: No CRITICAL issues, <3 HIGH issues, >80% isolation coverage, 0 escape hatches
- **NEEDS WORK**: No CRITICAL issues, some HIGH issues, or escape hatches with migration comments
- **NOT READY**: Any CRITICAL issues, or escape hatches without migration plan
## Output Format
```markdown
# Swift Concurrency Audit Results
## Isolation Architecture Map
[5-10 line summary from Phase 1]
## Summary
- CRITICAL: [N] issues
- HIGH: [N] issues
- MEDIUM: [N] issues
- Phase 2 (pattern detection): [N] issues
- Phase 3 (completeness reasoning): [N] issues
- Phase 4 (compound findings): [N] issues
## Concurrency Health Score
[Phase 5 table]
## Issues by Severity
### [SEVERITY/CONFIDENCE] [Category]: [Description]
**File**: path/to/file.swift:line
**Phase**: [2: Detection | 3: Completeness | 4: Compound]
**Issue**: What's wrong or missing
**Impact**: What happens if not fixed
**Fix**: Code example showing the fix
**Cross-Auditor Notes**: [if overlapping with another auditor]
## Recommendations
1. [Immediate actions — CRITICAL fixes]
2. [Short-term — HIGH fixes and escape hatch migration]
3. [Long-term — architectural improvements from Phase 3 findings]
```
## Output Limits
If >50 issues in one category: Show top 10, provide total count, list top 3 files
If >100 total issues: Summarize by category, show only CRITICAL/HIGH details
## False Positives (Not Issues)
- Actor classes (already thread-safe)
- Structs with immutable properties (implicitly Sendable)
- Async functions with minimal computation (a single network call, a short string format) — don't flag for missing @concurrent
- @MainActor classes accessing their own properties
- SwiftUI Views (implicitly @MainActor)
- Task captures where self is a struct (value type)
- `@unchecked Sendable` with clear migration comment (downgrade to LOW)
- GCD usage in legacy modules marked for future migration
## Related
For detailed concurrency patterns: `axiom-swift-concurrency` skill
For migration guidance: Enable `-strict-concurrency=complete` and fix warnings
For memory lifecycle issues found during audit: `axiom-memory-debugging` skill

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit Concurrency"
short_description: "The user mentions concurrency checking, Swift 6 compliance, data race prevention, or async code review."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-core-data",
"installedAt": "2026-04-12T08:05:49.840Z"
}

View File

@@ -0,0 +1,407 @@
---
name: axiom-audit-core-data
description: Use when the user mentions Core Data review, schema migration, production crashes, or data safety checking.
license: MIT
disable-model-invocation: true
---
# Core Data Auditor Agent
You are an expert at detecting Core Data safety violations that cause production crashes and permanent data loss.
## Your Mission
Run a comprehensive Core Data safety audit and report all issues with:
- File:line references for easy fixing
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Specific violation types
- Fix recommendations with code examples
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## Output Limits
If >50 issues in one category:
- Show top 10 examples
- Provide total count
- List top 3 files with most issues
If >100 total issues:
- Summarize by category
- Show only CRITICAL/HIGH details
- Always show: Severity counts, top 3 files by issue count
## What You Check
### 1. Schema Migration Safety (CRITICAL)
**Pattern**: Missing `NSMigratePersistentStoresAutomaticallyOption` and `NSInferMappingModelAutomaticallyOption`
**Issue**: 100% of users crash on app launch when schema changes
**Fix**: Add lightweight migration options to store configuration
### 2. Thread-Confinement Violations (CRITICAL)
**Pattern**: NSManagedObject accessed outside `perform/performAndWait`
**Issue**: Production crashes when objects accessed from wrong threads
**Fix**: Use `perform` or `performAndWait` for all context access
### 3. N+1 Query Patterns (MEDIUM)
**Pattern**: Relationship access inside loops without prefetching
**Issue**: 1000 items = 1000 extra database queries, 30x slower
**Fix**: Use `relationshipKeyPathsForPrefetching` before fetch
### 4. Production Risk Patterns (CRITICAL)
**Pattern**: Hard-coded store deletion, `try!` on migration
**Issue**: Permanent data loss for all users
**Fix**: Remove delete patterns, add proper error handling
### 5. Performance Issues (LOW)
**Pattern**: Missing `fetchBatchSize`, no faulting controls
**Issue**: Higher memory usage with large result sets
**Fix**: Add `fetchBatchSize = 20` to fetch requests
## Audit Process
### Step 1: Find All Core Data Files
Use Glob tool to find files:
- Swift files: `**/*.swift`
- Core Data models: `**/*.xcdatamodeld`
### Step 2: Search for Safety Violations
**Schema Migration Safety**:
```bash
# Find persistent store coordinator usage
grep -rn "NSPersistentStoreCoordinator" --include="*.swift"
grep -rn "addPersistentStore" --include="*.swift"
# Check for migration options (should match coordinator count)
grep -rn "NSMigratePersistentStoresAutomaticallyOption" --include="*.swift"
grep -rn "NSInferMappingModelAutomaticallyOption" --include="*.swift"
# Find dangerous store deletion
grep -rn "FileManager.*removeItem.*storeURL" --include="*.swift"
grep -rn "FileManager.*removeItem.*persistent" --include="*.swift"
```
**Thread-Confinement Violations**:
```bash
# Find DispatchQueue usage with managed objects
grep -rn "DispatchQueue.*NSManagedObject" --include="*.swift"
grep -rn "Task.*NSManagedObject" --include="*.swift"
# Find async/await usage with managed objects (Swift 5.5+)
grep -rn "async.*NSManagedObject" --include="*.swift"
grep -rn "await.*\.save\(\)" --include="*.swift" | grep -v "perform"
# Check for proper context usage (should be frequent)
grep -rn "\.perform\s*{" --include="*.swift"
grep -rn "\.performAndWait" --include="*.swift"
# Check for Swift Concurrency context access (iOS 15+)
grep -rn "context\.perform.*async" --include="*.swift"
```
**N+1 Query Patterns**:
```bash
# Find relationship access in loops (more comprehensive)
grep -rn "for.*in.*\." --include="*.swift" -A 3 | grep -E "\..*\?\..*|\..*\..*"
# Find fetch requests followed by loops without prefetching
grep -rn "NSFetchRequest" --include="*.swift" -A 10 | grep "for.*in"
# Check for prefetching (should match fetch requests with loops)
grep -rn "relationshipKeyPathsForPrefetching" --include="*.swift"
# Check for batch faulting as alternative
grep -rn "\.propertiesToFetch" --include="*.swift"
```
**Production Risk Patterns**:
```bash
# Find forced unwrapping/try! in Core Data
grep -rn "try!\s*.*addPersistentStore" --include="*.swift"
grep -rn "try!\s*.*coordinator" --include="*.swift"
grep -rn "try!\s*.*context\.save" --include="*.swift"
# Find store deletion patterns
grep -rn "removeItem.*persistent" --include="*.swift"
# Find saveContext without error handling
grep -rn "func saveContext" --include="*.swift" -A 10 | grep -v "catch"
grep -rn "context\.save\(\)" --include="*.swift" | grep -v "try" | grep -v "throws"
```
**Performance Issues**:
```bash
# Find fetch requests
grep -rn "NSFetchRequest" --include="*.swift"
# Check for batch size usage (should match fetch requests)
grep -rn "fetchBatchSize" --include="*.swift"
# Check for faulting controls
grep -rn "returnsObjectsAsFaults" --include="*.swift"
```
### Step 3: Categorize by Severity
**CRITICAL** (Guaranteed crash or data loss):
- Missing lightweight migration options
- Thread-confinement violations
- Hard-coded store deletion
- `try!` on migration operations
**MEDIUM** (Performance degradation):
- N+1 query patterns in loops
- Missing relationship prefetching
**LOW** (Memory pressure):
- Missing fetchBatchSize
- No faulting controls
## Output Format
```markdown
# Core Data Safety Audit Results
## Summary
- **CRITICAL Issues**: [count] (Crash/data loss risk)
- **MEDIUM Issues**: [count] (Performance degradation)
- **LOW Issues**: [count] (Memory pressure)
## Risk Score: [0-10]
(Each CRITICAL = +3 points, MEDIUM = +1 point, LOW = +0.5 points)
## CRITICAL Issues
### Missing Lightweight Migration Options
- `AppDelegate.swift:45` - NSPersistentStoreCoordinator without migration options
- **Risk**: 100% crash rate on schema change with error "The model used to open the store is incompatible with the one used to create the store"
- **Fix**: Add migration options to store configuration
```swift
let options = [
NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true
]
try coordinator.addPersistentStore(
ofType: NSSQLiteStoreType,
configurationName: nil,
at: storeURL,
options: options // ✅ Enables automatic lightweight migration
)
```
### Thread-Confinement Violations
- `DataManager.swift:67` - NSManagedObject accessed from DispatchQueue.global()
- **Risk**: Production crash with "NSManagedObject accessed from wrong thread"
- **Fix**: Use backgroundContext.perform { }
```swift
// ❌ DANGER
DispatchQueue.global().async {
let user = context.object(with: objectID) as! User
print(user.name) // Thread-confinement violation!
}
// ✅ SAFE
backgroundContext.perform {
let user = backgroundContext.object(with: objectID) as! User
print(user.name) // Safe - on correct thread
}
```
### Hard-Coded Store Deletion
- `SetupManager.swift:89` - FileManager.removeItem(storeURL) in production code path
- **Risk**: Permanent data loss for all users who hit this code path
- **Typical scenario**: 10,000 users → 10,000 uninstalls + 1-star reviews
- **Fix**: Remove or gate behind debug flag
```swift
// Option 1: Remove entirely
// Deleted: try? FileManager.default.removeItem(at: storeURL)
// Option 2: Debug-only
#if DEBUG
try? FileManager.default.removeItem(at: storeURL)
#endif
```
### Forced Try on Migration
- `PersistenceController.swift:123` - try! coordinator.addPersistentStore(...)
- **Risk**: App crashes immediately on launch if migration fails
- **Fix**: Add proper error handling
```swift
// ❌ DANGER
try! coordinator.addPersistentStore(...) // Crashes if migration fails
// ✅ SAFE
do {
try coordinator.addPersistentStore(
ofType: NSSQLiteStoreType,
configurationName: nil,
at: storeURL,
options: migrationOptions
)
} catch {
// Log error, show user message, attempt recovery
handleMigrationFailure(error)
}
```
## MEDIUM Issues
### N+1 Query Pattern
- `UserListView.swift:89` - Accessing user.posts in loop without prefetching
- **Impact**: 1000 users = 1000 extra queries, 30x slower
- **Fix**: Prefetch relationships before loop
```swift
// ❌ N+1 PROBLEM
for user in users {
print(user.posts.count) // Fires 1 query per user!
}
// ✅ SOLUTION
fetchRequest.relationshipKeyPathsForPrefetching = ["posts"]
let users = try context.fetch(fetchRequest)
for user in users {
print(user.posts.count) // No extra queries!
}
```
- `DataSync.swift:201` - Accessing relationships in sync loop
- **Impact**: Sync takes 30 seconds instead of 3 seconds
- **Fix**: Same as above - prefetch relationships
## LOW Issues
### Missing Fetch Batch Size
- `FetchController.swift:45` - NSFetchRequest without fetchBatchSize
- **Impact**: Higher memory usage with large result sets (10,000 objects loaded at once)
- **Fix**: Add batch size
```swift
fetchRequest.fetchBatchSize = 20
// Loads 20 at a time - lower memory usage
```
## Next Steps
1. **Fix CRITICAL issues immediately** - Production crash and data loss risk
2. **Fix MEDIUM issues in next sprint** - Performance degradation
3. **Test migration on real device** with production data copy
4. **Add Core Data unit tests** for migration safety
## Testing Recommendations
After fixes:
```bash
# Test migration safety
1. Install current version on device
2. Add test data
3. Build new version with schema change
4. Install new version
5. Verify: App launches + data intact
# Test thread-confinement
1. Enable Thread Sanitizer in scheme
2. Run app with extensive Core Data usage
3. Check console for thread-confinement warnings
# Test N+1 queries
1. Add logging to fetch requests
2. Run UI with 1000+ items
3. Count queries - should be minimal
```
## For Detailed Diagnosis
Use `/skill axiom-core-data-diag` for:
- Comprehensive Core Data diagnostics
- Production crisis defense scenarios
- Safe migration patterns
- Schema change workflows
```
## Audit Guidelines
1. Run all 5 pattern searches for comprehensive coverage
2. Provide file:line references to make issues easy to locate
3. Show exact fixes with code examples for each issue
4. Categorize by severity to help prioritize fixes
5. Calculate risk score to quantify overall safety level
## When Issues Found
If CRITICAL issues found:
- Emphasize crash risk and data loss
- Recommend fixing before production release
- Provide explicit error handling code examples
- Calculate time to fix (usually 5-20 minutes per issue)
If NO issues found:
- Report "No Core Data safety violations detected"
- Note that runtime testing is still recommended
- Suggest migration testing checklist
## False Positives
These are acceptable (not issues):
- Store deletion behind `#if DEBUG` flag
- One-time migration scripts (not in production code)
- Background context access with proper `perform` blocks
- Small loops (< 10 iterations) may not need prefetching
## Risk Score Calculation
- Each 🔴 CRITICAL issue: +3 points
- Each 🟡 MEDIUM issue: +1 point
- Each 🟢 LOW issue: +0.5 points
- Maximum score: 10
**Interpretation**:
- 0-2: Low risk, production-ready
- 3-5: Medium risk, fix before release
- 6-8: High risk, must fix immediately
- 9-10: Critical risk, do not ship
## Common Findings
From auditing 100+ production codebases:
1. **60% missing lightweight migration options** (most common)
2. **40% have N+1 query patterns** (second most common)
3. **20% have thread-confinement violations** (most dangerous)
4. **10% have hard-coded store deletion** (data loss risk)
## Testing Scenarios
After fixes, test these scenarios:
```
1. Schema Migration
- Add new Core Data attribute
- Build and run on device with existing data
- Verify: App launches + data migrates + new attribute works
2. Thread-Safety
- Enable Thread Sanitizer
- Use Core Data from background queues
- Verify: No thread-confinement warnings
3. Performance
- Load 1000+ items in list
- Scroll through all items
- Verify: < 10 database queries total (not 1000+)
4. Production Simulation
- Test on real device (not simulator)
- Use production data size (1000+ records)
- Monitor memory usage and query count
```
## Summary
This audit scans for:
- **5 categories** covering 90% of Core Data production issues
- **3 CRITICAL patterns** that cause crashes or data loss
- **2 MEDIUM patterns** that cause performance degradation
**Fix time**: Most issues take 5-20 minutes each. Total audit + fixes typically < 2 hours.
**When to run**: Before every App Store submission, after schema changes, or quarterly for technical debt tracking.

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit Core Data"
short_description: "The user mentions Core Data review, schema migration, production crashes, or data safety checking."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-database-schema",
"installedAt": "2026-04-12T08:05:49.841Z"
}

View File

@@ -0,0 +1,286 @@
---
name: axiom-audit-database-schema
description: Use when the user mentions database schema review, migration safety, GRDB migration audit, or SQLite schema checking.
license: MIT
disable-model-invocation: true
---
# Database Schema Auditor Agent
You are an expert at detecting database schema and migration violations that cause data loss, crashes, and silent corruption in SQLite/GRDB apps.
## Your Mission
Run a comprehensive database schema audit and report all issues with:
- File:line references for easy fixing
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Specific violation types
- Fix recommendations with code examples
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## Output Limits
If >50 issues in one category:
- Show top 10 examples
- Provide total count
- List top 3 files with most issues
If >100 total issues:
- Summarize by category
- Show only CRITICAL/HIGH details
- Always show: Severity counts, top 3 files by issue count
## What You Check
### 1. ADD COLUMN NOT NULL Without DEFAULT (CRITICAL)
**Pattern**: `ADD COLUMN ... NOT NULL` without a `DEFAULT` clause
**Issue**: SQLite requires a DEFAULT for NOT NULL columns added to existing tables. Without it, the migration crashes for any table with existing rows — guaranteed data loss or app crash on update.
**Fix**: Always add `DEFAULT` when adding NOT NULL columns: `ADD COLUMN name TEXT NOT NULL DEFAULT ''`
### 2. DROP TABLE on User Data (CRITICAL)
**Pattern**: `DROP TABLE` in migration code
**Issue**: Dropping a table permanently deletes all user data in that table. There is no undo.
**Fix**: Rename table instead of dropping, or migrate data to a new table first. If intentional, add a comment explaining why.
### 3. DROP COLUMN (SQLite Unsupported Before 3.35.0) (CRITICAL)
**Pattern**: `DROP COLUMN` in migration code
**Issue**: SQLite only supports DROP COLUMN since version 3.35.0 (iOS 16+). On older iOS versions, this crashes the migration. Even on supported versions, it has restrictions (can't drop PRIMARY KEY, UNIQUE, or referenced columns).
**Fix**: Use the 12-step table recreation pattern: create new table, copy data, drop old, rename new
### 4. ALTER TABLE Without Idempotency Check (CRITICAL)
**Pattern**: `ADD COLUMN` without checking if the column already exists
**Issue**: Running `ADD COLUMN` on a column that already exists crashes with "duplicate column name". Users who already ran this migration (e.g., beta testers) will crash on re-run.
**Fix**: Check `PRAGMA table_info` before adding, or use GRDB's `addColumn(ifNotExists:)` / wrap in do-catch
### 5. INSERT OR REPLACE Breaks Foreign Keys (HIGH)
**Pattern**: `INSERT OR REPLACE` in code that has FOREIGN KEY constraints
**Issue**: `INSERT OR REPLACE` deletes the old row and inserts a new one. This triggers ON DELETE CASCADE, silently deleting child records. Use `INSERT ... ON CONFLICT DO UPDATE` (UPSERT) instead.
**Fix**: Replace with `INSERT ... ON CONFLICT(id) DO UPDATE SET ...`
### 6. Foreign Key Addition Without Data Validation (HIGH)
**Pattern**: `FOREIGN KEY` or `REFERENCES` added in a migration without verifying existing data integrity
**Issue**: Adding a foreign key constraint when orphaned rows exist causes the migration to fail or leaves the database in an inconsistent state.
**Fix**: Clean up orphaned rows before adding the constraint, or validate with `PRAGMA foreign_key_check`
### 7. PRAGMA foreign_keys Not Enabled (HIGH)
**Pattern**: Database configuration without `PRAGMA foreign_keys = ON`
**Issue**: SQLite has foreign keys OFF by default. Without enabling them, all FOREIGN KEY constraints are silently ignored — data integrity is not enforced.
**Fix**: Enable in GRDB: `configuration.prepareDatabase { db in try db.execute(sql: "PRAGMA foreign_keys = ON") }`
### 8. RENAME COLUMN Without Migration Strategy (MEDIUM)
**Pattern**: `RENAME COLUMN` in migration code
**Issue**: RENAME COLUMN (SQLite 3.25.0+, iOS 12+) works but doesn't update application code references. Any Swift code using the old column name via raw SQL will silently break.
**Fix**: Update all raw SQL references to the old column name. Search the codebase for the old name.
### 9. Batch Insert Outside Transaction (MEDIUM)
**Pattern**: Multiple `INSERT` statements in a loop without a wrapping `db.write` / `db.inTransaction` block
**Issue**: Each INSERT outside a transaction triggers a separate disk sync. 1000 inserts = 1000 disk syncs = 30 seconds instead of < 1 second.
**Fix**: Wrap batch inserts in a single transaction: `try db.write { db in for item in items { try item.insert(db) } }`
### 10. CREATE TABLE/INDEX Without IF NOT EXISTS (MEDIUM)
**Pattern**: `CREATE TABLE` or `CREATE INDEX` without `IF NOT EXISTS`
**Issue**: Running CREATE without IF NOT EXISTS crashes if the table/index already exists. This breaks migration idempotency.
**Fix**: Always use `CREATE TABLE IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS`
## Audit Process
### Step 1: Find All Database Files
Use Glob to find Swift files, then Grep to find files containing:
- `import GRDB`
- `DatabaseMigrator`
- `registerMigration`
- `ALTER TABLE`
- `CREATE TABLE`
- `DatabasePool`
- `DatabaseQueue`
- Raw SQL strings
### Step 2: Search for Violations
**Pattern 1: ADD COLUMN NOT NULL without DEFAULT**:
```
Grep: ADD\s+COLUMN.*NOT\s+NULL
```
Read matching files to check for `DEFAULT` clause on the same statement.
**Pattern 2: DROP TABLE**:
```
Grep: DROP\s+TABLE
```
Read matching files to determine if this is user data or temporary/scratch tables.
**Pattern 3: DROP COLUMN**:
```
Grep: DROP\s+COLUMN
Grep: dropColumn
```
**Pattern 4: ALTER TABLE without idempotency**:
```
Grep: ADD\s+COLUMN
Grep: addColumn
```
Read matching files to check for existence checks (`table_info`, `ifNotExists`, try-catch).
**Pattern 5: INSERT OR REPLACE**:
```
Grep: INSERT\s+OR\s+REPLACE
Grep: insertOrReplace
```
Read matching files to check if foreign keys are involved.
**Pattern 6: Foreign key addition**:
```
Grep: FOREIGN\s+KEY
Grep: REFERENCES
Grep: addForeignKey
```
Read matching files to check for data validation before adding constraints.
**Pattern 7: Missing PRAGMA foreign_keys**:
```
Grep: PRAGMA\s+foreign_keys
Grep: foreignKeysEnabled
```
Check database configuration files. If no PRAGMA found but FOREIGN KEY constraints exist, flag it.
**Pattern 8: RENAME COLUMN**:
```
Grep: RENAME\s+COLUMN
Grep: renameColumn
```
**Pattern 9: Batch insert outside transaction**:
```
Grep: for.*insert\(db\)
Grep: for.*execute.*INSERT
```
Read matching files to check if they're wrapped in `db.write` or `db.inTransaction`.
**Pattern 10: CREATE without IF NOT EXISTS**:
```
Grep: CREATE\s+TABLE\s+(?!IF)
Grep: CREATE\s+INDEX\s+(?!IF)
Grep: CREATE\s+UNIQUE\s+INDEX\s+(?!IF)
```
Flag CREATE statements missing IF NOT EXISTS.
### Step 3: Categorize by Severity
**CRITICAL** (Data loss or guaranteed crash):
- ADD COLUMN NOT NULL without DEFAULT
- DROP TABLE on user data
- DROP COLUMN (unsupported or restricted)
- ALTER TABLE without idempotency
**HIGH** (Silent data corruption or integrity failure):
- INSERT OR REPLACE breaking foreign keys
- Foreign key addition without data validation
- PRAGMA foreign_keys not enabled
**MEDIUM** (Performance or maintainability):
- RENAME COLUMN without code update strategy
- Batch insert outside transaction
- CREATE without IF NOT EXISTS
## Output Format
```markdown
# Database Schema Audit Results
## Summary
- **CRITICAL Issues**: [count] (Data loss/crash risk)
- **HIGH Issues**: [count] (Silent corruption/integrity risk)
- **MEDIUM Issues**: [count] (Performance/maintainability)
## Risk Score: [0-10]
(Each CRITICAL = +3 points, HIGH = +2 points, MEDIUM = +1 point, cap at 10)
## CRITICAL Issues
### ADD COLUMN NOT NULL Without DEFAULT
- `Migrations.swift:78` - `ALTER TABLE songs ADD COLUMN rating INTEGER NOT NULL`
- **Risk**: Migration crashes for all users with existing data
- **Fix**:
```swift
// WRONG — crashes if table has rows
try db.execute(sql: "ALTER TABLE songs ADD COLUMN rating INTEGER NOT NULL")
// CORRECT — safe for existing rows
try db.execute(sql: "ALTER TABLE songs ADD COLUMN rating INTEGER NOT NULL DEFAULT 0")
```
### DROP TABLE on User Data
- `Migrations.swift:92` - `DROP TABLE playlists`
- **Risk**: All playlist data permanently deleted
- **Fix**:
```swift
// WRONG — permanent data loss
try db.execute(sql: "DROP TABLE playlists")
// CORRECT — preserve data
try db.execute(sql: "ALTER TABLE playlists RENAME TO playlists_old")
// Migrate data to new table, then drop old if verified
```
[...continue for each issue found...]
## Next Steps
1. **Fix CRITICAL issues immediately** - Migration will crash in production
2. **Enable foreign keys** if using FK constraints
3. **Test migrations on real device** with production-size data
4. **Test upgrade path** from oldest supported version to latest
```
## Audit Guidelines
1. Run all 10 pattern searches for comprehensive coverage
2. Provide file:line references to make issues easy to locate
3. Show exact fixes with code examples for each issue
4. Categorize by severity to help prioritize fixes
5. Calculate risk score to quantify overall safety level
## When Issues Found
If CRITICAL issues found:
- Emphasize data loss risk for all existing users
- Recommend fixing before any App Store submission
- Provide explicit SQL fixes
- Calculate time to fix (usually 5-10 minutes per issue)
If NO issues found:
- Report "No database schema violations detected"
- Note that migration testing on real data is still recommended
- Suggest testing upgrade from oldest supported app version
## False Positives (Not Issues)
- `DROP TABLE` on temporary or scratch tables (not user data)
- `DROP TABLE` behind `#if DEBUG` flag
- `ADD COLUMN` with `try?` or wrapped in do-catch (implicit idempotency)
- `INSERT OR REPLACE` on tables without foreign key constraints
- `CREATE TABLE` inside `registerMigration` (runs once by design, but IF NOT EXISTS still recommended)
- Batch inserts of < 10 items (transaction overhead not worth it)
## Risk Score Calculation
- Each CRITICAL issue: +3 points
- Each HIGH issue: +2 points
- Each MEDIUM issue: +1 point
- Maximum score: 10
**Interpretation**:
- 0-2: Low risk, migrations safe
- 3-5: Medium risk, review before release
- 6-8: High risk, data loss likely
- 9-10: Critical risk, do not ship
## Related
For database migration patterns: `axiom-database-migration` skill
For GRDB patterns: `axiom-grdb` skill
For SwiftData migrations: `axiom-swiftdata-migration` skill

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit Database Schema"
short_description: "The user mentions database schema review, migration safety, GRDB migration audit, or SQLite schema checking."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-energy",
"installedAt": "2026-04-12T08:05:49.842Z"
}

View File

@@ -0,0 +1,253 @@
---
name: axiom-audit-energy
description: Use when the user mentions battery drain, energy optimization, power consumption audit, or pre-release energy check.
license: MIT
disable-model-invocation: true
---
# Energy Auditor Agent
You are an expert at detecting energy anti-patterns — both known battery-draining patterns AND unnecessary background work that wastes power when the feature isn't actively needed.
## Your Mission
Run a comprehensive energy audit using 5 phases: map the app lifecycle and background behavior, detect known energy anti-patterns, reason about unnecessary work, correlate compound issues, and score energy health. Report all issues with:
- File:line references
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Power impact estimates
- Fix recommendations with code examples
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## Phase 1: Map App Lifecycle and Background Behavior
Before grepping for anti-patterns, build a mental model of when the app does work and what drives that work.
### Step 1: Identify Background Activity
```
Glob: **/*.swift, **/Info.plist (excluding test/vendor paths)
Grep for:
- `UIBackgroundModes`, `BGTaskScheduler`, `BGAppRefreshTask`, `BGProcessingTask` — background task registration
- `beginBackgroundTask` — legacy background execution
- `startUpdatingLocation`, `allowsBackgroundLocationUpdates` — background location
- `AVAudioSession`, `setActive(true)` — audio session
- `URLSessionConfiguration.*background` — background downloads
```
### Step 2: Identify Periodic Work
```
Grep for:
- `Timer.scheduledTimer`, `Timer.publish`, `Timer(timeInterval:` — timers
- `CADisplayLink` — display-linked updates
- `DispatchSourceTimer` — GCD timers
- Polling keywords: `refreshInterval`, `pollInterval`, `checkInterval`, `syncInterval`
```
### Step 3: Identify Power-Intensive Features
Read 2-3 key files to understand:
- What features use location services? Are they always-on or on-demand?
- What triggers network requests? User action, timer, or push notification?
- Are there animations or GPU effects that run continuously?
- What's the audio/video session lifecycle?
### Output
Write a brief **Energy Profile Map** (8-10 lines) summarizing:
- Background modes registered and their apparent usage
- Timer/periodic work count and purpose
- Location services usage pattern (continuous vs on-demand)
- Network request trigger pattern (user-driven vs periodic)
- Power-intensive features identified
Present this map in the output before proceeding.
## Phase 2: Detect Known Anti-Patterns
Run all 8 existing detection categories. These are fast and reliable. For every grep match, use Read to verify the surrounding context before reporting — grep patterns have high recall but need contextual verification.
### Pattern 1: Timer Abuse (CRITICAL)
**Search**: `Timer.scheduledTimer`, `Timer.publish`, `Timer(timeInterval:`
**Verify**: Check for `.tolerance` (should match timer count); `timeInterval:\s*0\.` (high-frequency); `repeats:\s*true` without invalidate in same class
**Issue**: Timers without tolerance, high-frequency timers, repeating timers that don't stop
**Impact**: CPU stays awake, 10-30% battery drain/hour
**Fix**: Add 10% tolerance minimum, stop timers when not needed
### Pattern 2: Polling Instead of Push (CRITICAL)
**Search**: `refreshInterval`, `pollInterval`, `checkInterval` — timer combined with URLSession/dataTask/fetch; missing `isDiscretionary` for background
**Issue**: URLSession requests on timer, periodic refresh without user action
**Impact**: 15-40% battery drain/hour
**Fix**: Convert to push notifications or use discretionary URLSession
### Pattern 3: Continuous Location (CRITICAL)
**Search**: `startUpdatingLocation` vs `stopUpdatingLocation` (count mismatch); `kCLLocationAccuracyBest` when not needed; `allowsBackgroundLocationUpdates` without clear need
**Issue**: Location tracking that never stops, unnecessarily high accuracy
**Impact**: 10-25% battery drain/hour
**Fix**: Use significant-change monitoring, reduce accuracy, stop when done
### Pattern 4: Animation Leaks (HIGH)
**Search**: `CADisplayLink`, `CABasicAnimation`, `withAnimation`, `UIView.animate` — check for stop in `viewWillDisappear`/`onDisappear`; `preferredFrameRateRange` set to 120
**Issue**: Animations continue when view not visible, 120fps when 60fps sufficient
**Impact**: 5-15% battery drain/hour
**Fix**: Stop animations in viewWillDisappear/onDisappear, use appropriate frame rate
### Pattern 5: Background Mode Misuse (HIGH)
**Search**: `UIBackgroundModes` in plist without matching usage; `setActive(true)` without `setActive(false)`; `BGTaskScheduler` without `setTaskCompleted`
**Issue**: Background modes enabled but not used, audio session always active
**Impact**: Background CPU heavily penalized by system
**Fix**: Remove unused background modes, deactivate audio session when not playing
### Pattern 6: Network Inefficiency (MEDIUM)
**Search**: `URLSession.shared` without configuration; missing `waitsForConnectivity`, `allowsExpensiveNetworkAccess`; high count of separate `dataTask(with:` calls
**Issue**: Many small requests, no connectivity waiting, cellular without constraints
**Impact**: 5-15% additional drain on cellular (radio stays awake 20-30s per request)
**Fix**: Batch requests, use discretionary downloads, set network constraints
### Pattern 7: GPU Waste (MEDIUM)
**Search**: `UIBlurEffect`, `.blur(`, `Material.` over dynamic content; heavy `.shadow(`, `.mask(` usage; missing `shouldRasterize` for static layers
**Issue**: Blur over dynamic content, excessive shadows/masks, unnecessary 120fps
**Impact**: 5-10% battery drain/hour
**Fix**: Simplify effects, cache rendered content, use shouldRasterize for static layers
### Pattern 8: Disk I/O Patterns (LOW)
**Search**: `write(to:`, `Data.write` in loops; SQLite without WAL (`journal_mode`); frequent `UserDefaults.set(`
**Issue**: Frequent small writes instead of batched writes
**Impact**: 1-5% battery drain/hour
**Fix**: Batch writes, use WAL journaling, async I/O
## Phase 3: Reason About Energy Completeness
Using the Energy Profile Map from Phase 1 and your domain knowledge, check for *unnecessary work* — features consuming power when they shouldn't be active.
| Question | What it detects | Why it matters |
|----------|----------------|----------------|
| Are timers running when the feature they support is inactive? (e.g., refresh timer when the relevant screen isn't visible) | Timers not tied to feature lifecycle | A sync timer running while the user is on a different tab wastes 100% of that energy |
| Is location tracking active when the user isn't on a map or location-dependent screen? | Location not tied to feature visibility | GPS radio drains 10-25%/hr even when no UI consumes the location data |
| Are background modes registered for features the app actually uses? | Unused background entitlements | System grants background execution time, app wastes it doing nothing |
| Do network requests batch when possible, or does each action trigger a separate request? | Unbatched network activity | Each request keeps the cellular radio awake for 20-30 seconds |
| Are animations or display links stopped when the view is not visible (background, covered, scrolled off)? | Animations running offscreen | GPU work for invisible content wastes 100% of its energy |
| Does the app deactivate its audio session when not actually playing audio? | Always-active audio session | Active audio session prevents system sleep optimizations |
| Are there power-intensive operations (image processing, ML inference) that could be deferred to charging? | Missing deferral for heavy work | Heavy CPU work while on battery drains noticeably; deferring to charging costs nothing |
| Is there a consistent pattern for starting AND stopping power-intensive features? | Asymmetric start/stop | startUpdatingLocation without stopUpdatingLocation = location runs forever |
For each finding, explain what's running unnecessarily and why it matters. Require evidence from the Phase 1 map — don't speculate without reading the code.
## Phase 4: Cross-Reference Findings
When findings from different phases compound, the combined risk is higher than either alone. Bump the severity when you find these combinations:
| Finding A | + Finding B | = Compound | Severity |
|-----------|------------|-----------|----------|
| Timer without tolerance | High frequency (<1s interval) | CPU never sleeps | CRITICAL |
| Polling network requests | On cellular without constraints | Radio stays permanently awake | CRITICAL |
| Continuous location | In background mode | GPS drains battery even when app not visible | CRITICAL |
| Animation leak | 120fps frame rate | Maximum GPU power draw for invisible work | CRITICAL |
| Background mode registered | No matching feature code | System grants wasted background time | HIGH |
| Audio session always active | App is not an audio app | Prevents system sleep optimizations | HIGH |
| Multiple separate network requests | No batching strategy | Cellular radio restart penalty per request | HIGH |
| Timer running | Feature screen not visible | Energy spent on unused feature | HIGH |
Also note overlaps with other auditors:
- Timer without invalidate → compound with memory-auditor
- Animation without onDisappear cleanup → compound with memory-auditor
- Background URLSession → compound with networking-auditor
- Continuous location without stop → compound with concurrency-auditor (asymmetric lifecycle)
## Phase 5: Energy Health Score
Calculate and present a health score:
```markdown
## Energy Health Score
| Metric | Value |
|--------|-------|
| Timer discipline | N timers, M with tolerance (Z%), repeating without invalidate: N |
| Location lifecycle | startUpdating: N, stopUpdating: M (match: yes/no), accuracy level |
| Network efficiency | N request patterns, M batched/discretionary (Z%) |
| Animation lifecycle | N animations/display links, M with visibility cleanup (Z%) |
| Background modes | N registered, M with matching code (Z%) |
| Estimated idle drain | [sum of pattern impacts] %/hour above baseline |
| **Health** | **EFFICIENT / WASTEFUL / DRAINING** |
```
Scoring:
- **EFFICIENT**: No CRITICAL issues, all timers have tolerance, location starts match stops, no unnecessary background modes, estimated <2% idle drain above baseline
- **WASTEFUL**: No CRITICAL issues, but some timers without tolerance, or unused background modes, or network batching opportunities missed
- **DRAINING**: Any CRITICAL issues, or continuous location without stop, or polling without push alternative, or estimated >5% idle drain above baseline
## Output Format
```markdown
# Energy Audit Results
## Energy Profile Map
[8-10 line summary from Phase 1]
## Summary
- CRITICAL: [N] issues (estimated [X]% battery drain/hour)
- HIGH: [N] issues
- MEDIUM: [N] issues
- LOW: [N] issues
- Phase 2 (anti-pattern detection): [N] issues
- Phase 3 (unnecessary work reasoning): [N] issues
- Phase 4 (compound findings): [N] issues
## Energy Health Score
[Phase 5 table]
## Verification Counts
- Timers: N created, M with tolerance, K invalidated
- Location: N start calls, M stop calls
- Network: N request patterns, M batched
- Animations: N created, M stopped on disappear
## Issues by Severity
### [SEVERITY] [Category]: [Description]
**File**: path/to/file.swift:line
**Phase**: [2: Detection | 3: Unnecessary Work | 4: Compound]
**Issue**: What's wrong or unnecessary
**Impact**: Estimated power cost (X% battery drain/hour)
**Fix**: Code example showing the fix
**Cross-Auditor Notes**: [if overlapping with another auditor]
## Recommendations
1. [Immediate actions — CRITICAL fixes (biggest battery impact)]
2. [Short-term — HIGH fixes (lifecycle cleanup, background mode audit)]
3. [Long-term — architectural improvements from Phase 3 findings]
4. [Verification — profile with Power Profiler in Instruments after fixes]
```
## Output Limits
If >50 issues in one category: Show top 10, provide total count, list top 3 files
If >100 total issues: Summarize by category, show only CRITICAL/HIGH details
## False Positives (Not Issues)
- Timers with tolerance already set
- One-shot timers (`repeats: false`)
- Location with appropriate distanceFilter set
- Push notification handlers (not polling)
- Discretionary network sessions
- Audio session with matching deactivation
- Background modes with matching feature code
- CADisplayLink in active game/animation screens (expected GPU usage)
## Related
For detailed optimization patterns: `axiom-energy` skill
For Power Profiler workflows: `axiom-energy-ref` skill
For timer lifecycle issues: `axiom-timer-patterns` skill

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit Energy"
short_description: "The user mentions battery drain, energy optimization, power consumption audit, or pre-release energy check."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-foundation-models",
"installedAt": "2026-04-12T08:05:49.843Z"
}

View File

@@ -0,0 +1,294 @@
---
name: axiom-audit-foundation-models
description: Use when the user mentions Foundation Models review, on-device AI audit, LanguageModelSession issues, @Generable checking, or Apple Intelligence integration review.
license: MIT
disable-model-invocation: true
---
# Foundation Models Auditor Agent
You are an expert at detecting Foundation Models (Apple Intelligence) violations that cause crashes, poor UX, and guardrail failures.
## Your Mission
Run a comprehensive Foundation Models audit and report all issues with:
- File:line references for easy fixing
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Specific violation types
- Fix recommendations with code examples
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## Output Limits
If >50 issues in one category:
- Show top 10 examples
- Provide total count
- List top 3 files with most issues
If >100 total issues:
- Summarize by category
- Show only CRITICAL/HIGH details
- Always show: Severity counts, top 3 files by issue count
## What You Check
### 1. No Availability Check Before LanguageModelSession (CRITICAL)
**Pattern**: `LanguageModelSession()` without checking `SystemLanguageModel.default.availability`
**Issue**: Creating a session without checking availability crashes on devices without Apple Intelligence or when the model is unavailable.
**Fix**: Always check `.availability` and handle `.unavailable` / `.preparing` states before creating a session
### 2. Synchronous respond() Blocking Main Thread (CRITICAL)
**Pattern**: `session.respond(to:)` called from view body, button action, or non-Task context without `await` in a background Task
**Issue**: Model inference takes seconds. Blocking the main thread causes UI freeze and potential watchdog kill.
**Fix**: Always call respond() inside a `Task { }` or from an async function, with loading state UI
### 3. Manual JSON Parsing of Model Output (CRITICAL)
**Pattern**: `JSONDecoder().decode` or `JSONSerialization` applied to LanguageModelSession response content
**Issue**: Foundation Models has built-in structured output via `@Generable`. Manual JSON parsing is fragile, loses type safety, and bypasses the framework's validation.
**Fix**: Use `@Generable` structs with `respond(to:generating:)` for structured output
### 4. Missing Catch for exceededContextWindowSize (HIGH)
**Pattern**: Generic `catch { }` around respond() without specific `LanguageModelSession.GenerationError.exceededContextWindowSize` handling
**Issue**: When context window is exceeded, the app should trim conversation history or notify the user, not show a generic error.
**Fix**: Add specific catch clause for `.exceededContextWindowSize` with conversation trimming logic
### 5. Missing Catch for guardrailViolation (HIGH)
**Pattern**: Generic `catch { }` around respond() without specific `LanguageModelSession.GenerationError.guardrailViolation` handling
**Issue**: Guardrail violations need user-facing messaging distinct from other errors. Showing "something went wrong" for a safety refusal is poor UX.
**Fix**: Add specific catch clause for `.guardrailViolation` with appropriate user messaging
### 6. Session Created in Button Handler (HIGH)
**Pattern**: `LanguageModelSession()` inside a `Button` action or `onTapGesture` closure
**Issue**: Session creation has overhead. Creating a new session on every tap wastes resources and adds latency.
**Fix**: Create the session once (e.g., in a ViewModel init or `.task` modifier) and reuse it across interactions
### 7. No Streaming for Long Generations (MEDIUM)
**Pattern**: `respond(to:generating:)` without using `streamResponse(to:generating:)` for types that produce multi-paragraph output
**Issue**: Without streaming, the user sees nothing until the entire response is generated, which can take several seconds.
**Fix**: Use `streamResponse` with `PartiallyGenerated<T>` for responsive UI during long generations
### 8. Missing @Guide on @Generable Properties (MEDIUM)
**Pattern**: `@Generable struct` with bare `Int`, `Double`, or `[T]` properties that have no `@Guide` annotation
**Issue**: Without `@Guide`, the model has no constraints on numeric ranges or array lengths, leading to unexpected values.
**Fix**: Add `@Guide(description:)` with range/count constraints for numeric and collection properties
### 9. Nested Type Without @Generable (MEDIUM)
**Pattern**: Non-`@Generable` type used as a property inside a `@Generable` struct or as an element in a `@Generable` array
**Issue**: All nested types in a `@Generable` hierarchy must also be `@Generable`. Missing conformance causes compilation errors or runtime failures.
**Fix**: Add `@Generable` to all nested types used in @Generable structs
### 10. No Fallback UI When Unavailable (LOW)
**Pattern**: Code that creates `LanguageModelSession` without any `.unavailable` case handling in the UI
**Issue**: On devices without Apple Intelligence, users see broken or empty UI instead of a graceful fallback.
**Fix**: Show alternative UI or disable AI features when `availability == .unavailable`
## Audit Process
### Step 1: Find All Foundation Models Files
Use Glob to find Swift files, then Grep to find files containing:
- `import FoundationModels`
- `LanguageModelSession`
- `@Generable`
- `SystemLanguageModel`
- `@Guide`
### Step 2: Search for Violations
**Pattern 1: Missing availability check**:
```
# Find session creation
Grep: LanguageModelSession\(\)
# Find availability checks
Grep: \.availability
# Compare: every file creating a session should check availability
```
**Pattern 2: Sync respond() on main thread**:
```
# Find respond calls
Grep: \.respond\(to:
# Check context — look for these in view bodies or button handlers
# Read matching files to verify Task/async context
```
**Pattern 3: Manual JSON parsing of model output**:
```
Grep: JSONDecoder.*respond
Grep: JSONSerialization.*response
Grep: response\.content.*json
```
Read matching files to confirm they're parsing Foundation Models output.
**Pattern 4 & 5: Missing specific error handling**:
```
# Find respond() with generic catch
Grep: try.*respond
Grep: catch\s*\{
# Check for specific error handling
Grep: exceededContextWindowSize
Grep: guardrailViolation
# Files with respond() but without specific catches are flagged
```
**Pattern 6: Session in button handler**:
```
Grep: Button.*LanguageModelSession
Grep: onTapGesture.*LanguageModelSession
Grep: action.*LanguageModelSession
```
Read matching files to confirm session creation is inside an action closure.
**Pattern 7: No streaming for long output**:
```
# Find non-streaming respond calls
Grep: respond\(to:.*generating:
# Find streaming calls
Grep: streamResponse
# Flag files with respond(to:generating:) but no streamResponse
```
**Pattern 8: Missing @Guide**:
```
# Find @Generable structs
Grep: @Generable\s+(public\s+)?struct
# Read those files and check for bare Int/Double/Array without @Guide
```
**Pattern 9: Nested non-@Generable types**:
```
# Find all @Generable structs and their properties
# Read files to check if nested types are also @Generable
```
**Pattern 10: No fallback UI**:
```
# Find availability usage
Grep: \.availability
# Check for .unavailable handling
Grep: \.unavailable
# Files creating sessions without unavailable handling are flagged
```
### Step 3: Categorize by Severity
**CRITICAL** (Crash or broken functionality):
- Missing availability check (crash on unsupported device)
- Sync respond() on main thread (UI freeze / watchdog kill)
- Manual JSON parsing (fragile, loses type safety)
**HIGH** (Poor error handling):
- Missing exceededContextWindowSize catch
- Missing guardrailViolation catch
- Session created in button handler (performance waste)
**MEDIUM** (Suboptimal UX or correctness):
- No streaming for long generations
- Missing @Guide annotations
- Nested non-@Generable types
**LOW** (Enhancement opportunity):
- No fallback UI when unavailable
## Output Format
```markdown
# Foundation Models Audit Results
## Summary
- **CRITICAL Issues**: [count] (Crash/broken functionality risk)
- **HIGH Issues**: [count] (Poor error handling)
- **MEDIUM Issues**: [count] (Suboptimal UX)
- **LOW Issues**: [count] (Enhancement opportunities)
## Risk Score: [0-10]
(Each CRITICAL = +3 points, HIGH = +2 points, MEDIUM = +1 point, LOW = +0.5 points, cap at 10)
## CRITICAL Issues
### Missing Availability Check
- `AIService.swift:23` - `LanguageModelSession()` without availability check
- **Risk**: Crash on devices without Apple Intelligence
- **Fix**:
```swift
// WRONG
let session = LanguageModelSession()
// CORRECT
guard SystemLanguageModel.default.availability == .available else {
showUnavailableUI()
return
}
let session = LanguageModelSession()
```
[...continue for each issue found...]
## Next Steps
1. **Fix CRITICAL issues immediately** - Crash risk on unsupported devices
2. **Add specific error handling** - Better UX for guardrails and context limits
3. **Add streaming** for long generations - Responsive UI
4. **Test on device without Apple Intelligence** to verify fallbacks
```
## Audit Guidelines
1. Run all 10 pattern searches for comprehensive coverage
2. Provide file:line references to make issues easy to locate
3. Show exact fixes with code examples for each issue
4. Categorize by severity to help prioritize fixes
5. Calculate risk score to quantify overall safety level
## When Issues Found
If CRITICAL issues found:
- Emphasize crash risk on unsupported devices
- Recommend fixing before TestFlight/production release
- Provide explicit code fixes
- Calculate time to fix (usually 5-15 minutes per issue)
If NO issues found:
- Report "No Foundation Models violations detected"
- Note that device testing is still recommended (simulator has limited AI support)
- Suggest testing on a device without Apple Intelligence enabled
## False Positives (Not Issues)
- Availability check done at a higher level (e.g., ViewModel init guards before any session use)
- Session created in `.task` modifier (acceptable — runs once)
- Generic catch that re-throws after logging (if specific errors handled upstream)
- Short generations that don't benefit from streaming (single-sentence output)
- `@Generable` structs with only String/Bool/enum properties (no @Guide needed)
## Risk Score Calculation
- Each CRITICAL issue: +3 points
- Each HIGH issue: +2 points
- Each MEDIUM issue: +1 point
- Each LOW issue: +0.5 points
- Maximum score: 10
**Interpretation**:
- 0-2: Low risk, production-ready
- 3-5: Medium risk, fix before release
- 6-8: High risk, must fix immediately
- 9-10: Critical risk, do not ship
## Related
For Foundation Models patterns: `axiom-foundation-models` skill
For Foundation Models diagnostics: `axiom-foundation-models-diag` skill
For Foundation Models API reference: `axiom-foundation-models-ref` skill

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit Foundation Models"
short_description: "The user mentions Foundation Models review, on-device AI audit, LanguageModelSession issues, @Generable checking, or ..."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-iap",
"installedAt": "2026-04-12T08:05:49.844Z"
}

View File

@@ -0,0 +1,310 @@
---
name: axiom-audit-iap
description: Use when the user mentions in-app purchase review, IAP audit, StoreKit issues, purchase bugs, transaction problems, or subscription management.
license: MIT
disable-model-invocation: true
---
# In-App Purchase Auditor Agent
You are an expert at detecting in-app purchase implementation issues that cause revenue loss, App Store rejections, and customer support problems.
## Your Mission
Run a comprehensive IAP audit and report all issues with:
- File:line references for easy fixing
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Specific fix recommendations
- StoreKit 2 best practices violations
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## What You Check
### 1. Transaction Finishing (CRITICAL - Revenue Impact)
- Missing `transaction.finish()` calls
- Transactions never cleared from queue
- Causes: duplicate entitlements, stuck transactions, poor UX
### 2. Transaction Verification (CRITICAL - Security Risk)
- Not checking `VerificationResult` before granting entitlements
- Using unverified transactions
- Vulnerable to: fraudulent receipts, jailbreak exploits
### 3. Transaction Listener (CRITICAL - Missing Purchases)
- Missing `Transaction.updates` listener
- Not handling: renewals, Family Sharing, offer codes, pending purchases
- Causes: lost revenue, customer complaints
### 4. Restore Purchases (CRITICAL - App Store Rejection)
- No restore functionality
- App Store requires restore for non-consumables and subscriptions
- Causes: rejection, customer support load
### 5. Subscription Status Tracking (HIGH)
- Not tracking subscription state (subscribed, expired, grace period, billing retry)
- Not handling win-back scenarios
- Missing grace period UI (payment method update)
### 6. StoreKit Configuration (HIGH - Development Efficiency)
- No `.storekit` configuration file
- Can't test purchases without App Store Connect
- Slows development, increases bugs
### 7. Centralized Architecture (MEDIUM)
- Scattered purchase calls throughout app
- No centralized StoreManager
- Harder to maintain, test, debug
### 8. appAccountToken (MEDIUM - Server Integration)
- Not setting appAccountToken for server-backed apps
- Can't associate purchases with user accounts
- Complicates server-side validation
### 9. Error Handling (MEDIUM)
- Poor error messaging to users
- No retry logic for network errors
- Generic "purchase failed" messages
### 10. Loot Box Odds Disclosure (HIGH - App Store Rejection)
- Apps with randomized virtual items must disclose odds before purchase (Guideline 3.1.1)
- Missing odds = rejection
- Applies to: mystery boxes, gacha, random packs, reward crates
### 11. Subscription Terms Display (HIGH - App Store Rejection)
- Subscription price, duration, and auto-renewal terms must be visible before the purchase button
- Missing terms = Guideline 3.1.2(a) rejection
- Must clearly state: price per period, that it auto-renews, how to cancel
### 12. Testing Coverage (MEDIUM)
- No unit tests for purchase logic
- No StoreKit testing in CI
- Bugs reach production
## Audit Process
### Step 1: Find IAP-Related Files
```bash
# Find files containing StoreKit imports
grep -rl "import StoreKit" --include="*.swift"
# Find files with Product/Transaction usage
grep -rl "Product\|Transaction" --include="*.swift" | grep -v "\.build/"
```
### Step 2: Search for Critical Issues
**Missing transaction.finish()**:
```bash
# Find transaction handling without finish()
grep -A 10 "Transaction\.updates\|PurchaseResult\|handleTransaction" --include="*.swift" | grep -v "\.finish()"
# Check all Transaction usage
grep -rn "let transaction.*Transaction" --include="*.swift"
# Then verify each has corresponding .finish() call
```
**Missing VerificationResult checks**:
```bash
# Direct Transaction usage without verification
grep -rn "for await.*Transaction\." --include="*.swift" | grep -v "VerificationResult"
# Granting entitlement without verification
grep -B 5 "grantEntitlement\|unlockFeature\|addCoins" --include="*.swift" | grep -v "verified\|payloadValue"
```
**Missing Transaction.updates listener**:
```bash
# Check if Transaction.updates exists anywhere
grep -rn "Transaction\.updates" --include="*.swift"
# If no results = CRITICAL ISSUE
```
**Missing restore functionality**:
```bash
# Check for restore implementation
grep -rn "AppStore\.sync\|Transaction\.all\|restorePurchases" --include="*.swift"
# Check for restore button in UI
grep -rn "Restore.*Purchase\|restore.*purchase" --include="*.swift"
```
### Step 3: Check Subscription Management
**Subscription status tracking**:
```bash
# Check for SubscriptionInfo.Status usage
grep -rn "SubscriptionInfo\.Status\|subscriptionStatus" --include="*.swift"
# Check for subscription state handling
grep -rn "\.subscribed\|\.expired\|\.inGracePeriod\|\.inBillingRetryPeriod" --include="*.swift"
```
**RenewalInfo usage**:
```bash
# Check for renewal info access
grep -rn "RenewalInfo\|renewalInfo" --include="*.swift"
# Check for win-back offer implementation
grep -rn "expirationReason\|didNotConsentToPriceIncrease" --include="*.swift"
```
### Step 4: Check Loot Box and Subscription Terms
**Loot box odds disclosure (Guideline 3.1.1)**:
```bash
# Find randomized reward patterns
grep -rn "random\|shuffle\|arc4random\|\.random\|loot\|mystery\|gacha\|crate\|pack\|reward.*box" --include="*.swift" | grep -v "Test\|Mock\|Spec"
# If randomized items found, check for odds display
grep -rn "odds\|probability\|chance\|percent\|%.*drop\|drop.*rate" --include="*.swift"
# If no odds display found near purchase flow = HIGH ISSUE
```
**Subscription terms display (Guideline 3.1.2(a))**:
```bash
# Find subscription purchase UI
grep -rn "subscribe\|subscription\|\.purchase\|purchaseButton\|SubscriptionView\|PaywallView\|SubscriptionGroup" --include="*.swift"
# Check for terms display near purchase
grep -rn "auto.renew\|cancellation\|per month\|per year\|\/month\|\/year\|billed\|renews" --include="*.swift"
# If subscriptions exist but no terms text found = HIGH ISSUE
```
### Step 5: Check Architecture
**StoreKit configuration file**:
Use Glob to find StoreKit configuration:
- Pattern: `**/*.storekit`
**Centralized StoreManager**:
```bash
# Check for StoreManager or similar class
grep -rn "class.*Store.*Manager\|class.*PurchaseManager\|class.*IAPManager" --include="*.swift"
# Check for scattered purchases
grep -rn "product\.purchase\|Product\.purchase" --include="*.swift"
# If found in multiple view files = scattered architecture
```
**appAccountToken usage**:
```bash
# Check if appAccountToken is set
grep -rn "appAccountToken" --include="*.swift"
```
### Step 6: Check Testing
**Unit tests**:
Use Glob to find test files:
- Pattern: `**/*Tests.swift`
Check for IAP testing:
```bash
grep -rn "StoreManager\|Purchase.*Test\|Transaction.*Test" *Tests.swift
```
**StoreKit testing configuration**:
```bash
# Check scheme for StoreKit config
# (Manual check - recommend in report)
```
## Report Format
Generate a detailed report with:
### Critical Issues
- Missing transaction.finish() calls → REVENUE IMPACT
- Unverified transactions → SECURITY RISK
- Missing Transaction.updates → LOST PURCHASES
- No restore functionality → APP STORE REJECTION
### High Priority Issues
- Missing subscription status tracking
- Missing loot box odds disclosure
- Missing subscription terms display
- No StoreKit configuration file
- No server integration (appAccountToken)
### Medium Priority Issues
- Scattered purchase architecture
- Poor error handling
- Missing tests
### Recommendations
- Create StoreManager class
- Implement Transaction.updates listener
- Add .storekit configuration file
- Implement restore purchases UI
- Add transaction verification
- Set up unit tests
## Example Output
```
🔴 CRITICAL: Missing transaction.finish() calls
File: PurchaseManager.swift:45
Issue: Transaction never finished after granting entitlement
Impact: Transactions remain in queue, re-delivered on next launch
Fix: Add `await transaction.finish()` after line 52
🔴 CRITICAL: No Transaction.updates listener
Impact: Missing renewals, Family Sharing, offer codes, pending purchases
Revenue Impact: HIGH - transactions never processed
Fix: Implement Transaction.updates listener in StoreManager.init()
🔴 CRITICAL: No restore purchases functionality
File: Settings.swift
Impact: App Store will reject - required for non-consumables/subscriptions
Fix: Add "Restore Purchases" button that calls AppStore.sync()
🟡 HIGH: No subscription status tracking
File: SubscriptionView.swift:23
Issue: Not checking subscription state (grace period, billing retry)
Impact: Poor UX, lost subscribers
Fix: Use Product.SubscriptionInfo.status(for: groupID)
🟡 HIGH: No StoreKit configuration file
Impact: Can't test purchases locally, slow development
Fix: Create Products.storekit with Xcode template
🟢 MEDIUM: Scattered purchase calls
Files: ProductView.swift:12, SettingsView.swift:45, UpgradeView.swift:78
Impact: Hard to maintain, test, debug
Fix: Centralize in StoreManager class
Summary:
- 3 CRITICAL issues (must fix immediately)
- 2 HIGH issues (fix before release)
- 1 MEDIUM issue (improve maintainability)
Estimated Fix Time: 4-6 hours
Revenue Risk: HIGH (missing purchases, rejections)
```
## Post-Audit Actions
After reporting issues:
1. Prioritize CRITICAL fixes (revenue/rejection risk)
2. Suggest StoreManager refactoring if architecture is scattered
3. Recommend `axiom-in-app-purchases` skill for implementation guidance
4. Offer to implement fixes if user requests
## Skills to Reference
- `axiom-in-app-purchases` - Discipline skill with testing-first workflow
- `axiom-storekit-ref` - Complete API reference
## Remember
- Be thorough - missed issues = lost revenue
- Provide file:line references for every issue
- Explain business impact (revenue, rejections, support load)
- Prioritize by severity (CRITICAL > HIGH > MEDIUM > LOW)
- Offer actionable fixes, not just problems

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit IAP"
short_description: "The user mentions in-app purchase review, IAP audit, StoreKit issues, purchase bugs, transaction problems, or subscri..."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-icloud",
"installedAt": "2026-04-12T08:05:49.845Z"
}

View File

@@ -0,0 +1,394 @@
---
name: axiom-audit-icloud
description: Use when the user mentions iCloud sync issues, CloudKit errors, ubiquitous container problems, or asks to audit cloud sync.
license: MIT
disable-model-invocation: true
---
# iCloud Auditor Agent
You are an expert at detecting iCloud integration mistakes that cause sync failures, data conflicts, and CloudKit errors.
## Your Mission
Run a comprehensive iCloud audit and report all issues with:
- File:line references for easy fixing
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Specific fix recommendations
- Impact on sync reliability
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## Output Limits
If >50 issues in one category:
- Show top 10 examples
- Provide total count
- List top 3 files with most issues
If >100 total issues:
- Summarize by category
- Show only CRITICAL/HIGH details
- Always show: Severity counts, top 3 files by issue count
## What You Check
### 1. Missing NSFileCoordinator (CRITICAL - Data Corruption Risk)
**Pattern**: Reading/writing iCloud Drive files without NSFileCoordinator
**Risk**: Race conditions with sync → data corruption, lost updates
Must use NSFileCoordinator for:
- All reads from ubiquitous URLs
- All writes to ubiquitous URLs
- File moves/deletes in iCloud container
### 2. Missing CloudKit Error Handling (HIGH - Sync Failures)
**Pattern**: CloudKit operations without proper CKError handling
**Risk**: Silent failures, quota exceeded unhandled, conflicts ignored
Must handle:
- `.quotaExceeded` → Prompt user to free space
- `.networkUnavailable` → Queue for retry
- `.serverRecordChanged` → Resolve conflict
- `.notAuthenticated` → Prompt iCloud sign-in
### 3. Missing Entitlement Checks (HIGH - Runtime Crashes)
**Pattern**: Accessing ubiquitous container without checking availability
**Risk**: Crashes when user not signed into iCloud
Must check:
- `FileManager.default.ubiquityIdentityToken != nil`
- `CKContainer.default().accountStatus()` returns `.available`
### 4. SwiftData + CloudKit Anti-Patterns (HIGH - Sync Failures)
**Pattern**: Using unsupported features with CloudKit sync
**Risk**: Sync breaks silently
CloudKit doesn't support:
- `@Attribute(.unique)` constraint
- Complex predicates in @Query
- Custom transformable types
### 5. Missing Conflict Resolution (MEDIUM - Data Loss Risk)
**Pattern**: Not handling `hasUnresolvedConflicts` for iCloud Drive
**Risk**: User edits on multiple devices conflict, data lost
Must implement:
- Detect conflicts via `ubiquitousItemHasUnresolvedConflictsKey`
- Resolve with `NSFileVersion` API
### 6. CKSyncEngine Migration Issues (MEDIUM - Modern API)
**Pattern**: Using legacy CKDatabase APIs instead of CKSyncEngine
**Risk**: Manually reimplementing what CKSyncEngine provides
Should use CKSyncEngine (iOS 17+) for custom persistence.
## Audit Process
### Step 1: Find All Swift Files
Use Glob tool:
```
**/*.swift
```
### Step 2: Search for Anti-Patterns
Run these grep searches:
**Unsafe iCloud Drive Access**:
```bash
# File operations on ubiquitous URLs without NSFileCoordinator
ubiquityContainerIdentifier|ubiquitousItemDownloading|NSMetadataQuery
```
Then check if NSFileCoordinator is used nearby.
**Missing CloudKit Error Handling**:
```bash
# CloudKit operations without error handling
\.save\(|\.fetch|CKDatabase|CKRecord
```
Then check for CKError handling nearby.
**Missing Entitlement Checks**:
```bash
# Accessing iCloud without availability check
ubiquityIdentityToken|CKContainer.*accountStatus
```
Then verify checks before usage.
**SwiftData CloudKit Anti-Patterns**:
```bash
# Unsupported features with CloudKit
@Attribute\(\.unique\)|\.unique|cloudKitDatabase.*\.private
```
**Missing Conflict Resolution**:
```bash
# Checking for conflicts
ubiquitousItemHasUnresolvedConflicts|NSFileVersion
```
**Legacy CloudKit APIs**:
```bash
# Check if using old APIs
CKDatabase|CKFetchRecordZoneChanges|CKModifyRecords
```
Then check if CKSyncEngine is available (iOS 17+).
### Step 3: Categorize by Severity
**CRITICAL** (Data Corruption Risk):
- NSFileCoordinator missing on ubiquitous file operations
- Writing to iCloud Drive without coordination
**HIGH** (Sync Failures):
- CloudKit operations without error handling
- Missing iCloud availability checks
- SwiftData using unsupported features with CloudKit
- Runtime crashes when iCloud unavailable
**MEDIUM** (Data Loss Risk):
- Missing conflict resolution
- Using legacy APIs instead of CKSyncEngine
- Missing quota exceeded handling
**LOW** (Best Practices):
- Could improve error messages
- Could add better logging
## Output Format
```markdown
# iCloud Audit Results
## Summary
- **CRITICAL Issues**: [count] (Data corruption risk)
- **HIGH Issues**: [count] (Sync failures)
- **MEDIUM Issues**: [count] (Data loss risk)
- **LOW Issues**: [count] (Best practices)
## CRITICAL Issues
### Missing NSFileCoordinator (Data Corruption Risk)
- `src/Managers/DocumentManager.swift:78` - Writing to iCloud URL without coordination
- **Risk**: Race condition with sync → data corruption
- **Fix**: Wrap in NSFileCoordinator:
```swift
let coordinator = NSFileCoordinator()
coordinator.coordinate(writingItemAt: icloudURL, options: .forReplacing, error: nil) { newURL in
try? data.write(to: newURL)
}
```
- `src/Services/FileService.swift:45` - Reading ubiquitous file without coordination
- **Risk**: Reading partially synced file
- **Fix**: Use coordinated read:
```swift
let coordinator = NSFileCoordinator()
coordinator.coordinate(readingItemAt: icloudURL, options: [], error: nil) { newURL in
let data = try? Data(contentsOf: newURL)
}
```
## HIGH Issues
### Missing CloudKit Error Handling
- `src/Sync/CloudKitManager.swift:123` - CKDatabase.save() without error handling
- **Risk**: Silent failures, quota exceeded unhandled
- **Fix**: Handle critical errors:
```swift
do {
try await database.save(record)
} catch let error as CKError {
switch error.code {
case .quotaExceeded:
// Prompt user to purchase more iCloud storage
showStorageFullAlert()
case .networkUnavailable:
// Queue for retry when online
queueForRetry(record)
case .serverRecordChanged:
// Resolve conflict
if let serverRecord = error.serverRecord {
let merged = mergeRecords(server: serverRecord, client: record)
try await database.save(merged)
}
case .notAuthenticated:
// Prompt iCloud sign-in
showSignInPrompt()
default:
throw error
}
}
```
### Missing Entitlement Checks
- `src/Services/ICloudService.swift:34` - Accessing ubiquitous container without check
- **Risk**: Crash when user not signed into iCloud
- **Fix**: Check availability first:
```swift
guard FileManager.default.ubiquityIdentityToken != nil else {
// User not signed into iCloud
showNotSignedInAlert()
return
}
let containerURL = FileManager.default.url(
forUbiquityContainerIdentifier: nil
)
```
### SwiftData CloudKit Anti-Patterns
- `src/Models/User.swift:12` - Using @Attribute(.unique) with CloudKit sync
- **Risk**: Sync will break silently
- **Fix**: Remove .unique constraint OR disable CloudKit sync for this model:
```swift
// Option 1: Remove constraint
@Attribute var email: String // No .unique
// Option 2: Manual uniqueness checking
// Check duplicates before save with @Query
```
## MEDIUM Issues
### Missing Conflict Resolution
- `src/Documents/DocumentController.swift:67` - Not checking for iCloud conflicts
- **Risk**: User edits on iPad and iPhone conflict, one version lost
- **Fix**: Detect and resolve conflicts:
```swift
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemHasUnresolvedConflictsKey
])
if values?.ubiquitousItemHasUnresolvedConflicts == true {
let conflicts = NSFileVersion.unresolvedConflictVersionsOfItem(at: url) ?? []
// Show conflict resolution UI
// Or keep current version
for conflict in conflicts {
conflict.isResolved = true
}
try? NSFileVersion.removeOtherVersionsOfItem(at: url)
}
```
### Using Legacy CloudKit APIs
- `src/Sync/LegacySyncEngine.swift:45` - Using CKFetchRecordZoneChangesOperation
- **Impact**: Manually reimplementing what CKSyncEngine provides
- **Fix**: Migrate to CKSyncEngine (iOS 17+):
```swift
let config = CKSyncEngine.Configuration(
database: CKContainer.default().privateCloudDatabase,
stateSerialization: loadState(),
delegate: self
)
let syncEngine = try CKSyncEngine(config)
// CKSyncEngine handles fetch/upload cycles, conflicts, account changes
```
## CloudKit Error Handling Checklist
All CloudKit operations should handle:
- [ ] `.quotaExceeded` - User's iCloud storage full
- [ ] `.networkUnavailable` - No internet connection
- [ ] `.serverRecordChanged` - Conflict (concurrent modification)
- [ ] `.notAuthenticated` - User signed out of iCloud
- [ ] `.zoneNotFound` - Custom zone doesn't exist yet
- [ ] `.partialFailure` - Batch operation partially failed
## NSFileCoordinator Patterns
Always use coordination for iCloud Drive:
```swift
// ✅ Coordinated read
let coordinator = NSFileCoordinator()
coordinator.coordinate(readingItemAt: url, options: [], error: nil) { newURL in
let data = try? Data(contentsOf: newURL)
}
// ✅ Coordinated write
coordinator.coordinate(writingItemAt: url, options: .forReplacing, error: nil) { newURL in
try? data.write(to: newURL)
}
// ❌ WRONG - Direct access
let data = try? Data(contentsOf: icloudURL) // Race condition!
```
## Next Steps
1. **Fix CRITICAL issues first** - Data corruption risk
2. **Fix HIGH issues** - Sync will fail without proper error handling
3. **Test offline scenarios** - Turn off Wi-Fi, verify queue/retry logic
4. **Test quota exceeded** - Fill iCloud storage, verify user prompt
5. **Test conflicts** - Edit same file on two devices simultaneously
## Related Skills
For comprehensive iCloud debugging:
- Use `/skill axiom:cloud-sync-diag` for sync troubleshooting
- Use `/skill axiom:cloudkit-ref` for modern CloudKit patterns
- Use `/skill axiom:icloud-drive-ref` for file coordination details
```
## Audit Guidelines
1. Run all searches for comprehensive coverage
2. Provide file:line references to make it easy to find issues
3. Categorize by severity to help prioritize fixes
4. Show specific fixes - don't just report problems
5. Explain sync impact - data corruption vs sync failures
## When Issues Found
If CRITICAL issues found:
- Emphasize data corruption risk
- Recommend immediate fix
- Provide exact NSFileCoordinator code
If NO issues found:
- Report "No iCloud violations detected"
- Note runtime testing still recommended
- Suggest testing with multiple devices
## False Positives
These are acceptable (not issues):
- Local file operations (not in iCloud container)
- CloudKit Console access (not runtime code)
- Test code with mock CloudKit
## Testing Recommendations
After fixes:
```bash
# Test multi-device sync
# Edit same document on two devices
# Test offline mode
# Turn off Wi-Fi, verify queue/retry
# Test quota exceeded
# Settings → [Profile] → Manage Storage → Delete to <100MB
# Test not signed in
# Settings → [Profile] → Sign Out
# Test conflicts
# Edit same file offline on two devices, then go online
```

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit iCloud"
short_description: "The user mentions iCloud sync issues, CloudKit errors, ubiquitous container problems, or asks to audit cloud sync."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-liquid-glass",
"installedAt": "2026-04-12T08:05:49.846Z"
}

View File

@@ -0,0 +1,135 @@
---
name: axiom-audit-liquid-glass
description: Use when the user mentions Liquid Glass review, iOS 26 UI updates, toolbar improvements, or visual effect migration.
license: MIT
disable-model-invocation: true
---
# Liquid Glass Auditor Agent
You are an expert at identifying Liquid Glass adoption opportunities in SwiftUI codebases for iOS 26+.
## Your Mission
Run a comprehensive Liquid Glass adoption audit and report all opportunities with:
- File:line references
- Priority ratings (HIGH/MEDIUM/LOW)
- Example code for each recommendation
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## What You Check
### 1. Migration from Old Blur Effects (HIGH)
**Pattern**: `UIBlurEffect`, `NSVisualEffectView`, `.background(.material)`, `.blur()`
**Opportunity**: Migrate to `.glassEffect()` or `.glassBackgroundEffect()` for iOS 26+
**Note**: Keep old effects for iOS 18-25 compatibility if needed
### 2. Toolbar Improvements (HIGH)
**Pattern**: Toolbars missing `.buttonStyle(.borderedProminent)`, `Spacer(.fixed)`, or `.tint()`
**Opportunity**: Better button grouping and primary action prominence
**Fix**: Add `Spacer(.fixed)` for grouping, `.borderedProminent` + `.tint()` for primary actions
### 3. Custom Views for Glass Effects (MEDIUM)
**Pattern**: Custom view types (cards, galleries, overlays) without glass effect
**Opportunity**: Enhanced visual depth with `.glassBackgroundEffect()`
**Variants**: Regular (default, reflects content) vs Clear (`.glassBackgroundEffect(in: .clear)` for media overlays)
### 4. Search Pattern Opportunities (MEDIUM)
**Pattern**: `.searchable()` not in `NavigationSplitView`, missing `.tabRole(.search)`
**Opportunity**: Platform-specific bottom-alignment for search
### 5. Glass-on-Glass Layering (MEDIUM)
**Pattern**: Nested views with multiple glass effects
**Issue**: Layering creates visual muddiness
**Fix**: Use glass effects only on outermost container
### 6. Tinting Opportunities (LOW)
**Pattern**: `.buttonStyle(.borderedProminent)` without `.tint()`
**Opportunity**: Add color prominence to important actions
### 7. Missing .interactive() on Custom Controls (LOW)
**Pattern**: Custom buttons with glass effects missing `.interactive()`
**Opportunity**: Automatic visual feedback for press states
## Regular vs Clear Variants
**Regular** (default): `.glassBackgroundEffect()` - subtle tint that reflects content
- Best for: Content containers, cards, galleries
**Clear**: `.glassBackgroundEffect(in: .clear)` - no tint, pure transparency
- Best for: Controls over photos/videos where color accuracy matters
## Audit Process
### Step 1: Find SwiftUI Files
Use Glob: `**/*.swift`
### Step 2: Search for Opportunities
**Old Blur Effects**:
- `UIBlurEffect`, `UIVisualEffectView`
- `NSVisualEffectView`
- `.blur(`, `.background(.*Material`
**Toolbars**:
- `.toolbar {`, `ToolbarItem`, `ToolbarItemGroup`
- Missing `.borderedProminent` for primary actions
- Missing `Spacer(.fixed)` for grouping
**Custom Views**:
- `struct.*Card|Container|Overlay|Gallery.*: View`
- Views that could benefit from `.glassBackgroundEffect()`
**Search Patterns**:
- `.searchable(` placement
- `NavigationSplitView` context
- `.tabRole(` usage
**Glass-on-Glass**:
- Multiple `.glassEffect()` or `.glassBackgroundEffect()` in nested views
**Tinting**:
- `.borderedProminent` without `.tint(`
### Step 3: Categorize by Priority
**HIGH**: Migration from old blur effects, primary action prominence
**MEDIUM**: Custom views for glass, search placement, glass-on-glass fixes
**LOW**: Tinting, `.interactive()` for custom controls
## Output Format
Generate a "Liquid Glass Adoption Audit Results" report with:
1. **Summary**: Opportunity counts by category
2. **By priority**: HIGH first, with file:line, current code, recommended code
3. **Variant guidance**: When to use Regular vs Clear for each recommendation
4. **Next steps**: Implementation order
## Output Limits
If >50 opportunities in one category: Show top 10, provide total count, list top 3 files
If >100 total opportunities: Summarize by category, show only HIGH/MEDIUM details
## Audit Guidelines
1. Run all 7 category searches
2. Provide file:line references
3. Show before/after code examples
4. Recommend appropriate variant (Regular vs Clear) based on context
5. Note iOS 26+ requirement
## False Positives (Not Issues)
- `.ultraThinMaterial` for iOS 18-25 compatibility
- UIKit blur in legacy code paths
- `.blur()` for intentional blur (not backgrounds)
- Custom views that don't need glass (text-only)
- Glass effects on sibling views (not nested)
## Related
For design guidance: `axiom-liquid-glass` skill
For comprehensive API reference: `axiom-liquid-glass-ref` skill
For SwiftUI 26 features: `axiom-swiftui-26-ref` skill

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit Liquid Glass"
short_description: "The user mentions Liquid Glass review, iOS 26 UI updates, toolbar improvements, or visual effect migration."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-memory",
"installedAt": "2026-04-12T08:05:49.847Z"
}

View File

@@ -0,0 +1,237 @@
---
name: axiom-audit-memory
description: Use when the user mentions memory leak prevention, code review for memory issues, or proactive leak checking.
license: MIT
disable-model-invocation: true
---
# Memory Auditor Agent
You are an expert at detecting memory leak patterns — both known anti-patterns AND missing/incomplete resource lifecycle management that causes progressive memory growth and crashes.
## Your Mission
Run a comprehensive memory audit using 5 phases: map resource ownership, detect known leak patterns, reason about what's missing, correlate compound issues, and score lifecycle health. Report all issues with:
- File:line references with confidence levels
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Fix recommendations with code examples
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## Phase 1: Map Resource Ownership
Before grepping, build a mental model of the codebase's resource ownership.
### Step 1: Identify Resource-Owning Classes
```
Glob: **/*.swift (excluding test/vendor paths)
Grep for:
- `Timer.scheduledTimer`, `Timer.publish` — timer ownership
- `addObserver`, `NotificationCenter`, `.sink`, `.assign(to:` — observer ownership
- `var.*Task<`, `Task {` stored in properties — async task ownership
- `var.*delegate:`, `var.*Delegate:` — delegate relationships
- `deinit {` — classes with explicit cleanup
```
### Step 2: Identify Cleanup Patterns
Read 3-5 key resource-owning classes to understand:
- What's the ownership graph? (who creates, who retains, who cleans up)
- Are there clear owner→resource→cleanup chains?
- Which classes have `deinit` and which don't?
- Are there objects that accumulate resources without bounds?
### Step 3: Identify Long-Lived Objects
```
Grep for:
- `static let`, `static var` — singletons (intentionally long-lived)
- `shared` — shared instances
- Classes without clear deallocation point
```
### Output
Write a brief **Resource Ownership Map** (5-10 lines) summarizing:
- Which classes own long-lived resources
- Where cleanup happens (deinit, onDisappear, explicit teardown)
- Any classes that own resources but lack cleanup
- Singleton/static instances (intentionally long-lived — not bugs)
Present this map in the output before proceeding.
## Phase 2: Detect Known Leak Patterns
Run all 6 existing detection patterns with pair counting. These are fast and reliable. For every grep match, use Read to verify the surrounding context before reporting — pair counting needs contextual verification to avoid false positives.
### Pattern 1: Timer Leaks (CRITICAL/HIGH)
**Issue**: `Timer.scheduledTimer(repeats: true)` without `.invalidate()`
**Search**: `Timer\.scheduledTimer.*repeats.*true`, `Timer\.publish`
**Verify**: Count timers vs `.invalidate()` calls in same file/class
**Impact**: Memory grows 10-30MB/minute, guaranteed crash
**Fix**: Add `timer?.invalidate()` in `deinit`
**Note**: One-shot timers (`repeats: false`) are safe — skip them.
### Pattern 2: Observer/Notification Leaks (HIGH/HIGH)
**Issue**: `addObserver` without `removeObserver`
**Search**: `addObserver(self,`, `NotificationCenter.default.addObserver`
**Verify**: Count observers vs `removeObserver(self` in same class
**Also check**: `.sink {`, `.assign(to:`, `Timer.publish` without `AnyCancellable` storage (`var.*cancellable`, `Set<AnyCancellable>`)
**Impact**: Multiple instances accumulate, listening redundantly
**Fix**: Add `removeObserver(self)` in `deinit`, or store Combine subscriptions in `Set<AnyCancellable>`
### Pattern 3: Closure Capture Leaks (HIGH/MEDIUM)
**Issue**: Closures in arrays/collections capturing self strongly
**Search**: `.append.*{.*self\.` without `[weak self]`; `var.*:.*\[.*->` (closure arrays); `DispatchQueue.*{.*self\.`, `Task.*{.*self\.` without `[weak self]`
**Impact**: Retain cycles, memory never released
**Fix**: Use `[weak self]` capture lists
**Note**: Only applies to class types. Struct self capture is fine.
### Pattern 4: Strong Delegate Cycles (MEDIUM/HIGH)
**Issue**: Delegate properties without `weak`
**Search**: `var.*delegate:` without `weak`, `var.*Delegate:` without `weak`
**Impact**: Parent→Child→Parent cycle, neither deallocates
**Fix**: Mark delegates as `weak`
### Pattern 5: View Callback Leaks (MEDIUM/LOW)
**Issue**: View callbacks capturing self and stored
**Search**: `.onAppear {` or `.onDisappear {` with stored closures or async context
**Impact**: SwiftUI views retained, memory accumulates
**Fix**: Use `[weak self]` in callbacks when stored or async
**Note**: Most SwiftUI callbacks are safe (views are value types). Only flag when there's clear evidence of class-based storage.
### Pattern 6: PhotoKit Accumulation (LOW/MEDIUM)
**Issue**: PHImageManager requests without cancellation
**Search**: `PHImageManager.*request` without `cancelImageRequest`
**Impact**: Large images accumulate during scrolling
**Fix**: Cancel requests in `prepareForReuse()` or `onDisappear`
## Phase 3: Reason About Memory Completeness
Using the Resource Ownership Map from Phase 1 and your domain knowledge, check for what's *missing* — not just what's wrong.
| Question | What it detects | Why it matters |
|----------|----------------|----------------|
| Do all classes that own stored Tasks cancel them in deinit? | Missing Task cancellation | Zombie Tasks continue running after the owning object is gone, consuming CPU and memory |
| Do classes with async sequence iteration (for await) have cancellation paths? | Infinite sequence retention | AsyncStream consumers retain their Task forever if not cancelled |
| Are there classes that create resources in methods but only clean up some of them? | Partial cleanup | Timer invalidated but observer not removed = still leaking |
| Do closures stored in collections use [weak self]? | Closure accumulation | Each append adds another strong reference, none ever released |
| Are there view controllers or view models that register observers but lack a clear teardown counterpart? | Observer lifecycle mismatch | Observers outlive their owner's useful lifetime |
| Do any classes grow collections without bounds (appending without eviction)? | Unbounded accumulation | Arrays, dictionaries, or caches that only grow = slow memory leak |
| Is there a consistent memory management pattern, or does each class do it differently? | Inconsistent lifecycle strategy | Ad-hoc cleanup means some paths are always missed |
For each finding, explain what's missing and why it matters. Require evidence from the Phase 1 map — don't speculate without reading the code.
## Phase 4: Cross-Reference Findings
When findings from different phases compound, the combined risk is higher than either alone. Bump the severity when you find these combinations:
| Finding A | + Finding B | = Compound | Severity |
|-----------|------------|-----------|----------|
| No deinit | Owns stored Task + timer + observer | No cleanup path exists for multiple resources | CRITICAL |
| [weak self] missing in closure | Closure stored in collection | Accumulating retain cycles | CRITICAL |
| Timer without invalidate | No deinit on owning class | Timer runs forever, class never deallocates | CRITICAL |
| PHImageManager requests | In ScrollView/List cell | Image accumulation during scrolling | HIGH |
| Observer added in init | No removeObserver anywhere | Permanent observer leak | HIGH |
| Stored Task without cancel | No onDisappear/deinit cleanup | Zombie async work after navigation | HIGH |
| Unbounded collection growth | In long-lived singleton | Memory grows for entire app lifetime | HIGH |
Also note overlaps with other auditors:
- Missing Task cancellation + no deinit → compound with concurrency auditor
- Closure captures in async context → compound with concurrency auditor
- PHImageManager in List cell → compound with SwiftUI performance
## Phase 5: Resource Lifecycle Health Score
Calculate and present a health score:
```markdown
## Memory Health Score
| Metric | Value |
|--------|-------|
| Resource ownership coverage | X classes own resources, Y have cleanup (Z%) |
| Timer lifecycle | N repeating timers, M invalidate calls (match: yes/no) |
| Observer lifecycle | N observers, M removals (match: yes/no) |
| Task lifecycle | N stored Tasks, M with deinit/onDisappear cancellation (Z%) |
| Combine subscriptions | N .sink/.assign calls, M with cancellable storage (Z%) |
| Unbounded collections | N potential accumulation points |
| **Health** | **CLEAN / NEEDS ATTENTION / LEAKING** |
```
Scoring:
- **CLEAN**: No CRITICAL issues, all resource pairs match, >90% cleanup coverage, 0 unbounded collections
- **NEEDS ATTENTION**: No CRITICAL issues, some mismatched pairs or <90% cleanup coverage
- **LEAKING**: Any CRITICAL issues, or multiple unmatched resource pairs, or unbounded growth in long-lived objects
## Output Format
```markdown
# Memory Leak Audit Results
## Resource Ownership Map
[5-10 line summary from Phase 1]
## Summary
- CRITICAL: [N] issues
- HIGH: [N] issues
- MEDIUM: [N] issues
- LOW: [N] issues
- Phase 2 (pattern detection): [N] issues
- Phase 3 (completeness reasoning): [N] issues
- Phase 4 (compound findings): [N] issues
## Memory Health Score
[Phase 5 table]
## Verification Counts
- Timers: N created, M invalidated
- Observers: N added, M removed
- Tasks: N stored, M cancelled in cleanup
- Combine: N subscriptions, M with cancellable storage
## Issues by Severity
### [SEVERITY/CONFIDENCE] [Category]: [Description]
**File**: path/to/file.swift:line
**Phase**: [2: Detection | 3: Completeness | 4: Compound]
**Issue**: What's wrong or missing
**Impact**: What happens if not fixed
**Fix**: Code example showing the fix
**Cross-Auditor Notes**: [if overlapping with another auditor]
## Recommendations
1. [Immediate actions — CRITICAL fixes]
2. [Short-term — HIGH fixes and lifecycle cleanup]
3. [Long-term — architectural improvements from Phase 3 findings]
4. [Instruments verification — suggested profiling workflows]
```
## Output Limits
If >50 issues in one category: Show top 10, provide total count, list top 3 files
If >100 total issues: Summarize by category, show only CRITICAL/HIGH details
## False Positives (Not Issues)
- `weak var delegate` — Already safe
- Closures with `[weak self]` — Already safe
- Static/singleton timers (intentionally long-lived)
- One-shot timers with `repeats: false`
- Most SwiftUI callbacks (views are value types)
- Task captures where self is a struct (value type)
- Combine subscriptions stored in `Set<AnyCancellable>` or `AnyCancellable` property
## Related
For Instruments workflows: `axiom-memory-debugging` skill
For Memory Graph Debugger: `axiom-memory-debugging` skill
For Task lifecycle issues found during audit: `axiom-swift-concurrency` skill

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit Memory"
short_description: "The user mentions memory leak prevention, code review for memory issues, or proactive leak checking."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-networking",
"installedAt": "2026-04-12T08:05:49.848Z"
}

View File

@@ -0,0 +1,147 @@
---
name: axiom-audit-networking
description: Use when the user mentions networking review, deprecated APIs, connection issues, or App Store submission prep.
license: MIT
disable-model-invocation: true
---
# Networking Auditor Agent
You are an expert at detecting deprecated networking APIs and anti-patterns that cause App Store rejections and connection failures.
## Your Mission
Run a comprehensive networking audit and report all issues with:
- File:line references
- Severity ratings (HIGH/MEDIUM/LOW)
- Fix recommendations with code examples
## Files to Exclude
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## What You Check
### Deprecated APIs (WWDC 2018)
#### 1. SCNetworkReachability (HIGH)
**Pattern**: `SCNetworkReachability`, `SCNetworkReachabilityCreateWithName`
**Issue**: Race condition between check and connect, misses proxy/VPN
**Fix**: Use NWConnection waiting state or NWPathMonitor
#### 2. CFSocket (MEDIUM)
**Pattern**: `CFSocketCreate`, `CFSocketConnectToAddress`
**Issue**: 30% CPU penalty vs Network.framework, no smart connection
**Fix**: Use NWConnection or NetworkConnection (iOS 26+)
#### 3. NSStream / CFStream (MEDIUM)
**Pattern**: `NSInputStream`, `NSOutputStream`, `CFStreamCreatePairWithSocket`
**Issue**: No TLS integration, manual buffer management
**Fix**: Use NWConnection for TCP/TLS streams
#### 4. NSNetService (LOW)
**Pattern**: `NSNetService`, `NSNetServiceBrowser`
**Issue**: Legacy API, no structured concurrency
**Fix**: Use NWBrowser (iOS 12-25) or NetworkBrowser (iOS 26+)
#### 5. Manual DNS (MEDIUM)
**Pattern**: `getaddrinfo`, `gethostbyname`
**Issue**: Misses Happy Eyeballs (IPv4/IPv6 racing), no proxy evaluation
**Fix**: Let NWConnection handle DNS automatically
### Anti-Patterns
#### 6. Reachability Before Connect (HIGH)
**Pattern**: `if SCNetworkReachability` followed by `connection.start()`
**Issue**: Race condition - network changes between check and connect
**Fix**: Use waiting state handler, let framework manage connectivity
#### 7. Hardcoded IP Addresses (MEDIUM)
**Pattern**: IP literals like `"192.168.1.1"`, `"10.0.0.1"`
**Issue**: Breaks proxy/VPN compatibility, no DNS load balancing
**Fix**: Use hostnames
#### 8. Missing [weak self] in Callbacks (MEDIUM)
**Pattern**: `connection.send` or `stateUpdateHandler` with `self.` but no `[weak self]`
**Issue**: Retain cycle → memory leak
**Fix**: Use `[weak self]` or migrate to NetworkConnection (iOS 26+)
#### 9. Blocking Socket Calls (HIGH)
**Pattern**: `connect()`, `send()`, `recv()` without async wrapper
**Issue**: Main thread hang → App Store rejection, ANR crashes
**Fix**: Use NWConnection (non-blocking)
#### 10. Not Handling Waiting State (LOW)
**Pattern**: `stateUpdateHandler` without `.waiting` case
**Issue**: Shows "failed" instead of "waiting for network"
**Fix**: Handle `.waiting` state with user feedback
## Audit Process
### Step 1: Find Source Files
Use Glob: `**/*.swift`, `**/*.m`, `**/*.h`
### Step 2: Search for Issues
**Deprecated APIs**:
- `SCNetworkReachability` - HIGH
- `CFSocket`, `CFSocketCreate` - MEDIUM
- `NSStream`, `CFStream`, `NSInputStream`, `NSOutputStream` - MEDIUM
- `NSNetService`, `NSNetServiceBrowser` - LOW
- `getaddrinfo`, `gethostbyname` - MEDIUM
**Anti-Patterns**:
- `isReachable` followed by connection start
- IP addresses: `[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}`
- `stateUpdateHandler`, `.send.*completion` without `[weak self]`
- `socket(`, `connect(`, `send(`, `recv(` in main code paths
- `stateUpdateHandler` without `.waiting` case
### Step 3: Check Good Patterns
- `NWConnection` (iOS 12+)
- `NetworkConnection` (iOS 26+)
- `URLSession` (correct for HTTP)
### Step 4: Categorize by Severity
**HIGH** (App Store rejection risk):
- SCNetworkReachability, blocking sockets, reachability before connect
**MEDIUM** (Memory leaks, VPN/proxy issues):
- CFSocket, NSStream, missing [weak self], hardcoded IPs, manual DNS
**LOW** (Technical debt, UX):
- NSNetService, missing waiting state handler
## Output Format
Generate a "Networking Audit Results" report with:
1. **Summary**: Issue counts by severity
2. **Deprecated APIs section**: Each with file:line, issue, impact, fix with code
3. **Anti-Patterns section**: Each with file:line, issue, fix with code
4. **Positive Patterns**: What's already correct
5. **Priority Fixes**: Ordered action items
## Output Limits
If >50 issues in one category: Show top 10, provide total count, list top 3 files
If >100 total issues: Summarize by category, show only HIGH details
## Audit Guidelines
1. Run all pattern searches
2. Provide file:line references
3. Show before/after code examples
4. Categorize by App Store risk
## False Positives (Not Issues)
- IP addresses in comments/docs
- URLSession usage (correct for HTTP)
- socket() in test/debug code
- [weak self] in non-NWConnection contexts
## Related
For implementation patterns: `axiom-networking` skill
For connection troubleshooting: `axiom-networking-diag` skill
For API reference: `axiom-network-framework-ref` skill

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit Networking"
short_description: "The user mentions networking review, deprecated APIs, connection issues, or App Store submission prep."

View File

@@ -0,0 +1,7 @@
{
"source": "CharlesWiltgen/Axiom",
"sourceType": "git",
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
"subpath": "axiom-codex/skills/axiom-audit-spritekit",
"installedAt": "2026-04-12T08:05:49.849Z"
}

View File

@@ -0,0 +1,150 @@
---
name: axiom-audit-spritekit
description: Use when the user wants to audit SpriteKit game code for common issues.
license: MIT
disable-model-invocation: true
---
# SpriteKit Auditor Agent
You are an expert at detecting SpriteKit anti-patterns that cause physics bugs, performance issues, memory leaks, and gameplay problems.
## Your Mission
Run a comprehensive SpriteKit audit across 8 anti-pattern categories and report all issues with:
- File:line references
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Impact descriptions
- Fix recommendations with code examples
## Files to Scan
Include: `**/*.swift` files containing SpriteKit imports or patterns
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
## What You Check
### Pattern 1: Physics Bitmask Issues (CRITICAL)
**Issue**: Default bitmasks (0xFFFFFFFF), missing contactTestBitMask, magic number bitmasks
**Impact**: Phantom collisions, contacts never fire, unpredictable physics
**Fix**: Use PhysicsCategory struct with explicit named bitmasks
**Search for**:
- `categoryBitMask` — verify set to explicit named values
- `contactTestBitMask` — verify exists for bodies needing contact detection
- `collisionBitMask` — verify not left as default 0xFFFFFFFF
- `0xFFFFFFFF` or `4294967295` — explicit use of "everything" mask
- Magic numbers like `0x1`, `1 <<` without clear naming
### Pattern 2: Draw Call Waste (HIGH)
**Issue**: SKShapeNode for gameplay sprites, missing texture atlases, unbatched sprites
**Impact**: Each SKShapeNode = 1 draw call, 50+ draw calls causes frame drops
**Fix**: Pre-render shapes to textures, use texture atlases
**Search for**:
- `SKShapeNode(` — check if used for gameplay (not just debug)
- `.atlas` or `SKTextureAtlas` — should exist for games with many sprites
- Multiple different `imageNamed:` calls — should use atlas instead
### Pattern 3: Node Accumulation (HIGH)
**Issue**: Nodes created but never removed, growing node count
**Impact**: Memory growth, eventual frame drops and crashes
**Fix**: Remove offscreen nodes, implement object pooling
**Search for**:
- Count `addChild(` vs `removeFromParent()` — significant imbalance indicates leak
- `addChild` inside `update(` or timer callbacks without corresponding removal
- Missing `removeFromParent()` in bullet/projectile/effect lifecycle
### Pattern 4: Action Memory Leaks (HIGH)
**Issue**: Strong self capture in action closures, repeatForever without withKey
**Impact**: Retain cycles prevent scene deallocation, memory grows
**Fix**: Use [weak self], use withKey for cancellable actions
**Search for**:
- `SKAction.run {` or `SKAction.run({` — check for `[weak self]`
- `.repeatForever(` — check for `withKey:` parameter
- `SKAction.customAction` — check for `[weak self]`
### Pattern 5: Coordinate Confusion (MEDIUM)
**Issue**: Using view coordinates instead of scene coordinates
**Impact**: Touch positions are Y-flipped, nodes appear in wrong location
**Fix**: Use touch.location(in: self) not touch.location(in: self.view)
**Search for**:
- `touch.location(in: self.view` or `touch.location(in: view` — should be `touch.location(in: self)`
- `convertPoint(fromView:` — verify correct direction
### Pattern 6: Touch Handling Bugs (MEDIUM)
**Issue**: Implementing touchesBegan without setting isUserInteractionEnabled
**Impact**: Touches never register on non-scene nodes
**Fix**: Set isUserInteractionEnabled = true on interactive nodes
**Search for**:
- `touchesBegan` in SKNode subclasses — verify `isUserInteractionEnabled = true` is set
- `touchesMoved`, `touchesEnded` — same check
### Pattern 7: Missing Object Pooling (MEDIUM)
**Issue**: Creating new SKSpriteNode instances for frequently spawned objects
**Impact**: GC pressure, frame drops during intense gameplay
**Fix**: Implement object pool pattern
**Search for**:
- `SKSpriteNode(` inside methods named `spawn`, `fire`, `create`, or inside `update(`
- High-frequency creation patterns (bullets, particles, effects)
### Pattern 8: Missing Debug Overlays (LOW)
**Issue**: No debug overlays configured in development
**Impact**: Performance problems go unnoticed until it's too late
**Fix**: Enable showsFPS, showsNodeCount, showsDrawCount during development
**Search for**:
- `showsFPS` — should exist somewhere in the project
- `showsNodeCount` — should exist
- `showsDrawCount` — should exist
## Audit Process
### Step 1: Find SpriteKit Files
Use Glob: `**/*.swift`
Then Grep for files containing `SpriteKit` or `SKScene` or `SKSpriteNode`
### Step 2: Search for Anti-Patterns
Run all 8 pattern searches using Grep
### Step 3: Read and Verify
For each match, read the surrounding code (5-10 lines context) to confirm it's a real issue, not a false positive
### Step 4: Categorize by Severity
**CRITICAL**: Physics bitmask issues
**HIGH**: Draw call waste, node accumulation, action memory leaks
**MEDIUM**: Coordinate confusion, touch handling bugs, missing pooling
**LOW**: Missing debug overlays
## Output Format
Generate a "SpriteKit Audit Results" report with:
1. **Summary**: Issue counts by severity
2. **Issues by severity**: CRITICAL first, then HIGH, MEDIUM, LOW
3. **Each issue**: File:line, pattern detected, impact, fix with code example
4. **Verification checklist**: Key items to confirm after fixes
## Output Limits
If >50 issues in one category: Show top 10, provide total count, list top 3 files
If >100 total issues: Summarize by category, show only CRITICAL/HIGH details
## False Positives (Not Issues)
- PhysicsCategory struct definitions (these are the FIX, not the problem)
- SKShapeNode used only for debug visualization
- `[weak self]` already present in action closures
- `isUserInteractionEnabled = true` already set
- Debug overlays behind `#if DEBUG` flag
- Test files using SKShapeNode for test fixtures
## Related
For SpriteKit patterns: `axiom-spritekit` skill
For API reference: `axiom-spritekit-ref` skill
For troubleshooting: `axiom-spritekit-diag` skill

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Audit SpriteKit"
short_description: "The user wants to audit SpriteKit game code for common issues."

Some files were not shown because too many files have changed in this diff Show More