mrmarino023/light-ios-simulator

5

stars

55

commits

Rust

primary language

Aug 29, 2026

updated

README

LIGH agent opens Messages and types a pitch line

LIGH

Truth machine for iOS coding agents.
Fail-closed certify · structured repair (TRAIL) · scored eval/CI — not another tap MCP.
MIT · macOS + Xcode

The bet · Results · Scorepack · Install · How it works


The bet

Maestro proves durable UI flows. XcodeBuildMCP builds. LIGH proves the agent's Swift change — with ok: true only, structured faults, and optional TRAIL repair.

Sold to people who cannot vibes-merge: agent eval harnesses, platforms, and CI on agent-authored PRs.
Compose with Maestro (E2E partner). Do not compete for “every Cursor user installs another MCP.”

BuyerJob
Eval / agent platformsFrozen scorepack → inject bug → agent or TRAIL → scoreboard
CI (agent PRs)Goal certify on critical paths → block merge on ok: false
Local Mac dogfoodSecondary — docs/AGENT_PARADISE.md

→ Strategy + distance: docs/SCOREPACK.md · vs stack: docs/COMPETITIVE.md


What it does

agent edits Swift → build → sim → prove → fault taxonomy → localize/repair → certify
  • ligh_test — goal-first verify; always writes .ligh/last-certify.json
  • TRAIL — prove → effect-class localize → ≤2 patches → certify (lab + scorepack)
  • BuildGovernor — serialize builds, memory backpressure, infra_oom (host plane)
  • 0 LLM UI tokens on motor (Autopilot)

Requires Mac + Xcode Simulator.

Agent Scorepack (start here if you buy the bet)

./scripts/gate-scorepack.sh --dry-run    # contract + scoreboard schema
./scripts/gate-scorepack.sh              # full TRAIL core pack (OPENAI_API_KEY + Mac)

Pack: scorepack/v1/manifest.json · CI: .github/workflows/ligh-scorepack.yml

Local certify (secondary)

./scripts/ligh-paradise.sh /path/to/MyApp.xcodeproj --build
LIGH_WORKSPACE=/path/to/app ./scripts/ligh-agent-loop.sh

MCP: ligh_initligh_testligh_viewer — dogfood, not the wedge.

AGENTS.md


Results

Same job for every column: a bug is injected into a real iOS app → the agent must fix the Swift and prove the fix in Simulator.
Wall = time to verified fix. Tokens = LLM tokens burned. ✓/✗ = postconditions passed.

What we compare

StackPlain English
Vision LLM agentWhat people do today: screenshots → LLM decides taps → LLM edits code in a long chat. No structured host repair.
Chat agent + LIGH tapsCoding agent still repairs in unconstrained chat, but uses LIGH Autopilot for UI instead of screenshots. Shows that better taps alone are not enough.
LIGH (TRAIL)Full LIGH repair path: host proves the failure → finds the file → ≤2 scoped LLM patches → rebuild → certify on the same flow.

Artifact: docs/TRAIL_RESULTS.md · trail-holy-compare-latest.json

Head-to-head (repair)

BugVision LLM agentChat agent + LIGH tapsLIGH (TRAIL)
Login never navigates (XCUITestDemo)622s · 212k tokens · ✗ failed61s · 14k · ✓33s · 1.3k · ✓
Notes tab missing (Kix)460s · 128k · ✓644s · 148k · ✓78s · 0 · ✓
Onboarding stuck (OnboardingDemo)(no vision A/B yet)(no A/B yet)64s · 4.4k · ✓

Read the Kix row: vision burned ~8 minutes / 128k tokens. Chat+taps was worse. LIGH restored the omitted tab from the broken tree (View type still present) and verified in ~78s / 0 LLM tokens.

LIGH vs Vision LLMLIGH vs Chat+taps
Login wall~19× faster (vision never verified)~1.9× faster
Kix wall~5.9× faster~8× faster
Login tokens~160× fewer~10× fewer

