Releases: SyncTek-LLC/simdrive
Release list
simdrive 1.0.0a9 — recording state contracts
[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 ofexact | minor | major | any(defaultminor).requires.sim.{device, ios_version}—ios_versionaccepts 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 existingsom.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 againstrequires:. On mismatch, halts
withhalt_reason: "state_contract_mismatch",halted_at: 0,
structuredexpected/actual/remedypayload. If a permission
alert is detected inactual.text_subset_present(heuristic on
"Don't Allow" / "Allow" / "OK" / "Cancel"), theremedyfield points
atxcrun simctl privacy ... grant ...rather than generic advice. - New
replayparameter:halt_on_state_mismatch(defaultTrue).
Today's "run anyway" behavior is now opt-in by passingFalse— a
warning is still emitted in the response. - Deprecation warning for unannotated recordings: recordings without
arequires: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.--jsonfor programmatic
consumption (CIverify-pr.shintegration).simdrive migrate-recording <name> [--force] [--dry-run]CLI
command. Re-OCRs the step-0pre_screenshotof a pre-a9 recording,
builds aRequiresBlock, writes the YAML back in place with a
.pre-migrate.baksibling for recovery.- Both also exposed as MCP tools (
lint_recordings,migrate_recording)
for agentic / CI consumers. Tool count: 30 → 32.
Internal
- Refactored
_capture_state_contractto share_build_requires_block
(pure transform onmarks + app/sim metadata) with
migrate_recording— capture-time and migrate-time paths produce
identical RequiresBlock shapes by construction. robustness.validate_replay()now tolerates the newrequires:
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
idevicesyslogoutput on real devices) still investigation-
only — awaiting Palace dogfood capture of
idevicesyslog -u <udid> 2>&1for root-cause selection. See
simdrive/docs/D6_LOGS_INVESTIGATION.md.
simdrive 1.0.0a8 — device session usable on real hardware
[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_appno longer passes--start-stopped=false—
modern devicectl rejected the flag, blocking everysession_start
withapp_bundle_id. - D2
session_start target=devicenow opens a default WDA test
session unconditionally (with or withoutapp_bundle_id); input verbs
no longer hitwda_session_not_open. - D1
observegatesscreenshot_b64behind a new
include_screenshot_b64param (defaultfalse). The unconditional
101k-char b64 payload was overflowing the MCP token budget on real
devices.screenshot_pathis still returned by default.
Fixed — daemonization
- B3
bootstrap-devicenow passesstart_new_session=Trueto 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 manualnohupworkaround. - B3 new CLI:
simdrive wda-up <udid>(relaunch from registry
without rebuilding) andsimdrive wda-down <udid>(SIGTERM via
pidfile).
Fixed — simctl-leak cluster (device sessions stop using simctl)
- D4 Device-launch failure raises
device_launch_failedwith a
devicectl-aware recovery hint instead ofno_devicewith a simctl
hint. - D5
tool_appsfortarget=devicenow queries
xcrun devicectl device info apps --device <udid> --json-output -
instead of returning{apps: []}from a simctl-only call. - D7
tool_app_statefortarget=devicenow queries
xcrun devicectl device info processes ...instead of leaking
simctl's "Invalid device" error string. - New helper
simdrive.diagnostics._devicectl_info_jsonunderlies both.
Fixed — bootstrap polish
- B1
verify_xcode_account_for_team(team_id)now scans the parsed
plist output ofdefaults read com.apple.dt.Xcode DVTDeveloperAccountManagerAppleIDListsfor 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
raisingwda_signing_ambiguous. Ambiguity is still raised when
team_idwas not provided. - B4 When
xcodebuild build-for-testingemits aBUILD FAILED
before its-allowProvisioningUpdatesretry 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-devicenow captures device name from
xcrun devicectl device info detailsand iOS version from WDA
/status;~/.simdrive/wda/<udid>.jsoncarriesdevice_nameand
os_versionkeys;tool_session_startreturns these instead of
device: "Real Device",os_version: "".
Investigated (not fixed — gated to a9)
- D6
tool_logsreturns 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 capturesidevicesyslog -u <udid> 2>&1against
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
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_textreportsinjection_methodanddispatch_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 legacykeyboard_visibleandfocused_fieldstay for cliclick-path debugging.- OCR confidence is dictionary-gated. Stylized covers used to OCR as
"Sary of the Canadan liothest"with confidence1.0. Newconfidence_band(high/medium/low) and a clamped legacyconfidencefield flag misreads even when the OCR engine reports high internal confidence.raw_confidenceexposes the unclamped score for diagnostics. - Stale-MCP detection. When the loaded simdrive version differs from the version on disk (after
pip install --upgradewithout restarting), every tool response carries a_simdrive_warningside-channel field flagging the drift.
Added
versionMCP tool. Zero-arg →{version, loaded_at, disk_version, drift}. No more guessing whether the running server matches the on-disk package.clear_fieldMCP 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.mdanddocs/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
versionandclear_field) - 91 / 91 unit tests passing (11 new)
- Live smoke verified
type_textreturnsinjection_method="hid"+dispatch_succeeded=Trueon iOS 26.3 sim
Full changelog
simdrive v0.3.0a1 — SpecterQA parity round 1 (13 → 27 tools)
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
simdrive v0.2.0a2 — Palace dogfood + maintainer feedback round
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 masking —
replay(mask_regions=[(x,y,w,h),...])orssim_masks: [...]inrecording.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_textreturns focus signal. New response fieldskeyboard_visible(heuristic) andfocused_field(stable_id oftap_firsttarget).
Quality of life
stable_id_loosecompanion. 60px bucket (3× the tight 20px) tolerates the >3px shifts that re-bucket the tightstable_id.step_idin 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_devicesreportshid_supportedper device + top-levelhid_note. No more guessing whether tap will work.- Recording metadata.
recording.yamlnow capturessimdrive_version,created_by_session,screenshot_size_pixels, and atags: []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
simdrive v0.2.0a1 — Real-device support (observe + logs + app lifecycle)
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.whlReal 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.xTool 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
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.whlHighlights
type_textcasing/substitution fixed.A1QA→Alqaregression cured. Shift modifier now sent correctly forA-Zand shifted symbols. Verified live withMaurice,A1QA, and other mixed-case strings.- Observation JSON sidecars. Every screenshot now drops
<name>.jsonnext to the PNG with the full structured observation. Session directories are now self-contained fixture artifacts. - Action audit log.
<session>/actions.jsonlcaptures every act-tool call. Replay-ready withoutrecord_start. - Stable mark IDs (
stable_id). Survives observe() reshuffling. New tap form:tap({stable_id: "abc123"}). - Home-indicator guard rail.
swipewarns wheny2lands 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
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.whlThen 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: "..."})ortap({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 +
makeinsimdrive/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
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
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