Skip to content

Releases: SyncTek-LLC/simdrive

simdrive 1.0.0a9 — recording state contracts

Choose a tag to compare

@SyncTekLLC SyncTekLLC released this 11 May 15:48
7703fa7

[1.0.0a9] — 2026-05-11 (alpha — recording state contracts)

Closes a class of replay failure where a divergent app state silently
executed dozens of blind taps. Implements the recording state contract
proposed by the Palace dogfood team
(simdrive/docs/FEATURE_REQUEST_STATE_CONTRACT_2026_05_11.md): every
recording now carries a requires: block captured automatically at
record_start and verified at replay step −1.

The bug a9 closes: in the Palace a8 dogfood, a replay of the
SAML-signin recording against a fresh-install state (with a notifications
permission alert visible at step 0) silently executed 23 taps at SSIM
0.014, reporting ok: true. First tap meant for "Add Library" actually
hit "Don't Allow"; the remaining 22 landed on arbitrary UI. Drift detection
was post hoc — by the time the user knew, the app had been touched 23
times. Third occurrence of this failure mode against Palace; a9 closes it
at the schema level so individual projects no longer have to invent local
workarounds.

Added — state contract on recordings

  • requires: schema in recording YAML. Captured, not authored. Three
    sub-blocks:
    • requires.app.{bundle_id, version, version_match}version_match
      is one of exact | minor | major | any (default minor).
    • requires.sim.{device, ios_version}ios_version accepts a raw
      string or a semver predicate (>=18.0, <19.0, !=18.2, etc.).
    • requires.initial_state.{foreground, text_subset_required, text_subset_forbidden, primary_button_label} — captured by OCRing
      the step-0 screen via the existing som.detect_marks() pipeline.
  • Auto-population at record_start — observes the live screen,
    captures app bundle/version + sim device/iOS + top text marks. User
    doesn't write the contract; the camera does.
  • Verification at replay step −1 — before any step executes, observes
    the live state and compares against requires:. On mismatch, halts
    with halt_reason: "state_contract_mismatch", halted_at: 0,
    structured expected / actual / remedy payload. If a permission
    alert is detected in actual.text_subset_present (heuristic on
    "Don't Allow" / "Allow" / "OK" / "Cancel"), the remedy field points
    at xcrun simctl privacy ... grant ... rather than generic advice.
  • New replay parameter: halt_on_state_mismatch (default True).
    Today's "run anyway" behavior is now opt-in by passing False — a
    warning is still emitted in the response.
  • Deprecation warning for unannotated recordings: recordings without
    a requires: block replay with _simdrive_warning: "Recording has no \requires:` block. State contract not verified. Run
    `simdrive migrate-recording ` to capture one."` Existing
    recordings continue to work.

Added — lint + migrate tooling

  • simdrive lint-recordings [--path DIR] [--quiet] [--json] CLI
    command. Walks the directory tree, prints [OK] / [FAIL] per
    recording, exits non-zero on any FAIL. --json for programmatic
    consumption (CI verify-pr.sh integration).
  • simdrive migrate-recording <name> [--force] [--dry-run] CLI
    command. Re-OCRs the step-0 pre_screenshot of a pre-a9 recording,
    builds a RequiresBlock, writes the YAML back in place with a
    .pre-migrate.bak sibling for recovery.
  • Both also exposed as MCP tools (lint_recordings, migrate_recording)
    for agentic / CI consumers. Tool count: 30 → 32.

Internal

  • Refactored _capture_state_contract to share _build_requires_block
    (pure transform on marks + app/sim metadata) with
    migrate_recording — capture-time and migrate-time paths produce
    identical RequiresBlock shapes by construction.
  • robustness.validate_replay() now tolerates the new requires:
    top-level key (was: would have rejected as unknown field).
  • One e2e test (test_replay_halts_on_drift_when_screen_diverges)
    updated to opt out of the new step-0 contract check — it deliberately
    diverges state to test the per-step drift path, which a9.0 would
    otherwise short-circuit.

Tests

  • 38 new tests across a9.0 (17) + a9.1 (21).
  • Suite total: 1.0.0a8 baseline 711 → 1.0.0a9 749 passing, 1 skipped.

Known (carried forward from a8)

  • D6 (empty idevicesyslog output on real devices) still investigation-
    only — awaiting Palace dogfood capture of
    idevicesyslog -u <udid> 2>&1 for root-cause selection. See
    simdrive/docs/D6_LOGS_INVESTIGATION.md.