LIGH repair runs (absolute)

BugAppWallTokensFile localized
Login never navigatesXCUITestDemo33s1.3kLoginViewModel.swift
Onboarding stuckOnboardingDemo64s4.4kOnboardingView.swift
Notes tab missingKix78s0MainTabView.swift

3/3 verified ≤120s (L2* suite)trail-holy-multi-latest.json
How it works: docs/TRAIL_BULLETPROOF.md (architecture for every OSS app — not per-task patches)

Architecture (all OSS apps)

Same repair_engine.py for every vendored app — no task-id modes, no filename priors:

broken tree → StructuralKB → classify → causal localize → operators → LLM (if miss) → certify

Refuse unknown effects rather than edit the wrong file.

Stranger OSS apps (0 accessibility ids)

Architecture v5 — two products, same motor (docs/OSS_PIPELINE.md):

ProductInputKPI
Agent loop (primary)workspace / ligh_inittime-to-ok per patch
Stranger proofTier B --app / prebuilt .apptier_b_verify_pass
Cold git buildTier C URLhonest skip/benchmark — build fail ≠ LIGH broken
HostCapability → preflight_v2(SPM) → [Tier B: no build | Tier C: xcodebuild]
  → EyesReady → process_health → label-first discover → ligh_test
ClassMeaning
✓ passmotor-proven chrome + ligh_test ok
⊘ host-skipmissing_watchos_runtime, xcode_format_too_new, swift_tools_too_new, …
✗ hostsim_boot_hung / eyes_unusable — fix Simulator, not Swift
✗ appapp_crashed / app_not_running / discover_no_chrome / goal fail
crash ≠ chromerecent DiagnosticReports → app_crashed (never discover_no_chrome)

System surfaces (login / ASWebAuth / share / permission): hit-test occlusion → classify role → overlay: system_surface. Motor policy from role table (auth never auto-dismisses). → docs/ARCHITECTURE.md

Proven (label-first, 0 AX ids):

AppModeChromeResult
CountriesSwiftUIstrangermotor label
Food Truckstrangermotor label
MastodonTier B --appUnisciti a mastodon.socialligh_test · 10.5s · artifact
./scripts/gate-oss-stranger-batch.sh    # scripts/oss-stranger-urls.txt
./scripts/gate-oss-stranger-smoke.sh    # Countries + Food Truck only
# Tier B (primary stranger verify — no cold xcodebuild):
python3 scripts/ligh_oss_smoke.py --app /path/to/App.app --bundle-id bid --source-root /path/to/src

→ artifact oss-stranger-trial-latest.json · Mastodon Tier B oss-stranger-mastodon-tierb.json · contract docs/OSS_PIPELINE.md · vs Maestro docs/COMPETITIVE.md

LIGH Autopilot only (UI goals — not repair)

Separate claim: reach a UI goal with 0 LLM tokens for taps (host policy on accessibility). This is not the repair table above.

AppFlowWallStepsLLM UI tokens
LighFixtureform11.5s20
LighOnboardmulti-step wizard15.2s40
LighModalsheet overlay9.5s20
LighFeedlist drill-down8.2s10
XCUITestDemologin11.8s30
Kixcatalog + auth + tabs11.5s30

autopilot-generality-latest.json

Reproduce:

./scripts/gate-trail-holy-multi.sh          # L2* regression suite
./scripts/gate-autopilot-generality.sh      # UI-goal motor claim

Apps under test

Open-source / third-party (vendored)

AppUpstreamWhat we exercise
KixbyKosta/Kix-appLogin, catalog, tab chrome; Notes-tab repair task
XCUITestDemofixtures/third-party/XCUITestDemo (com.himali.XCUITestDemo)Login credentials + navigation repair

Local notes for Kix: fixtures/third-party/Kix/UPSTREAM.md

In-repo fixtures (flow shapes)

