mobile-debug-mcp 0.35.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/.cursor-plugin/marketplace.json +1 -1
  2. package/.github/workflows/ci.yml +1 -1
  3. package/README.md +86 -24
  4. package/agent-plugin/.antigravity-plugin/README.md +48 -0
  5. package/agent-plugin/.antigravity-plugin/plugin.json +18 -0
  6. package/agent-plugin/.claude-plugin/plugin.json +2 -2
  7. package/agent-plugin/.codex-plugin/plugin.json +1 -1
  8. package/agent-plugin/.cursor-plugin/plugin.json +2 -2
  9. package/agent-plugin/.mcp.json +1 -1
  10. package/agent-plugin/README.md +37 -8
  11. package/agent-plugin/cursor/.cursor-plugin/plugin.json +23 -0
  12. package/agent-plugin/cursor/LICENSE +201 -0
  13. package/agent-plugin/cursor/README.md +9 -0
  14. package/agent-plugin/cursor/skills/mobile-debug-mcp/SKILL.md +253 -0
  15. package/agent-plugin/mcp.json +1 -1
  16. package/agent-plugin/mcp_config.json +13 -0
  17. package/agent-plugin/plugin.json +1 -1
  18. package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +36 -16
  19. package/dist/debugger/guard.js +4 -4
  20. package/dist/debugger/ios-worker.js +62 -0
  21. package/dist/debugger/ios.js +57 -0
  22. package/dist/debugger/sessions.js +33 -16
  23. package/dist/debugger/worker.js +6 -4
  24. package/dist/interact/index.js +12 -2
  25. package/dist/manage/ios.js +2 -0
  26. package/dist/observe/ios-trace/artifact.js +63 -0
  27. package/dist/observe/ios-trace/capabilities.js +86 -0
  28. package/dist/observe/ios-trace/native.js +93 -0
  29. package/dist/observe/ios-trace/notification.js +35 -0
  30. package/dist/observe/ios-trace/process.js +98 -0
  31. package/dist/observe/ios-trace/sessions.js +244 -0
  32. package/dist/observe/readiness-service.js +1 -1
  33. package/dist/observe/readiness.js +4 -4
  34. package/dist/observe/system-trace-error.js +7 -0
  35. package/dist/observe/system-trace-router.js +81 -0
  36. package/dist/observe/system-trace.js +4 -8
  37. package/dist/server/handlers/debugger-handlers.js +27 -8
  38. package/dist/server/handlers/diagnostic.js +12 -4
  39. package/dist/server/handlers/tool-handlers.js +19 -9
  40. package/dist/server/schemas/debugger-schema.js +10 -7
  41. package/dist/server/schemas/system-trace-schema.js +14 -9
  42. package/dist/server-core.js +1 -1
  43. package/dist/server.js +4 -4
  44. package/dist/utils/ios/utils.js +3 -0
  45. package/docs/CHANGELOG.md +23 -1
  46. package/docs/agent-plugin-installation-verification.md +39 -0
  47. package/docs/code-structure.md +3 -1
  48. package/docs/specs/031-ios-system-traces/intent.md +38 -0
  49. package/docs/specs/031-ios-system-traces/investigation.md +92 -0
  50. package/docs/specs/031-ios-system-traces/late-run-failure-evidence.json +32 -0
  51. package/docs/specs/031-ios-system-traces/plan.md +138 -0
  52. package/docs/specs/031-ios-system-traces/pr102-monitor-evidence.json +22 -0
  53. package/docs/specs/031-ios-system-traces/simulator-evidence.json +47 -0
  54. package/docs/specs/031-ios-system-traces/spec.md +135 -0
  55. package/docs/specs/032-ios-runtime-debugger/evidence.json +176 -0
  56. package/docs/specs/032-ios-runtime-debugger/intent.md +38 -0
  57. package/docs/specs/032-ios-runtime-debugger/plan.md +95 -0
  58. package/docs/specs/032-ios-runtime-debugger/report.md +205 -0
  59. package/docs/specs/032-ios-runtime-debugger/spec.md +153 -0
  60. package/docs/tools/TOOLS.md +2 -2
  61. package/docs/tools/debugger.md +45 -5
  62. package/docs/tools/system-traces.md +81 -14
  63. package/package.json +2 -1
  64. package/scripts/agent-plugin.mjs +55 -5
  65. package/src/debugger/android.ts +1 -1
  66. package/src/debugger/guard.ts +5 -5
  67. package/src/debugger/ios-worker.ts +49 -0
  68. package/src/debugger/ios.ts +45 -0
  69. package/src/debugger/lldb/breakpoints.py +51 -0
  70. package/src/debugger/lldb/inspection.py +109 -0
  71. package/src/debugger/lldb/worker.py +203 -0
  72. package/src/debugger/sessions.ts +24 -12
  73. package/src/debugger/worker.ts +7 -4
  74. package/src/interact/index.ts +13 -2
  75. package/src/manage/ios.ts +2 -0
  76. package/src/observe/ios-trace/artifact.ts +55 -0
  77. package/src/observe/ios-trace/capabilities.ts +74 -0
  78. package/src/observe/ios-trace/native.ts +72 -0
  79. package/src/observe/ios-trace/notification.ts +30 -0
  80. package/src/observe/ios-trace/process.ts +82 -0
  81. package/src/observe/ios-trace/sessions.ts +204 -0
  82. package/src/observe/readiness-service.ts +1 -1
  83. package/src/observe/readiness.ts +4 -4
  84. package/src/observe/system-trace-error.ts +3 -0
  85. package/src/observe/system-trace-router.ts +61 -0
  86. package/src/observe/system-trace.ts +4 -4
  87. package/src/server/handlers/debugger-handlers.ts +28 -9
  88. package/src/server/handlers/diagnostic.ts +11 -4
  89. package/src/server/handlers/tool-handlers.ts +20 -9
  90. package/src/server/schemas/debugger-schema.ts +10 -7
  91. package/src/server/schemas/system-trace-schema.ts +15 -9
  92. package/src/server-core.ts +1 -1
  93. package/src/server.ts +3 -3
  94. package/src/utils/ios/utils.ts +3 -0
  95. package/test/device/automated/debugger/runtime.ios.test.ts +221 -0
  96. package/test/device/automated/observe/ios-system-trace.smoke.ts +119 -0
  97. package/test/fixtures/contracts/tool-definitions.json +192 -32
  98. package/test/fixtures/ios-debugger-app/Main.swift +95 -0
  99. package/test/fixtures/ios-debugger-app/README.md +27 -0
  100. package/test/fixtures/ios-debugger-app/build.ts +21 -0
  101. package/test/helpers/ios-trace-shutdown.ts +29 -0
  102. package/test/unit/debugger/handle-guards.test.ts +88 -0
  103. package/test/unit/debugger/ios-preparation.test.ts +57 -0
  104. package/test/unit/debugger/ios.test.ts +104 -0
  105. package/test/unit/debugger/lldb-worker.py +166 -0
  106. package/test/unit/debugger/lldb-worker.test.ts +10 -0
  107. package/test/unit/observe/ios-trace-native.test.ts +90 -0
  108. package/test/unit/observe/ios-trace-sessions.test.ts +131 -0
  109. package/test/unit/observe/readiness.test.ts +1 -1
  110. package/test/unit/observe/system-trace-router.test.ts +49 -0
  111. package/test/unit/quality/agent-plugin-export.test.ts +38 -0
  112. package/test/unit/server/system-trace.test.ts +12 -3
