ios-dev — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ios-dev (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
npx expo run:ios (local builds, no EAS)npx create-expo-app@latest <app-name> --template blank-typescript
npx expo install expo-router react-native-safe-area-context react-native-screensSet "strict": true in tsconfig. Configure app.json bundleIdentifier, version: "1.0.0", ios.buildNumber: "1". Install all required libraries before writing features.
Version discipline — Apple rejects without this: version is semver, bump manually per App Store release. ios.buildNumber increments per TestFlight upload. Expo never auto-bumps either.
The supabase skill handles everything else. These three things differ from web:
1. Install
npx expo install @supabase/supabase-js @react-native-async-storage/async-storage \
expo-secure-store expo-auth-session expo-web-browser2. Client config — SecureStore adapter + detectSessionInUrl: false (breaks auth without it):
import { createClient } from '@supabase/supabase-js'
import * as SecureStore from 'expo-secure-store'
export const supabase = createClient(
process.env.EXPO_PUBLIC_SUPABASE_URL!, // EXPO_PUBLIC_ prefix required
process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,
{
auth: {
storage: { getItem: SecureStore.getItemAsync, setItem: SecureStore.setItemAsync, removeItem: SecureStore.deleteItemAsync },
autoRefreshToken: true, persistSession: true,
detectSessionInUrl: false,
},
}
)3. OAuth — web redirect flow doesn't work in RN:
import * as AuthSession from 'expo-auth-session'
import * as WebBrowser from 'expo-web-browser'
WebBrowser.maybeCompleteAuthSession()
const redirectUri = AuthSession.makeRedirectUri({ scheme: 'your-scheme' })
const { data } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: { redirectTo: redirectUri, skipBrowserRedirect: true },
})
if (data.url) await WebBrowser.openAuthSessionAsync(data.url, redirectUri)Register scheme in app.json. Register redirect URI in Supabase dashboard → Auth → URL Configuration.
Storage uploads — direct URI doesn't work, read via expo-file-system as base64 first.
npx expo run:ios --simulator "iPhone 17 Pro" # simulator — use --simulator, NOT --device
# --device targets physical devices and requires code signingBoot + open:
xcrun simctl boot "iPhone 17 Pro" 2>/dev/null || true && open -a Simulator`xcrun simctl io booted tap` does NOT exist. Do not attempt it.
Options for driving UI without touch injection:
1. Debugger-driven navigation (preferred for validation)
Wire up a globalThis.__navigateTo helper in __DEV__ so the Metro debugger can drive navigation and read/write Zustand state without UI interaction:
// App.tsx or root layout — DEV only
import { createNavigationContainerRef } from '@react-navigation/native';
export const navigationRef = createNavigationContainerRef<any>();
if (__DEV__) {
(globalThis as any).__navigateTo = (name: string) => {
if (navigationRef.isReady()) navigationRef.navigate(name as never);
};
}Connect from Node.js:
const WebSocket = require('ws');
// get device id: GET http://localhost:8081/json
const ws = new WebSocket('ws://localhost:8081/inspector/debug?device=<id>&page=1');
ws.on('open', () => {
ws.send(JSON.stringify({
id: 1, method: 'Runtime.evaluate',
params: { expression: '__navigateTo("ProfileScreen")' }
}));
});Use Runtime.evaluate to call __navigateTo, read/write Zustand stores, assert UI state.
2. `xcrun simctl io booted screenshot <path>` — always works, any process:
xcrun simctl io booted screenshot /tmp/sim.png3. Physical mouse clicks on Simulator — only when Simulator is frontmost app.
4. `xcrun simctl io booted sendPasteboard` + keyboard shortcut — for text input.
Run after every feature. Do not declare done until all pass.
npx tsc --noEmit # must be clean
npx expo-doctor # must be clean
xcrun simctl boot "iPhone 17 Pro" 2>/dev/null || true && open -a Simulator
npx expo run:ios --simulator "iPhone 17 Pro"For each screen: navigate (via debugger or manual click) → screenshot → verify renders → exercise interactive elements → screenshot → verify result. Check empty/loading/error states.
xcrun simctl io booted screenshot /tmp/sim.pngRepeat on iPhone SE:
xcrun simctl boot "iPhone SE (3rd generation)" 2>/dev/null || true
npx expo run:ios --simulator "iPhone SE (3rd generation)"Sign-off checklist:
tsc --noEmit cleanexpo-doctor cleanexpo run:ios, not just restarting Metro. Same applies to changes to babel.config.js — module IDs change at the native boundary. Rule: anything that affects the Metro module graph at the native boundary needs a full rebuild.--simulator "iPhone 17 Pro" to target a sim by name. --device targets physical devices and will trigger code signing prompts if one is paired.<SafeAreaProvider>, screens need <SafeAreaView> — missing = content under notch<KeyboardAvoidingView behavior="padding">expo run:ios dev buildsbabel-preset-expo, not @react-native/babel-preset — the latter causes crashesreferences/eas-testflight.md — TestFlight/App Store submission when readyreferences/common-libraries.md — curated Expo-compatible library listreferences/plugin-architecture.md — recommended structure for extensible apps~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.