AppPathFlow shape
LighFixturefixtures/LighFixtureForm submit
LighOnboardfixtures/LighOnboardMulti-step wizard
LighModalfixtures/LighModalSheet / overlay
LighFeedfixtures/LighFeedList → detail
OnboardingDemofixtures/frozen/OnboardingDemoBlocked overlay / home gate

These are the surfaces used for Autopilot generality and TRAIL repair gates — not App Store binaries.


Install

Requirements: macOS, Xcode + iOS Simulator runtime, Rust.

git clone https://github.com/mrmarino023/light-ios-simulator.git
cd light-ios-simulator

# Option A — installer (puts ligh / lighd on PATH)
./scripts/install.sh

# Option B — init (release build + doctor + MCP snippet)
./scripts/ligh-init.sh

# Option C — from source only
unset CARGO_TARGET_DIR
cargo build --release -p ligh-cli -p ligh-daemon

Smoke:

./scripts/developer-trial.sh

Homebrew HEAD (from a clone): see Formula/README.md.


Connect Cursor

  1. Build release binaries (above).
  2. Print an MCP config with absolute paths:
./scripts/print-cursor-mcp.sh
  1. Paste into Cursor → Settings → MCP (or ~/.cursor/mcp.json).

Example shape:

{
  "mcpServers": {
    "ligh": {
      "command": "python3",
      "args": ["/ABS/PATH/light-ios-simulator/scripts/ligh_mcp.py"],
      "env": { "LIGH_BIN": "/ABS/PATH/light-ios-simulator/target/release/ligh" }
    }
  }
}
  1. In chat, use the prompt in docs/CURSOR_PROMPT.md. Short form:

Build my Debug Simulator .app, then verify with ligh_cap_app_job using accessibility identifiers. On { ok: false, fault }, fix source, rebuild, retry. Do not claim success without ok: true.

Full developer trial: docs/DEVELOPER_TRIAL.md

Useful MCP tools

ToolJob
ligh_perceiveSettled world + Feel IR
ligh_cap_autopilotGoal + params → path → verified (0 UI tokens)
ligh_cap_repair_jobTRAIL: prove → localize → fix → build → certify (task_path)
ligh_attemptAct + host verdict
ligh_cap_app_jobScripted wait/tap/type + assert
ligh_perceive_routedAX first; vision only if eyes fail

Expo / physical iPhone

Same agent loop. Different motors.

SimulatorPhysical (your Debug / Expo build)
EyesCoreSimulator AXDevDriver over LAN (@mm-labs/ligh-expo)
HandsIndigoHIDDevDriver → WDA cascade
# Vendor the Expo config plugin into your app
./scripts/sync-ligh-expo.sh /path/to/YourExpoApp

app.json:

{
  "expo": {
    "plugins": ["@mm-labs/ligh-expo"]
  }
}

Then rebuild native (npx expo run:ios or EAS development). JS reload is not enough after driver changes.

Package docs: packages/ligh-expo/README.md
Runbook: docs/PHYSICAL.md

Or during init:

./scripts/ligh-init.sh /path/to/YourExpoApp

How it works

Cursor MCP
    ↓
lighd  — Autopilot over Feel IR
    ↓
Simulator (CoreSimulator)  or  Physical (DevDriver eyes + WDA hands)
    ↓
Your Debug / Expo app

Feel IR (what the agent sees):

{
  "place": { "surface": "app", "title": "Welcome" },
  "salience": [
    { "rank": 1, "kind": "primary_button", "label": "Get Started" }
  ],
  "feel": { "phase": "settled", "ready": true },
  "suggest": { "intent": "tap", "label": "Get Started" }
}

TRAIL (when a goal fails):

TraceFailure → hybrid localize → constrained fix → build → certify

Architecture: docs/ARCHITECTURE.md


License

MIT

Contributors

mattisky999

44 commits

mrmarino023

8 commits

cursoragent

3 commits

mrmarino023/light-ios-simulator

5

stars