@@ -0,0 +1,135 @@
1
+ # iOS system traces — proposed specification
2
+
3
+ 2026-11-04. **Approved** by the user's second “ok” in this chat, responding to
4
+ the complete specification and implementation approval request.
5
+ [Intent](intent.md) approved by the user's “ok” in this chat.
6
+ Source: [issue #78](https://github.com/clivejefferies/mobile-debug-tools/issues/78).
7
+ [Native investigation and limits](investigation.md).
8
+
9
+ ## Supported scope
10
+
11
+ Extend the existing five system-trace tools with an Instruments backend on macOS
12
+ with full Xcode. Initial supported combination: booted iOS simulator and Time
13
+ Profiler, attached to an already running development app. Baseline is Xcode 27.2
14
+ and iOS 26.4.1. Other versions require successful prerequisite checks and artifact
15
+ validation; installed templates alone do not establish support.
16
+
17
+ System Trace is discoverable but not enabled initially: the simulator probe did
18
+ not establish scheduling data. Animation Hitches is unsupported on the tested
19
+ simulator. Hangs is an instrument within Time Profiler here, not a selectable
20
+ standalone template; do not promise detected hangs. Physical-device capture is
21
+ explicitly unsupported in this initial release, with verification unrun because
22
+ no device is available. Report pairing, Developer Mode, signing/profiling and
23
+ symbol prerequisites as applicable setup guidance, never as completed checks.
24
+ Expanding this matrix requires evidence and a specification revision.
25
+
26
+ No host recording, all-processes mode, custom templates, app launching, automatic
27
+ diagnosis, summaries, arbitrary queries, or public export API. Android retains
28
+ its existing preset, inputs, results and verification requirements.
29
+
30
+ ## Tools and contracts
31
+
32
+ Keep existing names and strict schemas. Extend capabilities and start inputs with
33
+ optional `appId` (nonblank bundle identifier) and `template` enum
34
+ `System Trace | Time Profiler | Animation Hitches | Hangs`. These fields are
35
+ iOS-only; reject them on Android. iOS start requires explicit `deviceId` and
36
+ `appId`; omitted template defaults to Time Profiler. Capabilities require explicit
37
+ `deviceId` on iOS; `appId` optionally probes running-app readiness. Duration stays
38
+ 1,000–120,000 ms, default 30,000. Controls still accept only `traceId`.
39
+
40
+ iOS capabilities return `backend:xctrace`, resolved simulator/device metadata,
41
+ Xcode version, discovered templates, per-template support/reasons/coverage,
42
+ prerequisite states (`available`, `missing`, `unknown`), and actionable recovery.
43
+ Discovery starts no recording and grants no permissions. Distinguish unavailable
44
+ tooling/target, unsupported matrix, app not running, permission failure and checks
45
+ that only a recording can establish. Do not label unknown permissions as granted.
46
+
47
+ Reuse session state, completion reasons, IDs, host bounds, cleanup and correlation
48
+ fields. Extend result unions to iOS, `backend:xctrace`, and artifact
49
+ `format:instruments` with absolute `.trace` path, recursive `size_bytes`, and
50
+ `kind:directory|file`. Include pinned `app_id`, PID, executable identity,
51
+ selected template, target type, warnings and validation result. Never return raw
52
+ trace data. Unreached fields are null. Errors use existing MCP `isError` and
53
+ TRACE_* codes; bounded reason/recovery values distinguish setup, permission,
54
+ target disconnect, readiness, export and storage failures without raw stderr.
55
+
56
+ Resolve bundle identity to one current executable/PID on the pinned simulator;
57
+ ambiguous/missing identity fails. Always pass `--device <UDID>` and revalidate
58
+ identity before starting. Do not retry against a different process/name, template,
59
+ device or host. Exported target UUID, PID and template must match the session.
60
+
61
+ ## Lifecycle, limits and privacy
62
+
63
+ Use uniquely owned mode-0700 temporary directories and fixed argument arrays.
64
+ Spawn the recorder with `--time-limit`, unique output and unique
65
+ `--notify-tracing-started` notification. Register an owned Darwin-notification
66
+ listener before spawning; return recording only after that notification while
67
+ the recorder remains live. Console “Starting recording” alone is insufficient.
68
+ Notification helper availability is a prerequisite, never a silent fallback.
69
+
70
+ Allow 35 seconds for readiness, 30 seconds for graceful stop/finalisation and
71
+ 30 seconds for validation. Bound every child process and cap captured diagnostic
72
+ output. At requested duration, reconcile and finalise automatically. SIGINT only
73
+ the session-owned recorder; after its grace deadline terminate that owned child
74
+ and report failure/partial output. Never signal the app, by process name, or an
75
+ unrelated Instruments recording. On server shutdown cancel owned sessions under
76
+ one 10-second overall deadline; forced cleanup may leave an unreadable artifact
77
+ and must report that. Crashes provide no cleanup guarantee; no restart-wide sweep.
78
+
79
+ Share the existing one-session-per-device, four-active-session server limit across
80
+ backends. Serialise controls; retain 100 terminal metadata records for ten minutes.
81
+ Stale/other-server IDs fail. Completed controls are idempotent. Disconnect,
82
+ premature recorder exit and journey failure have explicit outcomes; journey failure
83
+ does not cancel capture. Retry validation only on an existing finalised owned
84
+ bundle, never restart recording. Cancel removes owned partial output; completed
85
+ artifacts remain user-managed.
86
+
87
+ Poll recursive output size every second; stop/discard on exceeding 256 MiB per
88
+ iOS session, reject starts with less than 1 GiB free in its output filesystem.
89
+ Finalisation can temporarily overshoot the polling threshold: report this as a
90
+ soft bound, reject oversized final output and remove it. No symlink traversal.
91
+ Apply restrictive permissions recursively before returning validated output.
92
+ Instruments may contain system-wide metadata, paths, symbols, logs and signposts
93
+ despite app attachment. Disclose this in tool/skill documentation. No upload,
94
+ telemetry or permission bypass, including `--no-prompt`. User handles any required
95
+ OS permissions outside the tool.
96
+
97
+ ## Artifact and journey evidence
98
+
99
+ Require successful recorder exit, nonempty bounded output, parseable
100
+ `xctrace export --toc`, matching target/app/template metadata, expected
101
+ `time-sample` schema and successful table export. Record row count, including zero;
102
+ zero samples means no CPU sample evidence, not diagnosed inactivity. Do not call
103
+ a failed recording valid merely because a readable partial bundle exists.
104
+ Expose warnings and unavailable instruments; do not claim symbolication unless
105
+ verified. Validation exports are private temporary data removed after validation.
106
+
107
+ Keep `run_journey.systemTraceId` explicit. Validate active same-server,
108
+ same-platform/same-UDID session before any journey input. Link journey and step
109
+ evidence through existing trace context and correlation IDs. The trace pins the
110
+ profiled app; journey actions retain their existing targeting semantics and may
111
+ leave that app. Report that limitation. Duration expiry or stop during the journey
112
+ means partial coverage. Host capture bounds and unknown device-clock alignment
113
+ establish association only; no device markers or causal inference.
114
+
115
+ ## Acceptance and verification
116
+
117
+ 1. Capability matrix and rejected inputs are side-effect-free, with exact target
118
+ and actionable prerequisite reasons; unknown permissions stay unknown.
119
+ 2. Updated-server simulator Time Profiler start → existing fixture journey tapping
120
+ Show loader and asserting Done → stop yields matching IDs, target/app metadata,
121
+ notification-before-input evidence, readable bundle and exported sample rows.
122
+ 3. Deterministic checks cover stale/mismatched IDs preventing all input, ownership,
123
+ concurrency, expiry, cancellation, partial coverage, disconnect, identity races,
124
+ notification/start/stop/export/storage failures, retries and shutdown cleanup.
125
+ 4. Real simulator checks cover explicit stop, expiry, cancellation and failed
126
+ journey; unavailable physical/failure-injection evidence is separate from mocks.
127
+ 5. Existing Android traces and untraced journeys pass regression checks. Run
128
+ `npm run verify`, applicable `npm run test:device`, and focused updated-server
129
+ calls; report actual suite coverage and unrun platforms.
130
+
131
+ Affected: trace sessions/adapters/types, schemas/registration/handlers, journey and
132
+ evidence integration, shutdown, tests/goldens, tool docs, README, changelog and
133
+ packaged usage skill. Main implementation risks remain notification readiness,
134
+ reliable app identity resolution and finalisation deadlines; feasibility probes
135
+ do not establish these acceptance criteria.
@@ -0,0 +1,176 @@
1
+ {
2
+ "date": "2026-11-04",
3
+ "status": "PASS",
4
+ "transport": "MCP stdio",
5
+ "runtime": "v23.11.0",
6
+ "deviceId": "F9243BAF-62A8-4990-8A99-17F527F56FDA",
7
+ "server_sha256": "6a00a33d994a449ecbb54d5dbf894780f48a1c0e5a89fc871b91dd7048f4ecdc",
8
+ "source_sha256": "295ff0f30f5670530f9cdd7073b958917c1c574ccca9c7b921e295d0d902b699",
9
+ "xcode": "Xcode 27.2\nBuild version 27B52",
10
+ "lldb": "lldb-2103.0.34.407\nApple Swift version 6.4.2 (swiftlang-6.4.2.1.9 clang-2100.4.1.1)",
11
+ "fixture": "test/fixtures/ios-debugger-app/Main.swift",
12
+ "fixture_triggers": "Atomic command file; installed idb HID backend unavailable; not proof of successful MCP tap injection",
13
+ "extended_pause_expiry": true,
14
+ "process_exit": true,
15
+ "server_shutdown_recovery": true,
16
+ "tests_sha256": "37fbda265b5735b1b826aa448aa937359c45db0e55f8f1998a169b1aa3073acc",
17
+ "observations": [
18
+ {
19
+ "reason": "breakpoint",
20
+ "line": 49,
21
+ "method": "DebuggerFixture.Controller.calculate() -> ()"
22
+ },
23
+ {
24
+ "reason": "step",
25
+ "line": 8,
26
+ "method": "DebuggerFixture.helper(Swift.Int) -> Swift.Int"
27
+ },
28
+ {
29
+ "reason": "step",
30
+ "line": 9,
31
+ "method": "DebuggerFixture.helper(Swift.Int) -> Swift.Int"
32
+ },
33
+ {
34
+ "reason": "step",
35
+ "line": 49,
36
+ "method": "DebuggerFixture.Controller.calculate() -> ()"
37
+ },
38
+ {
39
+ "reason": "breakpoint",
40
+ "line": 20,
41
+ "method": "(1) suspend resume partial function for DebuggerFixture.AsyncWork.calculate(Swift.Int) async -> Swift.Int"
42
+ },
43
+ {
44
+ "reason": "breakpoint",
45
+ "line": 21,
46
+ "method": "(3) suspend resume partial function for DebuggerFixture.AsyncWork.calculate(Swift.Int) async -> Swift.Int"
47
+ },
48
+ {
49
+ "reason": "breakpoint",
50
+ "line": 20,
51
+ "method": "(1) suspend resume partial function for DebuggerFixture.AsyncWork.calculate(Swift.Int) async -> Swift.Int"
52
+ },
53
+ {
54
+ "reason": "step",
55
+ "line": 21,
56
+ "method": "(3) suspend resume partial function for DebuggerFixture.AsyncWork.calculate(Swift.Int) async -> Swift.Int"
57
+ },
58
+ {
59
+ "reason": "breakpoint",
60
+ "line": 66,
61
+ "method": "(3) suspend resume partial function for closure #1 () async -> () in DebuggerFixture.Controller.startCancellation() -> ()"
62
+ },
63
+ {
64
+ "reason": "breakpoint",
65
+ "line": 49,
66
+ "method": "DebuggerFixture.Controller.calculate() -> ()"
67
+ },
68
+ {
69
+ "reason": "breakpoint",
70
+ "line": 49,
71
+ "method": "DebuggerFixture.Controller.calculate() -> ()"
72
+ }
73
+ ],
74
+ "locals": [
75
+ [
76
+ {
77
+ "name": "known",
78
+ "type": "Swift.Int",
79
+ "value": "41"
80
+ }
81
+ ],
82
+ [
83
+ {
84
+ "name": "seed",
85
+ "type": "Swift.Int",
86
+ "value": "42"
87
+ },
88
+ {
89
+ "name": "known",
90
+ "type": "Swift.Int",
91
+ "value": "42"
92
+ }
93
+ ],
94
+ [
95
+ {
96
+ "name": "seed",
97
+ "type": "Swift.Int",
98
+ "value": "42"
99
+ },
100
+ {
101
+ "name": "known",
102
+ "type": "Swift.Int",
103
+ "value": "42"
104
+ },
105
+ {
106
+ "name": "after",
107
+ "type": "Swift.Int",
108
+ "value": "43"
109
+ }
110
+ ],
111
+ [
112
+ {
113
+ "name": "cancelled",
114
+ "type": "Swift.Bool",
115
+ "value": "true"
116
+ }
117
+ ]
118
+ ],
119
+ "async_boundary_step": {
120
+ "expected_line": 21,
121
+ "observed": {
122
+ "source_file": "test/fixtures/ios-debugger-app/Main.swift",
123
+ "line": 21,
124
+ "method_name": "(3) suspend resume partial function for DebuggerFixture.AsyncWork.calculate(Swift.Int) async -> Swift.Int",
125
+ "class_name": null
126
+ }
127
+ },
128
+ "assertions": [
129
+ "Exact source binding and pending source creation",
130
+ "Known primitive locals",
131
+ "Into/over/out",
132
+ "Stop/frame stale rejection",
133
+ "Paused tap rejected before dispatch",
134
+ "Paused readiness without UI acquisition",
135
+ "Cancelled event wait preserves session",
136
+ "Before/after await values and nested async child",
137
+ "Step over await reaches after-await source line",
138
+ "Task cancellation and fresh Task cancelled UI",
139
+ "Real 60-second pause expiry and fresh result UI",
140
+ "Target self-exit, disconnected state and complete cleanup",
141
+ "Idempotent detach with no remaining resources",
142
+ "Server shutdown while paused then independent UI recovery"
143
+ ],
144
+ "unrun": [
145
+ "physical iOS devices",
146
+ "other Xcode/Swift versions",
147
+ "optimized apps",
148
+ "installed/published package",
149
+ "forced host termination",
150
+ "real device disconnect",
151
+ "live idle/absolute expiry",
152
+ "late-loaded-image binding"
153
+ ],
154
+ "scope": "Focused real MCP iOS simulator debugger journey, including extended pause expiry",
155
+ "repository_verification": {
156
+ "status": "PASS",
157
+ "unit_files": 96,
158
+ "command": "npm run verify"
159
+ },
160
+ "broader_device_suite": {
161
+ "status": "FAIL",
162
+ "observations": [
163
+ "Initial Android interaction SDK timeout; isolated unchanged retry passed",
164
+ "Retry failed Android Perfetto shutdown-artifact assertion; isolated baseline and current checks passed"
165
+ ],
166
+ "root_cause": "unestablished",
167
+ "full_suite_pass_claimed": false
168
+ },
169
+ "cleanup": {
170
+ "ios_simulator": "restored to initial shutdown state",
171
+ "swift_fixture_build_directories_remaining": 0,
172
+ "owned_adb_forwards_remaining": 0,
173
+ "owned_perfetto_artifacts_remaining": 0,
174
+ "unrelated_installed_fixtures": "preserved"
175
+ }
176
+ }
@@ -0,0 +1,38 @@
1
+ # iOS runtime debugger — intent
2
+
3
+ - Source: [issue #70](https://github.com/clivejefferies/mobile-debug-tools/issues/70), and the user's 2026-11-04 request to take the next Ready ticket with Android-equivalent functionality, including async debugging.
4
+ - Originator: user in this chat.
5
+ - Captured: 2026-11-04.
6
+ - Status: intent approved by the user's “ok” on 2026-11-04 in response to the scope and approval request in this chat. Specification also approved in this chat on 2026-11-04; see spec.md. Issue #70 matches the roadmap and current branch; Ready-column membership could not be checked because GitHub authentication lacks `read:project`.
7
+
8
+ ## Problem and outcome
9
+
10
+ MCP clients can debug Android development apps but lack equivalent iOS runtime
11
+ inspection. Add an LLDB-backed debugger for iOS simulator development apps,
12
+ reusing the existing debugger tools and session/evidence conventions where
13
+ appropriate. Support attach, source breakpoints and events, bounded stacks and
14
+ shallow locals, stepping, resume and detach, with explicit capabilities and
15
+ actionable setup failures.
16
+
17
+ ## Async parity and success
18
+
19
+ Verify a deterministic Swift concurrency fixture with source stops and useful
20
+ locals before and after `await`, resumed execution, nested task work, task
21
+ cancellation and application recovery. Assess stepping across suspension and
22
+ report its observed limits. Android's existing coroutine assessment establishes
23
+ execution inspection, not reconstruction of suspended tasks or logical async
24
+ stacks; distinguish those capabilities explicitly on iOS too.
25
+
26
+ Preserve stale-reference rejection, pause-aware interaction guards, bounded
27
+ waits/cancellation, session isolation, expiry and owned cleanup. Prove the journey
28
+ through actual MCP calls and independent fixture outcomes, and retain Android
29
+ regression coverage. Report unavailable environments honestly.
30
+
31
+ ## Constraints and questions
32
+
33
+ Physical-device support follows separately. Evaluation, mutation, arbitrary LLDB
34
+ commands and generic suspended-task reconstruction are excluded from this initial
35
+ scope. Inspect LLDB/Xcode support for async frames and stepping before proposing
36
+ the exact capability contract. Debug values remain bounded and sensitive; avoid
37
+ persisting app data in public evidence. Specification approval follows intent
38
+ acceptance under the repository process.
@@ -0,0 +1,95 @@
1
+ # iOS runtime debugger — implementation and verification
2
+
3
+ Scope: [intent](intent.md) and [specification](spec.md), both approved by the user
4
+ on 2026-11-04. Use the repository MCP Builder and Test Authoring skills.
5
+
6
+ ## Implementation
7
+
8
+ - [x] Establish LLDB Python loading and explicit simulator/process attribution.
9
+ - [x] Add structured LLDB worker, safe inspection, exact breakpoints and watchdogs.
10
+ - [x] Reuse session lifecycle with platform-specific native/worker configuration;
11
+ update schemas, dispatch, diagnostics, guards and readiness.
12
+ - [x] Add deterministic Swift simulator fixture and MCP runtime/async journeys.
13
+ - [x] Add contract, identity, lifecycle and guard regression tests; update tool
14
+ compatibility golden only for approved schema changes.
15
+ - [x] Align debugger guide, tool reference, README, changelog and packaging.
16
+ - [x] Review diff, execute required verification and record actual results below.
17
+ Full-suite success remains unestablished; see the failed checks below.
18
+
19
+ ## Verification matrix
20
+
21
+ | Spec criterion | Check |
22
+ | --- | --- |
23
+ | Core runtime flow | `MCP_IOS_DEBUGGER_DEVICE=<UDID> node --import tsx test/device/automated/debugger/runtime.ios.test.ts` through built stdio server |
24
+ | Async/cancellation | Same journey with controlled before/after-await values, nested work, cancellation and fresh UI assertions; separate step-boundary result |
25
+ | Identity, failure, references and guards | Focused `test/unit/debugger/*.test.ts`, simulator invalid PID/foreign target, stale frame/stop, guarded input/readiness, event cancellation and detach |
26
+ | Expiry and shutdown | Worker watchdog unit checks plus real pause expiry and server EOF cleanup in fixture journey |
27
+ | Contract/Android regression | `npm run verify`; existing runtime, Kotlin and coroutine Android MCP checks on explicit emulator |
28
+ | Broader device compatibility | `npm run test:device` with selected targets; report the actual covered platforms and skipped prerequisites |
29
+
30
+ Required: Node 22+, installed Xcode 27.2/Swift 6.4.2/LLDB 2103.0.34.407,
31
+ simulator runtime, development fixture compiled with debug metadata and no
32
+ optimisation. Android regressions require existing JDK/SDK/Kotlin dependencies.
33
+ No physical-device iOS support is claimed. Hosted CI is separate from local checks.
34
+
35
+ Select existing simulator F9243BAF-62A8-4990-8A99-17F527F56FDA (iPhone 17 Pro,
36
+ iOS 26.4), initially shut down. Boot it for the fixture; preserve existing apps
37
+ and settings, install/remove only fixture-owned app/build files, detach owned
38
+ debuggers, and restore the initial shutdown state. Record exact server/fixture
39
+ hashes, observed stops/locals, independent UI state and cleanup. Simulator access
40
+ requires outside-sandbox native commands; the authorised inventory read succeeded.
41
+ If a prerequisite is unavailable, retain the acceptance criterion and record a
42
+ concrete blocker. Do not treat mock/native readiness checks as live verification.
43
+
44
+ ## Results
45
+
46
+ - **PASS** `npm run verify`: lint/quality/architecture, build, all 96 unit files,
47
+ plugin consistency, Node 23.11.0. Log `/tmp/ios-debugger-final-verify.log`.
48
+ - **PASS** `MCP_IOS_DEBUGGER_DEVICE=F9243BAF-62A8-4990-8A99-17F527F56FDA MCP_IOS_DEBUGGER_EXTENDED=1 node --import tsx test/device/automated/debugger/runtime.ios.test.ts`.
49
+ Exact stable-build hash, stops, locals and asserted cleanup/recovery are in
50
+ [evidence.json](evidence.json). No HID success is inferred from fixture control.
51
+ - **PASS** focused Android runtime, Kotlin locals and coroutine MCP regressions
52
+ using the existing fixture/compiler dependencies. The broader suite also reran
53
+ the final Android runtime debugger successfully.
54
+ - **PASS** unchanged standalone iOS screenshot, log and UI-tree smoke checks.
55
+ - **PASS** package dry-run contents, plugin generation/consistency and diff/link
56
+ checks. No publication or installed-package verification occurred.
57
+ - **FAIL** broader `ADB_PATH=/Users/clivejefferies/Library/Android/sdk/platform-tools/adb MCP_DEBUGGER_DEVICE=emulator-5554 npm run test:device`: initial Android
58
+ interaction request timeout; focused unchanged retry passed. The full-suite
59
+ retry passed that branch and both-platform observations/correlated evidence,
60
+ then failed Android Perfetto active-shutdown artifact removal. The isolated
61
+ Perfetto check passed on both unchanged HEAD and current build. These failures
62
+ remain unreproduced and unexplained; no full-suite pass is claimed.
63
+ - Full-suite opt-in Kotlin/coroutine/iOS debugger checks were **UNRUN** in that
64
+ invocation; each passed its separate focused run above. iOS system tracing was
65
+ **UNRUN** (no `IOS_SYSTEM_TRACE_DEVICE` supplied); it is outside this feature.
66
+ - **PASS** owned cleanup: no MCP device trace files or ADB forwards remained;
67
+ debugger/coroutine packages and Swift build directories removed. Unrelated
68
+ fixtures preserved; iOS simulator restored to shutdown; Android emulator left
69
+ running. Temporary baseline source/build copy removed.
70
+
71
+ See [verification report](report.md) for evidence boundaries, resolved findings,
72
+ unrun variants and original failed observations.
73
+
74
+
75
+ ## PR #103 review repair
76
+
77
+ - [x] Cache shared iOS preparation and reset rejected preparation for retry.
78
+ - [x] Add fake-toolchain coverage for concurrent reuse, retry and worker creation.
79
+ - [x] Check real Xcode cached preparation and rerun the focused iOS MCP journey.
80
+ - [x] Complete quiet repository verification (97 unit files); publish the repair to PR #103.
81
+
82
+ See the [report follow-up](report.md#pr-103-preparation-cache-follow-up--2026-11-04)
83
+ for exact evidence and retained broader-suite limitations.
84
+
85
+
86
+ ## PR #103 stored-handle pause guard repair
87
+
88
+ - [x] Validate guard targets through the stored-handle execution lookup.
89
+ - [x] Cover omitted/partial hints, known/racing pauses, platform/device isolation,
90
+ and explicit mismatches; prove regression detects the original route.
91
+ - [x] Run `npm run verify` (98 unit files) and the updated real iOS MCP journey.
92
+ - [x] Restore fixture/device state and prepare the narrow repair for PR publication.
93
+
94
+ The finding is in an outside-diff review summary, with no new inline thread to
95
+ resolve. See the [report](report.md) for exact evidence and retained limitations.
@@ -0,0 +1,205 @@
1
+ # iOS runtime debugger verification — issue #70
2
+
3
+ Date: 2026-11-04. Scope: [approved specification](spec.md).
4
+ Implementation: existing eleven MCP debugger tools gain the optional simulator
5
+ LLDB backend; Android remains on JDI. This is local source/build verification,
6
+ not hosted CI, an installed package, a publication or physical-device evidence.
7
+
8
+ ## Environment and method
9
+
10
+ macOS, Node 23.11.0 (repository requires >=22; hosted CI selects Node 22),
11
+ Xcode 27.2/27B52, LLDB 2103.0.34.407 and Swift 6.4.2. Simulator:
12
+ F9243BAF-62A8-4990-8A99-17F527F56FDA, iPhone 17 Pro, iOS 26.4.
13
+ The disposable Swift fixture uses `-g -Onone`, targeting iOS 18 simulator.
14
+
15
+ The runner communicates with a freshly built stdio MCP server. It checks actual
16
+ source stops, stack frames and synthetic known-value locals; independent fresh
17
+ MCP UI-tree assertions establish completion/cancellation and recovery. It does
18
+ not infer logical task identity from opaque thread IDs or task/thread names.
19
+
20
+ Installed idb can read UI trees but cannot inject HID with this Xcode: its
21
+ SimulatorKit framework path is absent. Therefore fixture actions use a private
22
+ command file, delivered by atomic rename and handled by the fixture main-loop
23
+ timer. This is independent fixture control, not proof of successful MCP tap
24
+ injection. Paused MCP taps are checked for rejection before dispatch. No
25
+ production app instrumentation, source evaluation or security changes are used.
26
+
27
+ ## Evidence and results
28
+
29
+ The final stable-build simulator journey **PASS**ed with real pause expiry enabled.
30
+ [Compact evidence](evidence.json) records the exact server/fixture/test hashes,
31
+ source stops and bounded synthetic values. Recomputing the final source/build hash
32
+ matched the runtime fingerprint. Log: `/tmp/ios-debugger-final.log`.
33
+
34
+ | Check | Result |
35
+ | --- | --- |
36
+ | Exact source binding; pending source; non-executable line rejection | PASS |
37
+ | Core stack/locals and into/over/out | PASS: known=41, fresh result=42 UI |
38
+ | Pause guard/readiness and stale references | PASS |
39
+ | Cancelled event wait and usable running session | PASS |
40
+ | Before/after await with nested async child and actor/main-actor transitions | PASS: known=42, after=43, fresh Async result 44 UI |
41
+ | Step over await | PASS: source line 20 to line 21 in the resumed partial function |
42
+ | Task cancellation | PASS: cancelled=true and fresh Task cancelled UI |
43
+ | Real 60-second pause expiry | PASS: pause_limit, complete cleanup and fresh result UI |
44
+ | Target process exit | PASS: disconnected state, complete cleanup and fixture relaunch |
45
+ | Idempotent detach and server shutdown while paused | PASS: no remaining resources; independent fresh result UI |
46
+
47
+ `npm run verify` **PASS**: lint/structural/architecture checks, TypeScript build,
48
+ all 96 unit files and generated plugin consistency. Log:
49
+ `/tmp/ios-debugger-final-verify.log`. The portable usage skill and generated Cursor
50
+ copy now describe the advertised iOS backend; this does not install/update a
51
+ connected or pinned published server.
52
+
53
+ The broader device suite passed the final Android debugger regression, then
54
+ **FAIL**ed on an Android interaction SDK request timeout (120000 ms); its original
55
+ log does not identify the call. A focused assertion-identical retry with tool-name
56
+ and elapsed-time logging **PASS**ed every call (approximately 47 seconds total).
57
+ The timeout was not reproduced and its cause is unknown; no assertion, timeout,
58
+ retry policy or interaction implementation was changed. Original and retry logs:
59
+ `/tmp/ios-debugger-device-suite.log` and
60
+ `/tmp/ios-debugger-interaction-retest.log`. The unchanged full-suite retry passed the Android debugger/interaction checks and
61
+ both-platform screenshot/log/tree/correlated-evidence checks, then **FAIL**ed the
62
+ last Android Perfetto active-shutdown cleanup assertion (one owned capture file
63
+ still existed). Log: `/tmp/ios-debugger-device-suite-retry.log`.
64
+
65
+ The same isolated Perfetto test then **PASS**ed on both unmodified HEAD
66
+ `8bafc318a6342cf4585621d2108353836d40f72d` (archived and compiled in a temporary
67
+ copy using the same dependencies) and the current build. Logs:
68
+ `/tmp/ios-debugger-trace-baseline.log`, `/tmp/ios-debugger-trace-current.log`.
69
+ This does not establish the original timeout/cleanup cause or prove it fixed.
70
+ No tracing source, assertion or deadline was changed; no full device-suite pass
71
+ is claimed. The failed check's exact owned artifact was removed through the
72
+ existing identity-checking trace adapter. Final checks found no MCP capture files
73
+ or ADB forwards. Debugger/coroutine test packages and Swift build directories were
74
+ removed; unrelated installed fixtures were preserved. The iOS simulator was
75
+ returned to its initial shutdown state; the Android emulator remains running.
76
+
77
+ Independent unchanged iOS screenshot, log and UI-tree smoke checks **PASS**ed.
78
+ Log: `/tmp/ios-debugger-ios-observations.log`. These establish observation delivery
79
+ and structure, not a layout claim or successful HID input.
80
+
81
+ The initial Android runtime, Kotlin locals and coroutine MCP regressions passed
82
+ on emulator-5554, Android 16/API 36. Kotlin/stdlib 2.0.21, coroutines 1.9.0 and
83
+ fixture JDK 17.0.11 used existing cached dependencies and `/tmp/pr96-kotlinc`.
84
+ The debugger uses its ordinary Android Studio JBR discovery. Raw local logs:
85
+ `/tmp/ios-debugger-android-regression.log`,
86
+ `/tmp/ios-debugger-kotlin-regression.log`,
87
+ `/tmp/ios-debugger-coroutine-regression.log`.
88
+
89
+ `npm pack --dry-run --json` and explicit contents assertions passed for all three
90
+ LLDB Python modules and compiled iOS backend modules. CI packaging assertions now
91
+ cover these resources. This does not establish installed-package loading.
92
+
93
+ ## Findings resolved during development
94
+
95
+ - Ignore internal LLDB transitions without stopped-thread evidence; consume LLDB
96
+ process events before reading public process/thread snapshots.
97
+ - Keep fixture variables live across await. The original constant's scope had no
98
+ location in a resume partial function; requiring its later use preserved actual
99
+ `known=42` inspection. Assertions were retained, not replaced by availability.
100
+ - Use LLDB's invalid-line sentinel for the error-reporting StepInto overload;
101
+ line zero is not that sentinel. Check native step errors before acknowledging.
102
+ - Normalize Swift Bool static storage to textual true/false; never evaluate a
103
+ getter, formatter, expression or object description.
104
+ - Preserve known suspension when a stale/invalid step is rejected. Diagnostic
105
+ lifecycle fallbacks cannot bypass pause guards.
106
+ - Bound and reuse frame references; preserve cleanup uncertainty after lost worker
107
+ or process handles instead of treating process closure as confirmed release.
108
+ - Classify signals/exceptions separately from completed steps. A synchronous
109
+ `simctl terminate` probe timed out while attached; the fixture now exits itself
110
+ to test actual process-exit cleanup without blocking the runner on a signal stop.
111
+ - URL-based fixture control encountered a system confirmation. The simulator was
112
+ rebooted to clear this fixture-created prompt; URL control was removed. Atomic
113
+ file delivery avoids the timer observing a partially written command.
114
+
115
+ These failed development observations remain distinct from successful reruns;
116
+ none is represented as evidence that all environments or timings are covered.
117
+
118
+ ## Limits and review
119
+
120
+ Physical iOS devices, other Xcode/Swift/runtime versions, optimized builds,
121
+ installed/published-package operation, real device disconnect, forced host kill,
122
+ long idle/absolute expiry and a late-loaded-image binding journey remain unrun.
123
+ Pause expiry is checked live; idle/absolute expiry logic also has deterministic
124
+ worker/host unit coverage. Pending source creation is checked independently.
125
+
126
+ Swift strings and other aggregate locals expose type/opaque reference summaries;
127
+ standard supported numeric/bool wrappers use static storage. Missing debug values
128
+ remain explicitly unavailable. No suspended-task enumeration or complete logical
129
+ async-stack reconstruction is exposed. Async stepping evidence is the tested
130
+ step-over-await route, not every Swift concurrency construct.
131
+
132
+ Focused review covers target membership/start identity, source resolution, strict
133
+ schema/dispatch parity, bounded IPC/inspection, stop epochs, diagnostic privacy,
134
+ platform guards, expiry and owned teardown. Android contract compatibility remains
135
+ covered by its existing fixture and unit tests; iOS scope is simulator-only.
136
+
137
+
138
+ ## PR #103 preparation-cache follow-up — 2026-11-04
139
+
140
+ [Review comment](https://github.com/clivejefferies/mobile-debug-tools/pull/103#discussion_r4470121366)
141
+ identified duplicated prerequisite subprocesses during attach. Preparation now
142
+ shares its in-flight/successful promise per server process and clears a rejected
143
+ promise, preserving platform checks, readiness validation and worker configuration.
144
+ No tool schema, timeout or inspection behaviour changes.
145
+
146
+ - **PASS** isolated fake-toolchain regression: concurrent calls share preparation,
147
+ failure permits retry, successful configuration is reused, and worker creation
148
+ adds no extra prerequisite probes.
149
+ - **PASS** real Xcode preparation followed by cached reuse within a 100 ms budget;
150
+ no app was attached by that probe.
151
+ - **PASS** focused real iOS MCP runtime/async/cancellation/process-exit/shutdown
152
+ journey on the same simulator/toolchain. Build fingerprint:
153
+ `0200deeb0cff9f04b54ed929258ebd19185c8be86e2badb6cdcbfa21f798fdb1`.
154
+ Source fingerprint remains `295ff0f30f5670530f9cdd7073b958917c1c574ccca9c7b921e295d0d902b699`.
155
+ Log: `/tmp/pr103-cache-ios.log`. Extended pause expiry was not repeated for
156
+ this preparation-only fix; the earlier extended evidence remains above.
157
+ - **PASS** quiet `npm run verify`: lint/quality/architecture, build, all 97 unit
158
+ files and plugin consistency. Log: `/tmp/pr103-cache-verify-final.log`.
159
+ A prior run alongside native testing failed the unchanged conditional-action
160
+ timing assertion. Its isolated retry passed, as did the quiet full verification;
161
+ no assertion or timeout was modified. Original/retry logs:
162
+ `/tmp/pr103-cache-verify.log`, `/tmp/pr103-cache-conditional-retest.log`.
163
+
164
+ The regression child loads its TypeScript toolchain before platform emulation,
165
+ so the host retains its native compiler selection on Linux. This test-only ordering
166
+ adjustment also passed all 97 files in `npm run verify` (log:
167
+ `/tmp/pr103-cache-portable-verify.log`). The preparation fix was published in
168
+ `f00e9fd`, and its review thread was resolved; both hosted unit-test runs passed
169
+ that commit. Later PR-head checks must be assessed separately.
170
+
171
+ The broader device-suite failures above remain separate; this fix does not claim
172
+ that their causes were established or corrected. Simulator shutdown was restored.
173
+
174
+
175
+ ## PR #103 stored-handle guard follow-up — 2026-11-04
176
+
177
+ [Outside-diff review finding](https://github.com/clivejefferies/mobile-debug-tools/pull/103#pullrequestreview-5772655824):
178
+ `tap_element` could execute on its stored iOS handle's device while the central
179
+ guard selected Android when platform hints were omitted. The guard now obtains
180
+ its target through the same handle/platform/device validation used by execution.
181
+ Other tool defaults remain unchanged; explicit conflicting hints still reject
182
+ handle use rather than retargeting input.
183
+
184
+ - **PASS** regression using real handle creation with mocked device/UI/input
185
+ boundaries: omitted/partial hints select the stored iOS device, known pauses
186
+ prevent UI reads/input, a racing pause prevents native dispatch, and unrelated
187
+ platforms/devices remain usable. Mismatched and missing handles retain rejection.
188
+ - **PASS** mutation check: restoring the original Android-default guard route
189
+ makes that regression fail; fixed source was restored before verification.
190
+ - **PASS** `npm run verify`: lint/quality/architecture, build, all 98 unit files,
191
+ and plugin consistency. Log: `/tmp/pr103-handle-verify.log`.
192
+ - **PASS** updated real simulator MCP journey: obtain an iOS handle while running,
193
+ stop at a source breakpoint, then call `tap_element` with only that handle and
194
+ controls, omitting both platform and device ID. Observed `DEBUGGER_PAUSED`,
195
+ `delivery_status: not_delivered`, `dispatch_started: false`, and the correct
196
+ iOS debugger/device metadata. This proves pause rejection, not successful HID
197
+ injection. The existing core/async/cancellation/process-exit/shutdown journey
198
+ also passed on the same Xcode/LLDB/Swift/runtime matrix.
199
+
200
+ Native build fingerprint:
201
+ `28e56d77b9ba4caad778c223306609c3bb284b210b1ea996b927aca47134cf55`.
202
+ Log: `/tmp/pr103-handle-ios.log`. No fixture source change or timeout/assertion
203
+ weakening was needed. Device tests were run after repository verification to keep
204
+ the checks separate. The fixture was removed and simulator shutdown restored.
205
+ The earlier broader-suite limitations remain recorded above.