simdrive 1.0.0a8 — device session usable on real hardware

Choose a tag to compare

@SyncTekLLC SyncTekLLC released this 06 May 20:02
366bb30

[1.0.0a8] — 2026-05-06 (alpha — device session usable on real hardware)

First end-to-end real-device dogfood (a7, iPhone 17 Pro Max "Moes Max",
iOS 26.3.1, 2026-05-06) catalogued 12 device-session bugs in
simdrive/docs/DOGFOOD_FEEDBACK_2026_05_06_MOES_MAX.md. a8 fixes 11 of
12; the twelfth (D6, empty idevicesyslog output) ships as
investigation-only because the original simctl diagnosis was wrong and
the right fix differs by root cause — see
simdrive/docs/D6_LOGS_INVESTIGATION.md.

Net effect: a7 = "WDA bootstraps but MCP can't drive it"; a8 = "MCP
device session is usable end-to-end". Suite went 678 → 711 passed.

Fixed — priority unlock (turns "unusable" into "usable")

  • D3 device.launch_app no longer passes --start-stopped=false
    modern devicectl rejected the flag, blocking every session_start
    with app_bundle_id.
  • D2 session_start target=device now opens a default WDA test
    session unconditionally (with or without app_bundle_id); input verbs
    no longer hit wda_session_not_open.
  • D1 observe gates screenshot_b64 behind a new
    include_screenshot_b64 param (default false). The unconditional
    101k-char b64 payload was overflowing the MCP token budget on real
    devices. screenshot_path is still returned by default.

Fixed — daemonization

  • B3 bootstrap-device now passes start_new_session=True to the
    xcodebuild Popen and redirects stdout/stderr to
    ~/.simdrive/wda/<udid>.log; PID written to
    ~/.simdrive/wda/<udid>.pid. WDA survives bootstrap exit; users no
    longer need a manual nohup workaround.
  • B3 new CLI: simdrive wda-up <udid> (relaunch from registry
    without rebuilding) and simdrive wda-down <udid> (SIGTERM via
    pidfile).

Fixed — simctl-leak cluster (device sessions stop using simctl)

  • D4 Device-launch failure raises device_launch_failed with a
    devicectl-aware recovery hint instead of no_device with a simctl
    hint.
  • D5 tool_apps for target=device now queries
    xcrun devicectl device info apps --device <udid> --json-output -
    instead of returning {apps: []} from a simctl-only call.
  • D7 tool_app_state for target=device now queries
    xcrun devicectl device info processes ... instead of leaking
    simctl's "Invalid device" error string.
  • New helper simdrive.diagnostics._devicectl_info_json underlies both.