55

commits

Rust

primary language

Aug 29, 2026

updated

README

LIGH agent opens Messages and types a pitch line

LIGH

Truth machine for iOS coding agents.
Fail-closed certify · structured repair (TRAIL) · scored eval/CI — not another tap MCP.
MIT · macOS + Xcode

The bet · Results · Scorepack · Install · How it works


The bet

Maestro proves durable UI flows. XcodeBuildMCP builds. LIGH proves the agent's Swift change — with ok: true only, structured faults, and optional TRAIL repair.

Sold to people who cannot vibes-merge: agent eval harnesses, platforms, and CI on agent-authored PRs.
Compose with Maestro (E2E partner). Do not compete for “every Cursor user installs another MCP.”

BuyerJob
Eval / agent platformsFrozen scorepack → inject bug → agent or TRAIL → scoreboard
CI (agent PRs)Goal certify on critical paths → block merge on ok: false
Local Mac dogfoodSecondary — docs/AGENT_PARADISE.md

→ Strategy + distance: docs/SCOREPACK.md · vs stack: docs/COMPETITIVE.md


What it does

agent edits Swift → build → sim → prove → fault taxonomy → localize/repair → certify
  • ligh_test — goal-first verify; always writes .ligh/last-certify.json
  • TRAIL — prove → effect-class localize → ≤2 patches → certify (lab + scorepack)
  • BuildGovernor — serialize builds, memory backpressure, infra_oom (host plane)
  • 0 LLM UI tokens on motor (Autopilot)

Requires Mac + Xcode Simulator.

Agent Scorepack (start here if you buy the bet)

./scripts/gate-scorepack.sh --dry-run    # contract + scoreboard schema
./scripts/gate-scorepack.sh              # full TRAIL core pack (OPENAI_API_KEY + Mac)

Pack: scorepack/v1/manifest.json · CI: .github/workflows/ligh-scorepack.yml

Local certify (secondary)

./scripts/ligh-paradise.sh /path/to/MyApp.xcodeproj --build
LIGH_WORKSPACE=/path/to/app ./scripts/ligh-agent-loop.sh

MCP: ligh_initligh_testligh_viewer — dogfood, not the wedge.

AGENTS.md


Results

Same job for every column: a bug is injected into a real iOS app → the agent must fix the Swift and prove the fix in Simulator.
Wall = time to verified fix. Tokens = LLM tokens burned. ✓/✗ = postconditions passed.

What we compare

StackPlain English
Vision LLM agentWhat people do today: screenshots → LLM decides taps → LLM edits code in a long chat. No structured host repair.
Chat agent + LIGH tapsCoding agent still repairs in unconstrained chat, but uses LIGH Autopilot for UI instead of screenshots. Shows that better taps alone are not enough.
LIGH (TRAIL)Full LIGH repair path: host proves the failure → finds the file → ≤2 scoped LLM patches → rebuild → certify on the same flow.

Artifact: docs/TRAIL_RESULTS.md · trail-holy-compare-latest.json

Head-to-head (repair)

BugVision LLM agentChat agent + LIGH tapsLIGH (TRAIL)
Login never navigates (XCUITestDemo)622s · 212k tokens · ✗ failed61s · 14k · ✓33s · 1.3k · ✓
Notes tab missing (Kix)460s · 128k · ✓644s · 148k · ✓78s · 0 · ✓
Onboarding stuck (OnboardingDemo)(no vision A/B yet)(no A/B yet)64s · 4.4k · ✓

Read the Kix row: vision burned ~8 minutes / 128k tokens. Chat+taps was worse. LIGH restored the omitted tab from the broken tree (View type still present) and verified in ~78s / 0 LLM tokens.

LIGH vs Vision LLMLIGH vs Chat+taps
Login wall~19× faster (vision never verified)~1.9× faster
Kix wall~5.9× faster~8× faster
Login tokens~160× fewer~10× fewer

LIGH repair runs (absolute)

BugAppWallTokensFile localized
Login never navigatesXCUITestDemo33s1.3kLoginViewModel.swift
Onboarding stuckOnboardingDemo64s4.4kOnboardingView.swift
Notes tab missingKix78s0MainTabView.swift

3/3 verified ≤120s (L2* suite)trail-holy-multi-latest.json
How it works: docs/TRAIL_BULLETPROOF.md (architecture for every OSS app — not per-task patches)

Architecture (all OSS apps)

Same repair_engine.py for every vendored app — no task-id modes, no filename priors:

broken tree → StructuralKB → classify → causal localize → operators → LLM (if miss) → certify

Refuse unknown effects rather than edit the wrong file.

Stranger OSS apps (0 accessibility ids)

Architecture v5 — two products, same motor (docs/OSS_PIPELINE.md):

ProductInputKPI
Agent loop (primary)workspace / ligh_inittime-to-ok per patch
Stranger proofTier B --app / prebuilt .apptier_b_verify_pass
Cold git buildTier C URLhonest skip/benchmark — build fail ≠ LIGH broken
HostCapability → preflight_v2(SPM) → [Tier B: no build | Tier C: xcodebuild]
  → EyesReady → process_health → label-first discover → ligh_test
ClassMeaning
✓ passmotor-proven chrome + ligh_test ok
⊘ host-skipmissing_watchos_runtime, xcode_format_too_new, swift_tools_too_new, …
✗ hostsim_boot_hung / eyes_unusable — fix Simulator, not Swift
✗ appapp_crashed / app_not_running / discover_no_chrome / goal fail
crash ≠ chromerecent DiagnosticReports → app_crashed (never discover_no_chrome)

System surfaces (login / ASWebAuth / share / permission): hit-test occlusion → classify role → overlay: system_surface. Motor policy from role table (auth never auto-dismisses). → docs/ARCHITECTURE.md

Proven (label-first, 0 AX ids):

AppModeChromeResult
CountriesSwiftUIstrangermotor label
Food Truckstrangermotor label
MastodonTier B --appUnisciti a mastodon.socialligh_test · 10.5s · artifact
./scripts/gate-oss-stranger-batch.sh    # scripts/oss-stranger-urls.txt
./scripts/gate-oss-stranger-smoke.sh    # Countries + Food Truck only
# Tier B (primary stranger verify — no cold xcodebuild):
python3 scripts/ligh_oss_smoke.py --app /path/to/App.app --bundle-id bid --source-root /path/to/src

→ artifact oss-stranger-trial-latest.json · Mastodon Tier B oss-stranger-mastodon-tierb.json · contract docs/OSS_PIPELINE.md · vs Maestro docs/COMPETITIVE.md

LIGH Autopilot only (UI goals — not repair)

Separate claim: reach a UI goal with 0 LLM tokens for taps (host policy on accessibility). This is not the repair table above.

AppFlowWallStepsLLM UI tokens
LighFixtureform11.5s20
LighOnboardmulti-step wizard15.2s40
LighModalsheet overlay9.5s20
LighFeedlist drill-down8.2s10
XCUITestDemologin11.8s30
Kixcatalog + auth + tabs11.5s30

autopilot-generality-latest.json

Reproduce:

./scripts/gate-trail-holy-multi.sh          # L2* regression suite
./scripts/gate-autopilot-generality.sh      # UI-goal motor claim

Apps under test

Open-source / third-party (vendored)

AppUpstreamWhat we exercise
KixbyKosta/Kix-appLogin, catalog, tab chrome; Notes-tab repair task
XCUITestDemofixtures/third-party/XCUITestDemo (com.himali.XCUITestDemo)Login credentials + navigation repair

Local notes for Kix: fixtures/third-party/Kix/UPSTREAM.md

In-repo fixtures (flow shapes)