Fixed — bootstrap polish

  • B1 verify_xcode_account_for_team(team_id) now scans the parsed
    plist output of defaults read com.apple.dt.Xcode DVTDeveloperAccountManagerAppleIDLists for an actual team binding
    (teamID/teamIDs/DVTDeveloperAccountTeamID) instead of grepping
    for the literal substring "identifier" (which matched any non-empty
    account list and let bootstraps proceed when no account was bound to
    the requested team).
  • B2 When multiple Apple Development certs share the requested
    team_id, the most-recently-issued cert is auto-picked instead of
    raising wda_signing_ambiguous. Ambiguity is still raised when
    team_id was not provided.
  • B4 When xcodebuild build-for-testing emits a BUILD FAILED
    before its -allowProvisioningUpdates retry succeeds, the FAILED line
    is logged at DEBUG with a single INFO-level summary
    ("First attempt failed pending provisioning fetch; retry succeeded
    …expected"). Stops scaring users on first-run bootstraps.

Fixed — session metadata

  • D8 bootstrap-device now captures device name from
    xcrun devicectl device info details and iOS version from WDA
    /status; ~/.simdrive/wda/<udid>.json carries device_name and
    os_version keys; tool_session_start returns these instead of
    device: "Real Device", os_version: "".

Investigated (not fixed — gated to a9)

  • D6 tool_logs returns empty on real devices. The original a7
    report blamed simctl, but the device path actually uses
    idevicesyslog. Five candidate root causes ranked
    (1s-warmup-too-short → broken brew binary → over-aggressive predicate
    → DEVNULL'd stderr → Developer Mode regression) in
    simdrive/docs/D6_LOGS_INVESTIGATION.md. a9 will pick the right fix
    after Lyrasis dogfood captures idevicesyslog -u <udid> 2>&1 against
    Palace on a real device.

Tests

  • 33 new tests across the four fix waves (1.0.0a7 baseline 678 →
    1.0.0a8 711 passing, 1 skipped).
  • D5/D7 verified live against an iPad on iOS 26 / Xcode 26 (devicectl
    JSON shape confirmed).

Known

  • CHANGELOG history for a3 → a7 not yet backfilled (gap pre-dates a8).
  • Pre-existing iOS 26 simulator TextField-focus timing flake in
    test_e2e_testkit.py::test_type_text_followed_by_submit_produces_result
    can intermittently fail on busy hosts; passes on isolated rerun and
    full suite re-runs.

simdrive v0.3.0a3 — Palace v0.3.0a2 dogfood fixes

Choose a tag to compare

@SyncTekLLC SyncTekLLC released this 01 May 05:24

Fix-it release responding to Palace's dogfood report against v0.3.0a2.

One HIGH-severity (type_text was reporting wrong focus signal under HID dispatch), two MEDIUM (OCR confidence on stylized covers, stale-MCP-after-upgrade), and four QoL additions plus a first-pass docs starter set.

Fixed

  • type_text reports injection_method and dispatch_succeeded. Soft-keyboard heuristic was the wrong signal under HID dispatch — the keystrokes always land but the keyboard isn't drawn. New fields are reliable; the legacy keyboard_visible and focused_field stay for cliclick-path debugging.
  • OCR confidence is dictionary-gated. Stylized covers used to OCR as "Sary of the Canadan liothest" with confidence 1.0. New confidence_band (high / medium / low) and a clamped legacy confidence field flag misreads even when the OCR engine reports high internal confidence. raw_confidence exposes the unclamped score for diagnostics.
  • Stale-MCP detection. When the loaded simdrive version differs from the version on disk (after pip install --upgrade without restarting), every tool response carries a _simdrive_warning side-channel field flagging the drift.

Added

  • version MCP tool. Zero-arg → {version, loaded_at, disk_version, drift}. No more guessing whether the running server matches the on-disk package.
  • clear_field MCP tool + type_text(clear_first: true) flag. Sends Cmd-A then delete via HID. Replaces the five-press_key idiom for clearing search fields.
  • Icon-glyph semantic-name aliases. find_by_text(marks, "search") now matches the magnifying-glass OCR-misread `"Q/"`. Seed whitelist covers search, back, forward, settings, menu, close, add. Palace can extend.
  • docs/LIMITATIONS.md and docs/BEST_PRACTICES.md. First-pass docs covering documentation-only items from the dogfood: Dynamic Island modals, xctrace ceiling, MFA hard-wall, HID + debounce-window rule, text-resolution rapid-cycle fallback.

Install

```bash
pip install --pre --upgrade simdrive
```

After upgrade, restart your MCP host (the new stale-MCP detector will warn you if you forget).

Numbers

  • 29 MCP tools (was 27 — added version and clear_field)
  • 91 / 91 unit tests passing (11 new)
  • Live smoke verified type_text returns injection_method="hid" + dispatch_succeeded=True on iOS 26.3 sim

Full changelog

See `simdrive/CHANGELOG.md`.

simdrive v0.3.0a1 — SpecterQA parity round 1 (13 → 27 tools)

Choose a tag to compare

@SyncTekLLC SyncTekLLC released this 30 Apr 16:28

First parity sprint after the cutover. simdrive grows from 13 to 27 MCP tools, closing the major gaps that kept Palace's full SpecterQA migration from being a clean cut.

Headline: native performance monitoring on iOS simulators with no XCTest required.

Performance monitoring

  • `perf` — CPU%, memory RSS, thread count for the active app
  • `perf_baseline` — capture a labeled baseline, stored per-session
  • `perf_compare` — diff a current snapshot against a baseline; severity bands (high / medium / low)
  • `memory` — detailed memory breakdown (footprint, dirty, clean, reclaimable, peak) via macOS `footprint`

Diagnostics

  • `doctor` — environment readiness (Xcode CLT, simctl, runtimes, booted devices, native HID helper)
  • `app_state` — foreground / background / suspended / not-running
  • `apps` — list installed apps on a sim (handles `simctl listapps` OpenStep plist via `plutil` round-trip)
  • `crashes` — `.ips` retrieval from `~/Library/Logs/DiagnosticReports`, filterable by session-start time and bundle id

Robustness

  • `dismiss_first_launch_alerts` — taps Allow / Don't Allow on permission alerts. Includes the v0.1 dogfood 1-in-4 alert-race fix: re-observe 200 ms post-tap, retry once if the alert text persists.
  • `pre_grant_permissions` — `simctl privacy grant` for camera / photos / location / etc. before launch
  • `set_appearance` — light / dark toggle
  • `dismiss_sheet` — swipe-down sheet dismissal
  • `list_replays` — saved recordings with metadata
  • `validate_replay` — structural validation of a recording YAML without execution

Deferred (with rationale)

  • `network` — large port (CFNetwork log parsing + nettop merge); needs its own sprint
  • `accessibility_audit`, `webview_elements` — XCTest-only; do not fit simdrive's vision-first model
  • `app_relaunch` — iOS 26.3 teardown recovery is fragile; deferred to a stability cut

Install

```bash
pip install --pre --upgrade simdrive
```

Numbers

  • 27 MCP tools (was 13)
  • 73 / 73 unit tests passing (21 new)
  • Live smoke verified perf/memory/apps return realistic data on a booted iOS 26.3 sim running Preferences

Full changelog

See `simdrive/CHANGELOG.md`.

simdrive v0.2.0a2 — Palace dogfood + maintainer feedback round

Choose a tag to compare

@SyncTekLLC SyncTekLLC released this 30 Apr 04:37

Palace dogfood (v0.2.0a1) + maintainer feedback round.

Headline: simdrive is now Palace iOS's canonical sim driver, retiring SpecterQA. This release ships the full top-priority trio Palace identified as needed before simdrive can serve as a real PR gate.

Top priority — all three landed

  • SSIM region maskingreplay(mask_regions=[(x,y,w,h),...]) or ssim_masks: [...] in recording.yaml. Stops the iOS status-bar clock from dragging same-screen SSIM into the 0.6s. Unblocks PR-gating use case.
  • Recordings serialize stable_id. Replay prefers it against the live observe; falls back to recorded pixel coords. Survives 1px layout shifts.
  • type_text returns focus signal. New response fields keyboard_visible (heuristic) and focused_field (stable_id of tap_first target).

Quality of life

  • stable_id_loose companion. 60px bucket (3× the tight 20px) tolerates the >3px shifts that re-bucket the tight stable_id.
  • step_id in act-tool responses while recording. Correlates live actions with recording's step list.
  • observe(annotate=false) preserves mark cache. No more "no marks available" foot-gun after a fast un-annotated observe.
  • list_devices reports hid_supported per device + top-level hid_note. No more guessing whether tap will work.
  • Recording metadata. recording.yaml now captures simdrive_version, created_by_session, screenshot_size_pixels, and a tags: [] list. record_start({tags: [...]}) accepts free-form tags.
  • simdrive --version / --help. CLI no longer launches MCP server when invoked with a flag.
  • Replay halt context. Response now includes halt_reason, threshold, steps_planned.

Install

```bash
pip install --pre --upgrade simdrive
```

Numbers

  • 13 MCP tools
  • 52 / 52 unit tests passing (12 new this release)
  • Live recording + replay E2E: green

Full changelog

See `simdrive/CHANGELOG.md`.

simdrive v0.2.0a1 — Real-device support (observe + logs + app lifecycle)

Choose a tag to compare

@SyncTekLLC SyncTekLLC released this 29 Apr 18:18

First slice of real-device support — observe screenshots, tail logs, manage app lifecycle on paired iPhones / iPads. Touch input remains v0.2.x roadmap (WDA-based; see attached feasibility doc).

Install

pip install --upgrade https://github.com/SyncTek-LLC/specterqa-ios/releases/download/simdrive-v0.2.0a1/simdrive-0.2.0a1-py3-none-any.whl

Real device requirements:

brew install libimobiledevice

(Plus Xcode, plus device paired with this Mac, plus Developer Disk Image mounted on the device — error message names the exact ideviceimagemounter command if the DDI is missing.)

Usage

# Discover paired devices
result = list_devices()
# → [{udid: "00008112-...", name: "Maurice's iPad", model: "iPad Pro 12.9", transport: "wired", ...}]

# Attach to a real device
session_start({target: "device", udid: "00008112-..."})

# Same observe + logs surface as simulator
observe()  # screenshot via idevicescreenshot, marks via Vision OCR
logs({lines: 100})  # via idevicesyslog

# tap/swipe/type_text/press_key raise device_input_unavailable until v0.2.x

Tool surface (13 tools, was 12)

New: list_devices — enumerates paired real devices with udid, name, model, transport, state, and libimobiledevice-readiness diagnostics.

Updated: session_start accepts target: "simulator" | "device".

Updated: observe, logs route through the device backend when target=device.

Verification

  • 34 unit tests / 26 live E2E (simulator path) — all green
  • list_devices live-confirmed against 3 paired devices
  • session_start({target: "device"}) live-confirmed; observe surfaces a clear DDI-mount error when the developer disk isn't mounted

SHA-256

942bc0e9ee632270fa588bd5a0e28384221b401567fe66f4f7568dd7af5d8eea

See REAL_DEVICE_FEASIBILITY.md for the full v0.2 roadmap covering WDA-based touch input.

simdrive v0.1.0a2 — Palace dogfood feedback round 1

Choose a tag to compare

@SyncTekLLC SyncTekLLC released this 29 Apr 17:51

Round 1 of Palace dogfood fixes from Maurice's PP-4164 regression workload feedback. Five user-reported issues addressed, two investigated and noted.

Install

pip install --upgrade https://github.com/SyncTek-LLC/specterqa-ios/releases/download/simdrive-v0.1.0a2/simdrive-0.1.0a2-py3-none-any.whl

Highlights

  • type_text casing/substitution fixed. A1QAAlqa regression cured. Shift modifier now sent correctly for A-Z and shifted symbols. Verified live with Maurice, A1QA, and other mixed-case strings.
  • Observation JSON sidecars. Every screenshot now drops <name>.json next to the PNG with the full structured observation. Session directories are now self-contained fixture artifacts.
  • Action audit log. <session>/actions.jsonl captures every act-tool call. Replay-ready without record_start.
  • Stable mark IDs (stable_id). Survives observe() reshuffling. New tap form: tap({stable_id: "abc123"}).
  • Home-indicator guard rail. swipe warns when y2 lands in the bottom strip iOS reads as the home-up gesture.

Verification

  • 31/31 unit tests
  • 26/26 live E2E against TestKitApp (Form, List, Nav, Stress, Palace tabs)

See SIMDRIVE_DOGFOOD_2026_04_29_RESPONSE.md for per-item response to the original feedback.

SHA-256

4e8d6bf1a2c7cd6bbd928ab29932ad3f7f3444036796eb981534c8c559f28519

simdrive v0.1.0a1 — Palace dogfood alpha

Choose a tag to compare

@SyncTekLLC SyncTekLLC released this 29 Apr 17:30

First public alpha of simdrive — Claude-native iOS simulator driver. Replaces the SpecterQA / XCTest pipeline. Drops in via MCP.

Install

# Direct from this release
pip install https://github.com/SyncTek-LLC/specterqa-ios/releases/download/simdrive-v0.1.0a1/simdrive-0.1.0a1-py3-none-any.whl

Then add to .mcp.json:

{ "mcpServers": { "simdrive": { "command": "simdrive" } } }

Restart Claude Code. The 12 simdrive tools are available.

What's new

  • Native HID input — taps focus UITextFields and accept keyboard input on iOS 26 (the v15/v16 cliclick path didn't)
  • Background mode — your editor / browser / IDE keeps focus during dispatch
  • Set-of-Mark observe — every screenshot is OCR'd and returned with numbered red boxes; tap({text: "..."}) or tap({mark: 7}) instead of pixel coords
  • Recording + replay — YAML + PNG bundle, drift-aware halting

Verification

  • 22/22 unit tests
  • 26/26 live E2E tests against TestKitApp on iOS 26.3 (every tool, hardest scenarios: alert-while-focused, Palace state-machine, drift-halt replay)

Known requirements

  • macOS with Xcode + iOS Simulator
  • Apple Silicon wheel only (Intel users: clone repo + make in simdrive/native/)

SHA-256

$(shasum -a 256 simdrive/dist/simdrive-0.1.0a1-py3-none-any.whl | awk '{print $1}')

See PALACE-DOGFOOD.md for the dogfood test plan.

PR: #87

v11.3.0

Choose a tag to compare

@SyncTekLLC SyncTekLLC released this 08 Apr 05:28
47d7419

What's Changed

  • chore(v11.3.0): cleanup release — logging, exception hygiene, metadata, conftest by @SyncTekLLC in #35

Full Changelog: v11.2.2...v11.3.0

v11.2.2

Choose a tag to compare

@SyncTekLLC SyncTekLLC released this 08 Apr 04:38
893add2

What's Changed

  • fix(v11.2.2): adversarial review fixes — fallback version string, expose 7 hidden test failures by @SyncTekLLC in #34

Full Changelog: v11.2.1...v11.2.2