AppPathFlow shape
LighFixturefixtures/LighFixtureForm submit
LighOnboardfixtures/LighOnboardMulti-step wizard
LighModalfixtures/LighModalSheet / overlay
LighFeedfixtures/LighFeedList → detail
OnboardingDemofixtures/frozen/OnboardingDemoBlocked overlay / home gate

These are the surfaces used for Autopilot generality and TRAIL repair gates — not App Store binaries.


Install

Requirements: macOS, Xcode + iOS Simulator runtime, Rust.

git clone https://github.com/mrmarino023/light-ios-simulator.git
cd light-ios-simulator

# Option A — installer (puts ligh / lighd on PATH)
./scripts/install.sh

# Option B — init (release build + doctor + MCP snippet)
./scripts/ligh-init.sh

# Option C — from source only
unset CARGO_TARGET_DIR
cargo build --release -p ligh-cli -p ligh-daemon

Smoke:

./scripts/developer-trial.sh

Homebrew HEAD (from a clone): see Formula/README.md.


Connect Cursor

  1. Build release binaries (above).
  2. Print an MCP config with absolute paths:
./scripts/print-cursor-mcp.sh
  1. Paste into Cursor → Settings → MCP (or ~/.cursor/mcp.json).

Example shape:

{
  "mcpServers": {
    "ligh": {
      "command": "python3",
      "args": ["/ABS/PATH/light-ios-simulator/scripts/ligh_mcp.py"],
      "env": { "LIGH_BIN": "/ABS/PATH/light-ios-simulator/target/release/ligh" }
    }
  }
}
  1. In chat, use the prompt in docs/CURSOR_PROMPT.md. Short form:

Build my Debug Simulator .app, then verify with ligh_cap_app_job using accessibility identifiers. On { ok: false, fault }, fix source, rebuild, retry. Do not claim success without ok: true.

Full developer trial: docs/DEVELOPER_TRIAL.md

Useful MCP tools

ToolJob
ligh_perceiveSettled world + Feel IR
ligh_cap_autopilotGoal + params → path → verified (0 UI tokens)
ligh_cap_repair_jobTRAIL: prove → localize → fix → build → certify (task_path)
ligh_attemptAct + host verdict
ligh_cap_app_jobScripted wait/tap/type + assert
ligh_perceive_routedAX first; vision only if eyes fail

Expo / physical iPhone

Same agent loop. Different motors.

SimulatorPhysical (your Debug / Expo build)
EyesCoreSimulator AXDevDriver over LAN (@mm-labs/ligh-expo)
HandsIndigoHIDDevDriver → WDA cascade
# Vendor the Expo config plugin into your app
./scripts/sync-ligh-expo.sh /path/to/YourExpoApp

app.json:

{
  "expo": {
    "plugins": ["@mm-labs/ligh-expo"]
  }
}

Then rebuild native (npx expo run:ios or EAS development). JS reload is not enough after driver changes.

Package docs: packages/ligh-expo/README.md
Runbook: docs/PHYSICAL.md

Or during init:

./scripts/ligh-init.sh /path/to/YourExpoApp

How it works

Cursor MCP
    ↓
lighd  — Autopilot over Feel IR
    ↓
Simulator (CoreSimulator)  or  Physical (DevDriver eyes + WDA hands)
    ↓
Your Debug / Expo app

Feel IR (what the agent sees):

{
  "place": { "surface": "app", "title": "Welcome" },
  "salience": [
    { "rank": 1, "kind": "primary_button", "label": "Get Started" }
  ],
  "feel": { "phase": "settled", "ready": true },
  "suggest": { "intent": "tap", "label": "Get Started" }
}

TRAIL (when a goal fails):

TraceFailure → hybrid localize → constrained fix → build → certify

Architecture: docs/ARCHITECTURE.md


License

MIT

Contributors

mattisky999

44 commits

mrmarino023

8 commits

cursoragent

3 commits

Languages

Rust

47.8%

Python

28.0%

Shell

12.6%

Objective-C

10.7%