Skip to content

Latest commit

 

History

History
650 lines (496 loc) · 136 KB

File metadata and controls

650 lines (496 loc) · 136 KB

Changelog

Week of Jul 19 – Jul 25, 2026

✨ Features

  • Add a without_building option to test_macos, test_sim, and test_device that runs xcodebuild test-without-building to reuse the test bundle a prior run already compiled into the scoped DerivedData, skipping the build/planning phase on repeated runs; XcodebuildRunner.test gains a withoutBuilding flag (the test-arg assembly extracted into a testable static testArgs) that preserves the -destination and -only-testing/-skip-testing/-testPlan selectors into the without-building phase and records the actual action in RawBuildLog; a shared withoutBuildingSchemaProperty is merged into all three tools and threaded through TestToolHelper.runAndFormat; swift_package_test is intentionally left unchanged since SwiftPM's swift test has no build/test split; inspired by getsentry/XcodeBuildMCP#475 (#430)

Week of Jul 12 – Jul 18, 2026

✨ Features

  • Add extra_args as a session default and per-call override for passthrough xcodebuild arguments; SessionDefaults gains an extraArgs: [String]? (persisted, decoded leniently so session files written before the field still load) settable via set_session_defaults, and a new SessionManager.resolveExtraArgs resolves a per-invocation extra_args array — presence of the key, even an empty array, replaces the session default for that one call; otherwise the persisted default is used — threaded (appended last, so it takes precedence) into all 11 xcodebuild-invoking tools (build_sim, build_run_sim, test_sim, build_macos, build_run_macos, archive, test_macos, build_device, build_deploy_device, test_device, build_debug_macos), so a flag like -skipPackagePluginValidation can be set once instead of on every call; inspired by getsentry/XcodeBuildMCP#463 (#426)

🐞 Fixes

  • Resolve the simulator build destination from the selected simulator's runtime instead of hardcoding platform=iOS Simulator; build_sim / build_run_sim / test_sim built every simulator against an iOS destination, so an iOS+visionOS (or watchOS/tvOS) app targeting a non-iOS simulator built for the wrong platform and failed — the device build tools already resolved this correctly via DeviceCtlRunner.lookupDevice; SimctlRunner gains a SimulatorPlatform enum whose init?(runtimeIdentifier:) maps a CoreSimulator runtime id to a destination (the xrOS token becomes visionOS Simulator) and a resolveForBuild(matching:) that resolves a UDID or name to a concrete device, canonicalizes a name to its UDID so -destination id= is always valid, rejects an unavailable or removed runtime with a clear platformUndetermined error rather than silently guessing, and composes the correct platform=…,id=… destination; PreviewCaptureTool is intentionally left on its own iOS/macOS fallback; mirrors getsentry/XcodeBuildMCP#472 (#425)
  • Stop discover_projs from following a symlink out of the sandbox base during its recursive scan; DiscoverProjectsTool.search() validated only the initial search path against the base, then descended into any real subdirectory via FileManager.fileExists/contentsOfDirectory (both follow symlinks) without re-checking each entry — so a symlink inside the tree pointing outside the base let the walk report .xcodeproj/.xcworkspace bundles from outside the sandbox (read-only, gated behind the default sandbox, but a real boundary gap); a new PathUtility.isWithinSandbox(_:) resolves symlinks on both the candidate and base before reusing the existing separator-aware isPath(_:within:) (returning true when sandboxing is disabled, so --no-sandbox is unchanged), and search() now guards every entry with it before reporting or recursing; the exact prefix-match bug this mirrors — getsentry/XcodeBuildMCP 46b2cf6 — was already not present here since isPath(_:within:) anchors on a path separator (#429)
  • Fix two WaitForProcessExitTests that flaked under a starved cooperative thread pool during the full 1502-test parallel CI run (runs 300, 301, 304); "Detects process killed by SIGKILL mid-wait" fired the kill from a cooperative-pool Task that could be starved past the 15s wait timeout — leaving the process alive so waitForProcessExit correctly returned false and the assertion tripped — now killed from a detached Thread immune to pool pressure; and "Timeout is bounded" dropped its elapsed < 15s upper bound, which measured wall-clock around the await and so included unbounded continuation-scheduling latency after the kqueue wait resumes (observed 19.7s), keeping only the correctness and lower-bound checks since a genuine hang blocks the wait forever and is caught by the CI job timeout (#427)

Week of Jul 5 – Jul 11, 2026

✨ Features

  • Add show_last_build_raw (read-only) to xc-build and monolithic xc-mcp; it returns the complete, unparsed clang/ld output of the most recent build/test so an agent can recover the verbatim linker diagnostic — the full Undefined symbols … / duplicate symbol '…' in: block with every colliding source path — when the parsed summary truncates it and Xcode leaves a 0-byte .xcactivitylog; XcodebuildRunner.build/test now persist the raw combined stdout/stderr of every scheme build/test to a PPID-scoped file (/tmp/xc-mcp-last-build-{ppid}.log, XC_MCP_LAST_BUILD override) with a JSON metadata sidecar, capturing on both the BUILD-FAILED nonzero-exit path and the timeout/stuck partial-output path with no build-time flag; the tool defaults to LinkerDiagnostics-extracted error/linker regions (full: true dumps everything, tail: N the final lines) and always prints the on-disk path as a last-resort fallback (#414)
  • Add analyze_app_bundle (read-only) to xc-build and monolithic xc-mcp; it inspects a built .app (or the .app inside an .xcarchive) and reports total bundle size with a binary/resource split, the main executable's Mach-O segment breakdown (excluding the __PAGEZERO 4GB artifact) and architectures, the _relinkableLibraryClasses mergeable-library merge-marker count, LC_RPATH entries, linked in-project (@rpath) frameworks, per-embedded-framework sizes, and an rpath-aware launchability verdict — resolving each @rpath dependency dyld-style against the binary's actual LC_RPATH set to flag deps that only resolve via an absolute DerivedData PackageFrameworks path, making the "a loose build product is not self-contained; only an archive embeds everything" distinction explicit instead of something rediscovered via dyld crash logs; the string parsing (otool/size/nm/lipo) and resolution logic live in a pure, unit-tested Core/MachOInspector, and a single spaceless temp symlink works around cctools otool/size re-tokenizing a bundle path with spaces (e.g. MyApp (debug).app) even when passed as a single argv element (#423)

🐞 Fixes

  • Stop injecting -configuration Debug when no configuration is specified, so xcodebuild honors the scheme's own Build/Run/Test action configuration; SessionManager.resolveConfiguration returned a hardcoded "Debug" fallback whenever a tool got no configuration argument and no session default, and XcodebuildRunner.build/buildTarget/test/clean/showBuildSettings always appended the flag — overriding a scheme whose action uses a non-Debug configuration and reporting the wrong build settings (bundle identifier, derived .app path) from build_macos, test_macos, get_mac_app_path, get_app_bundle_id, show_build_settings, and the rest; the configuration is now optional (nil = unspecified) end-to-end — through the runner, resolveConfiguration, validateMacOSSupport, findProjectRoot, TestToolHelper, and ShowBuildDependencyGraph — and -configuration is emitted only when a value is actually provided; also removed the duplicated inline "Debug"-defaulting blocks in the bundle-id/app-path tools; mirrors upstream getsentry/XcodeBuildMCP 623db7a (#424)
  • Fix three flaky WaitForProcessExitTests (CI runs 295–297) by replacing ProcessResult.waitForProcessExit's Task.sleep poll loop with a blocking kqueue EVFILT_PROC/NOTE_EXIT wait dispatched to a dedicated thread; polling on the cooperative pool made both exit detection and the timeout unreliable — under a starved pool (full parallel test run) a 100ms sleep could overshoot by ~20s, so the loop either missed a process that had exited (returning false) or blew past its wall-clock deadline (elapsed 19.9s vs a 15s bound); the kqueue wait runs off the cooperative pool so exit detection is exact (a kernel event, not sampling) and the timeout is enforced by kevent's own timespec, with a fast-path kill(pid, 0) for an already-gone pid and an ESRCH-on-register race treated as an exit; the affected tests now pass in 0.369s (was ~22s and flaky) (#422)
  • Fix PathUtility's sandbox containment check rejecting every path when the base directory is the filesystem root; the separator-anchored isPath(_:within:) added to close the sibling-path escape computed basePath + "/", which becomes "//" for base / — a prefix no real path matches — so a PathUtility(basePath: "/") threw pathOutsideBasePath for all inputs and broke 11 CI tests (MoveFileTool, SearchTestPlansTool, AddTargetToTestPlanTool) that use temp paths under /var/folders/…; root is now special-cased to contain any absolute path, preserving the sibling-escape guard for all other bases; also silenced the InteractRunner build warning about ref as! AXValue never producing nil by switching the CFTypeID-verified downcast to unsafeDowncast (#421)
  • Add add_storekit_config to xc-project (and monolithic xc-mcp) and harden the file/scheme operations around .storekit files; the new tool does the whole coherent job in one call — creates the project file reference (so the config appears in Edit Scheme → Run → Options → StoreKit Configuration), optionally adds it to a named test target's resources for SKTestSession(configurationFileNamed:), and wires the scheme's Run/Test StoreKitConfigurationFileReference with the correct scheme-relative path — warning when the target isn't a unit/UI test bundle (and skipping the resource add so a config can't be bundled into a shipping app), when the config is already in an app target's Copy Bundle Resources, or when no scheme is wired; remove_file on a .storekit now cleanly unwires every scheme reference that resolved to it and warns about the picker / SKTestSession couplings instead of leaving a dangling reference; validate_scheme flags a StoreKit reference whose relative path doesn't resolve and a .storekit shipped in an app target's resources; and the raw-XML scheme editor was extracted into a shared SetSchemeStoreKitConfigTool.applyStoreKitReference (plus a storeKitIdentifiers reader) that writes each identifier verbatim relative to the scheme file and edits actions independently, so a sibling action's path can never be recomputed to the wrong depth (#408)
  • Warn from the macOS launch tools (build_run_macos, build_debug_macos, launch_mac_app) when the launched scheme's Run action references a StoreKit configuration; apps launched directly (not through the Xcode IDE) never receive the scheme's StoreKitConfigurationFileReference — Xcode injects it over a private storekitd XPC hand-off the CLI can't replicate — so Product.products(for:) silently returns an empty array and StoreKit-gated features stay disabled; rather than expose a storekit_config parameter that couldn't reliably apply it, a new pure StoreKitLaunchAdvisory reads the Run-action reference (reusing storeKitIdentifiers), flags when it doesn't resolve to a file, and points at the paths that do work (run from Xcode, or drive it from tests via SKTestSession); launch_mac_app uses the session's default scheme/project best-effort since it takes no scheme argument (#407)
  • Fix find_build_settings omitting project-level buildSettings, which made an audit look complete when it wasn't; the tool scanned only native-target buildSettings dicts and silently ignored the PBXProject-level ones, so a malformed inherited flag (e.g. OTHER_LDFLAGS = "-Wl -no_exported_symbols" on the whole project, stripping every framework's exported symbols) was invisible unless a target happened to override it; it now scans the project-level buildSettings in the same single pass and reports matches under a synthetic [project] label, with the values and configuration filters applied uniformly across the project scope and every target scope via a shared scan helper (#410)
  • Fix the linker-error parser reporting Undefined symbol '<name>' for ld duplicate symbol diagnostics — opposite root causes, so the label pointed a debugging session 180° the wrong way; LinkerError now carries an explicit kind (undefined / duplicate / other) that BuildResultFormatter labels from, the parser collects every indented defining-file line under a duplicate symbol 'X' in: header (framework binaries, dylibs, and the literal bundle-file — not just .o/.a, which had dropped them and left the error looking undefined), flushes each pending duplicate on the next header / the ld: N duplicate symbols summary / end-of-parse so multiple duplicates aren't collapsed into one, and folds kind into the dedup key so an undefined and a duplicate of the same symbol name stay distinct (#411)

🗜️ Tweaks

  • Reorganize the flat Sources/Core directory (59 top-level files, stale-documented as 25) into concern-based subdirectories via git mvRunners/, BuildOutput/, ProjectFile/, Interaction/, Locators/, MCP/, Testing/, Session/, and AppBundle/, leaving five genuinely cross-cutting singletons at the root — with no Package.swift change needed since the XCMCPCore target globs Sources/Core recursively; updated CLAUDE.md's Package Structure tree and the two path-specific references to match (#409)
  • Add audit_swift_packages to xc-project (and monolithic xc-mcp); a read-only, offline SwiftPM dependency-health check adapted (reimplemented, not a dependency) from crleonard/swift-package-audit that cross-references a project's declared XCRemoteSwiftPackageReference requirements against its Package.resolved pins and reports missingPackageResolved, unresolvedReference (declared but unpinned), stalePin (pinned but no longer declared), branchDependency / revisionDependency / exactVersion stability warnings, duplicateURLForm, and urlFormMismatch; a new Core/PackageResolvedParser normalizes both the legacy v1 (object.pins) and modern v2/v3 (pins) formats and locates the pins file under the project's embedded project.xcworkspace (#406)
  • Refactor Sources/Core/BuildOutput from a /swift review pass; migrate CoverageParser and BuildSettingExtractor off JSONSerialization + [String: Any] casting to Codable models decoding from Data, collapse BuildSettingExtractor's three copies of the settings-JSON parse behind one shared jsonSetting helper, extract appendErrorIfNew/appendWarningIfNew in BuildOutputParser and a single skipCommentOrString scanner helper in PreviewExtractor (replacing the string/comment-skip triad duplicated at four sites), add a pluralized helper and reserveCapacity in BuildResultFormatter, make ErrorExtractor's only_testing target-membership check O(1) via a Set, and drop a newestDate force-unwrap in CoverageParser — behavior-preserving, 1455 tests green (#412)
  • Refactor Sources/Core/AppBundle; parallelize per-framework codesign inspection in CodeSignInspector via a withTaskGroup, dedupe the codesign-output parsing, and standardize path handling across AppBundlePreparer and IconManifest (#413)
  • Refactor Sources/Core/Interaction from a /swift review pass; extract shared AppleScript (one escape — the two prior copies disagreed on escaping \n\r\t) and WindowList (typed wrapper over CGWindowListCopyWindowInfo) helpers plus SimctlRunner.findDevice, collapsing the duplicated appleScriptEscape / resolveBootedDevice / window-enumeration / focus-window-osascript bodies across SimulatorUIInput, SimulatorKeyboardHelper, and WindowCapture; replace all six blocking usleep calls inside the SimulatorUIInput actor with Task.sleep so they suspend instead of pinning a cooperative-pool thread; drop the per-sampled-pixel NSColor allocation in ScreenRectDetector by reading NSBitmapImageRep.bitmapData bytes directly and cache one CGEventSource instead of one per synthesized event; and rename bundleId/simulatorId/elementId to …ID at all call sites (#416)
  • Refactor Sources/Core/Locators from a /swift review pass; fix a path-prefix sandbox-escape bug where a raw hasPrefix boundary check let /base-evil pass the containment test for base /base (resolvePathURL / makeRelativePath now go through an isPath(_:within:) that requires the match to land on a path separator), extract findAncestorEntry(matching:startingFrom:) and an isWorkspaceBundle predicate to dedup findProjectPath / findWorkspacePath / findAncestorDirectory, migrate PIFCacheReader off [String: Any] + JSONSerialization + ~15 as? casts to private Decodable DTOs decoded through a generic decodeEach (with throws(Error) and a hoisted file-scope regex), and drop PIDResolver's redundant MainActor.run wrapper while renaming bundleIdbundleID (#417)
  • Consolidate Sources/Core/ProjectFile pbxproj parsing from a /swift review pass; extract a shared PBXProjParsing (one project.pbxproj reader, splitLines, and a single 24-char object-identifier test replacing four divergent implementations) reused across PBXProjTextEditor / PBXTargetMap / PBXProjReferenceAudit, add a stateful PBXProjEditor that holds the file as [String] and applies edits in place — migrating the six tools that chain 2+ edits (add_framework alone chained 15) off the per-edit split/join so the whole batch splits and re-joins once, with the existing static String -> String methods kept as thin wrappers so single-edit callers and tests are unchanged — and micro-optimize generateUUID (via String(unsafeUninitializedCapacity:)) and extractLeadingUUID (dropping an O(n) full-line count); behavior-preserving (#418)
  • Refactor Sources/Core/MCP from a /swift review pass; give ArgumentExtraction's getRequiredString / parseBatchTranslationEntries typed throws(MCPError) (converting a compactMap to a reserveCapacity'd loop so the typed throw propagates) and extract a shared stringValues(from:) reused by getStringDictionary and the batch-translation parser, replace ProgressReporter.ingest's chunk.split(...).reversed() with a backward-scanning lastNonBlankLine(in:) helper (no [Substring] allocation on the streaming hot path) and name the poll Task, cache NextStepHints' JSONEncoder as a static let instead of per-rendered allocation, and rename bundleIdbundleID (#419)
  • Refactor Sources/Core/Runners from a /swift review pass; add a scoped BuildGuard.withGuard that releases the cross-process lock fd via defer on every exit path (replacing the manual acquire / do-catch / release triplets in SwiftRunner.run, XcodebuildRunner.run, and an extracted DetectUnusedCodeTool.runPeriphery), a shared ProcessResult.xcrun(_:arguments:) that SimctlRunner / DeviceCtlRunner / XctraceRunner now delegate to plus a Process.xcrun(_:arguments:) factory for the unstarted-process cases (XctraceRunner.record, SimctlRunner.recordVideo), and an XcodebuildRunner.projectArgs(...) helper for the -workspace/-project + scoped -derivedDataPath prefix duplicated across build/buildTarget/test/clean/listSchemes/showBuildSettings; harden InteractRunner by collapsing the double AX children fetch per element (getAttributes takes a precomputed childCount; one children(of:) helper reused by traverse/findChildByTitle/navigateMenu), replacing AXValue/AXUIElement force casts with CFGetTypeID-guarded casts, and making navigateMenu async via Task.sleep instead of blocking Thread.sleep; plus lint hygiene — named the previously-unnamed Task/addTask closures (xcodebuild readers + watchdog, subprocess run/timeout, lldb command timeout), a static JSONDecoder in SimctlRunner, and a documented @unchecked Sendable on SendableAXUIElement (#420)

Week of Jun 28 – Jul 4, 2026

🐞 Fixes

  • Fix two flaky CI tests that tripped fixed time budgets under heavy parallel load (1422 tests) rather than any product bug; SubprocessTimeoutGroupKillTests now sleeps 600 (effectively unbounded) inside the subprocess and asserts the kill returns under 60s instead of 20s, decoupling the "did not hang" guard from natural command completion so cooperative-pool starvation can't trip it while a true hang still does, and SessionManagerWarmupTests raises its waitUntilCompleted poll budget from 15s to 60s (a .background-priority warmup that completed in 0.08s wasn't observed within 15s on a saturated runner) and fixes the stale "within 2s" failure message (#405)
  • Stop remove_target from orphaning a dependent target's own outgoing dependency edge; removing a target (e.g. a unit-test bundle that depends on an app target) deleted the target and its incoming edges but left its outgoing PBXTargetDependency + PBXContainerItemProxy objects behind, still pointing at the depended-on target — a pure orphan that the safe-write dangling-reference gate then used to refuse removal of that target; TargetGraphCleanup.removeReferences now also deletes the target's own outgoing dependencies and proxies and clears its dependencies array (fixing both remove_target and remove_app_extension, which share the helper), and repair_project gained a garbage-collection pass that drops PBXTargetDependency objects not referenced by any target (or whose target is gone) and PBXContainerItemProxy objects not referenced by any dependency/reference-proxy (or whose remote object is gone), so a project already left in the orphaned state can be recovered without a git restore (#404)
  • Stop remove_target (and remove_app_extension / remove_swift_package) from durably writing a project Xcode can't load; the real defect was incomplete cleanup leaving dangling object references — remove_target left a PBXFileSystemSynchronizedBuildFileExceptionSet and a TargetAttributes entry pointing at the deleted target, and remove_app_extension orphaned PBXTargetDependency objects — none of which the post-op validator (which only re-scanned .xctestplan / .xcscheme files) could catch; added a universal PBXProjReferenceAudit wired into the SafeProjectWrite chokepoint that refuses any write introducing a dangling reference (delta-checked against the on-disk baseline so pre-existing and cross-project remoteGlobalIDString references never false-positive), centralized the cross-target teardown (dependencies + proxies, container proxies, TargetAttributes, exception sets) in a shared TargetGraphCleanup helper, and made the removers refuse-by-default when another target still references the one being removed (remove_target gains an explicit cascade flag; remove_swift_package with remove_from_targets=false now refuses rather than leaving a product dependency pointing at a deleted package); plutil -lint had passed the corrupt files because they were valid plists with only an internally-inconsistent object graph, and the huge diffs that masked the bug were cosmetic XcodeProj serializer churn, not data loss (#403)
  • Stop remove_target from leaving a project that won't load or crashing the server; the tool now cascades the removal to every .xctestplan (entry stripped via TestPlanFile) and .xcscheme (owning wrapper element removed via new Core/SchemeTargetEditor, raw-XML editing rather than a lossy XCScheme model round-trip) that still references the target, editing those cross-file references before the project file drops the target so no intermediate on-disk state ever points at a missing target, then re-scans every test plan and scheme as a post-op gate that fails on any surviving dangling reference; it also now removes the PBXBuildFiles that embed/link the target's product from other targets' build phases (and sweeps orphans) before serializing, since a build file left pointing at the deleted product tripped a force-unwrap in XcodeProj's serializer and trapped the process — tearing down the MCP connection (-32000: Connection closed) instead of surfacing as a normal error (#402)
  • Route every Xcode project mutation through a single durable-write chokepoint so a crash, kill, invalid serialization, or concurrent writer can no longer corrupt or clobber the shared project.pbxproj; the new SafeProjectWrite writes bytes to a same-directory temp file, fsyncs, and rename(2)s over the original (so a crash leaves it byte-for-byte intact), serializes writers with an advisory flock on a per-project lock file, validates the candidate with plutil -lint before promoting it (an invalid project is rejected and the original left untouched), preserves the original mode bits, and refuses the write with a concurrentModification error when a caller passes the bytes it read and the file changed underneath it; both write paths (PBXProjWriter via PBXProj.dataRepresentation, PBXProjTextEditor) now funnel through it — making all 49 mutation call sites atomic, validated, and lock-serialized — and the highest-risk tools (remove_target, add_target, remove_swift_package) capture the load-time bytes to refuse a stale-read clobber outright (#401)

Week of Jun 21 – Jun 27, 2026

✨ Features

  • Extend manage_type_identifier so identifier-less / malformed type declarations are manageable; update / remove now accept match_description (by UTTypeDescription) or match_index (1-based, as shown by list_type_identifiers) in addition to identifier (precedence index → description → identifier), so a declaration missing its UTTypeIdentifier can be located, and passing identifier when located by description/index backfills the key in place instead of orphaning the entry behind a new appended one; a new prune action removes every declaration in the chosen list missing a non-empty UTTypeIdentifier (reporting which, and dropping the plist key when the list empties); and validation now requires a non-empty identifier on add and refuses to leave an entry without a UTTypeIdentifier on update (#399)
  • Add set_scheme_storekit_config to xc-project (and monolithic xc-mcp); sets or clears a scheme's StoreKit configuration by writing/removing the <StoreKitConfigurationFileReference> child under a shared .xcscheme's LaunchAction (Run) and/or TestAction (Test), with action add|remove and target_actions launch|test|both; the identifier is computed as the .storekit path relative to the scheme file (a repo-root Thesis.storekit becomes ../../../Thesis.storekit, matching Xcode's serialization) via a new SchemePathResolver.schemeRelativeIdentifier; idempotent (replaces an existing reference rather than duplicating), and it edits the scheme XML directly rather than round-tripping through XCScheme because the XcodeProj model only represents the reference on LaunchAction, so a model round-trip would silently drop an existing TestAction reference (#398)

🐞 Fixes

  • Fix the broken simulator UI-automation tools (tap / long_press / swipe / gesture / type_text / key_press / button); they shelled out to simctl io <device> <tap|swipe|keyboard|button>, but simctl io has no input operations, so every call was a no-op that errored; replaced with a host-side SimulatorUIInput actor that synthesizes CGEvents on the on-screen Simulator window — locating the device window via CGWindowList, detecting the device-screen rectangle inside the title bar and bezel via projection profiles, and mapping device-pixel coordinates (the screenshot image space) onto global display points; typing connects the hardware keyboard and sends US-layout keycodes (ASCII), hardware buttons drive the Simulator Device menu, and the screencapture / osascript subprocesses run through ProcessResult for cancellation safety; requires the Simulator window visible plus Screen Recording and Accessibility permissions (#396)
  • Drop the unsupported OnFailure value from set_test_plan_options' diagnostic_collection_policy enum; Xcode 26.2 only accepts Always and Never for XCTHDiagnosticCollectionPolicy, and writing OnFailure made the test plan unreadable ("String representation of XCTHDiagnosticCollectionPolicy was not a supported value"); the tool description no longer recommends OnFailure and now suggests Never to cut per-test diagnostic overhead (#395)

🗜️ Tweaks

  • Add set_test_plan_options to xc-project (and monolithic xc-mcp); edits a .xctestplan's per-configuration options block or plan-level defaultOptions for the keys the sibling SetTestPlan*Tool tools didn't reach — diagnosticCollectionPolicy (Always / OnFailure / Never), userAttachmentLifetime and uiTestingScreenshotsLifetime (keepNever / keepAlways / deleteOnSuccess), codeCoverage, and mainThreadCheckerEnabled; configuration_name targets a named configurations[] entry while omitting it edits defaultOptions, only the keys you pass are written (others left untouched), enum values are schema- and execute-validated, and a clear array resets keys to the plan default; reuses Core/TestPlanFile.swift and also backfills the missing set_test_plan_target_parallelizable entry in ServerToolDirectory (#394)

Week of Jun 14 – Jun 20, 2026

✨ Features

  • Add xcstrings_promote_literals to xc-strings (and ServerToolDirectory); promotes hand-typed localizable string literals to reusable manual String Catalog keys by adding extractionState = manual source-language entries to the .xcstrings, deriving a SCREAMING_SNAKE key (or accepting an explicit one) and reporting the camelCased Swift symbol Xcode 26 generates (e.g. NONE_SELECTED.noneSelected) so call sites like Button("Cancel") can be rewritten to Button(.cancel); reuses an existing key that already holds the same value and reports collisions when a key exists with a different value; parameterized values are supported (pass the format string, e.g. "Add %1$(ordinal)@ citation", and the result includes the generated method signature addCitationToGroup(ordinal: String)); create-only — it does not scan or rewrite Swift sources (#392)

🐞 Fixes

  • Namespace xc-mcp's scoped -derivedDataPath by platform so xc-build (macOS) and xc-simulator (iOS) builds against the same project no longer share Build/Products / Build/Intermediates.noindex and cross-link framework slices (the building for 'macOS', but linking in dylib built for 'iOS-simulator' GRDB cascade); DerivedDataScoper now derives an SDK-style suffix (-macosx, -iphonesimulator, …) from the build destination, XcodebuildRunner threads destination through build/buildTarget/test/showBuildSettings, every macOS read-back tool (GetMacAppPath, BuildRunMacOS, ProfileAppLaunch, DiffBuildSettings, the findProjectRoot diagnostics, and ShowBuildLog — which had been bypassing scoping entirely) passes the matching destination, and clean(derived_data: true) sweeps every per-platform sibling so one clean can't leave the other platform's contaminated cache behind (#391)
  • Fix four rough edges in the new-framework-module flow across move_group / create_group / add_target / add_dependency; move_group now snapshots where every synchronized folder resolves on disk before a re-path or re-parent and rewrites each affected child's path so it keeps pointing at the same directory (fixing the cascade that left Sources / Tests red after correcting a parent group's path), reporting how many children it preserved; create_group appends a Warning: when a supplied path (relative to the parent group) resolves to a directory that doesn't exist, catching the doubled-prefix mistake (parent_group=Integrations, path=Integrations/GoogleDocsIntegrations/Integrations/GoogleDocs); add_target sets DEFINES_MODULE=YES / SKIP_INSTALL=YES on framework targets and gains a create_group boolean (default true) to skip the empty placeholder navigator group; add_dependency gains a link_binary boolean that also adds the dependency's product to the target's Link Binary With Libraries phase for in-project dependencies (re-running links a dependency previously added without one); shared on-disk-path arithmetic extracted to a new OnDiskPath helper (#393)
  • Fix two flaky CI tests; raceTimeout in Sources/Core/ProcessResult.swift is now deterministic — the deadline task raises a sticky TimeoutFlag (a copyable Sendable box around a Mutex, so it crosses the addTask sending boundary) before killing the process group, so a post-kill run completion can no longer win the task-group race and swallow the ProcessError.timeout under parallel load; SessionManagerWarmupTests drops its explicit 5s poll override for the 15s default so the .background-priority warmup isn't starved on saturated runners (#390)

🗜️ Tweaks

  • Require positive evidence of build success in BuildOutputParser; a build or test killed before any terminal marker (OOM Killed: 9, truncated stream) now reports a new incomplete status instead of a false green, status is reconciled against the aggregate failed-test count so it can never disagree with summary.failedTests, and ** TEST SUCCEEDED ** / ** TEST EXECUTE SUCCEEDED ** / parenthesized Build succeeded (…) / xcbeautify Build Succeeded markers are now recognized; ported from xcsift #73 (#389)

Week of May 31 – Jun 6, 2026

🐞 Fixes

  • LLDBCommandTimeoutTests reader-leak regression test flaked on CI when cooperative-pool starvation under parallel load delayed the next sendCommand("version") past its tight 3s budget on a 5s-commandTimeout session; widened the session commandTimeout to 30s and the "reader didn't leak" budget to 15s so the binary leak signal (response silently swallowed → call wedges to the full timeout) still trips while absorbing CI variance (#372)
  • Add search_test_plans to xc-project (and monolithic xc-mcp); given a project path and substring, walks every .xctestplan under the project parent and reports the JSON path + matching value per hit (keys and stringified leaves), with an optional case_sensitive flag — closes the rename-sweep gap where pbxproj/entitlements/Swift sites had bulk tools (find_build_settings, etc.) but test-plan JSON did not, forcing a per-file Read loop just to confirm no plan still references an old bundle ID, scheme, or target name (#382)
  • remove_target leaves orphaned references in project.pbxproj (#347)
  • debug_evaluate user-cancel on multi-line Swift expression with custom types (#384)
  • mcp__xc-build__archive reported success but produced no .xcarchive on disk; the no-output watchdog (added in #331) recognised the build-phase Build succeeded in … marker as terminal during an archive run and short-circuited a still-running install/codesign phase, returning exit 0 while xcodebuild was SIGKILL'd mid-install; outputShowsBuildFinished now takes the xcodebuild argv and, when archive is the action, only treats archive-specific markers (** ARCHIVE SUCCEEDED/FAILED **, Archive succeeded in …, Archive failed after …) as terminal; ArchiveTool also verifies the .xcarchive bundle exists after checkBuildSuccess and throws a clear "reported success but no bundle on disk; retry with a larger timeout" error otherwise (#385)

✨ Features

  • Add set_framework_merge_attribute to xc-project (and monolithic xc-mcp); toggles the per-library Merge PBXBuildFile attribute on entries inside a target's PBXFrameworksBuildPhase (the flag MERGED_BINARY_TYPE = manual uses to pick which mergeable dependencies actually merge), matching SPM productName, cross-project PBXReferenceProxy name/path, or local framework path/last-component; preserves sibling attributes (e.g. Weak), refuses ambiguous matches, no-ops when already in the requested state; list_frameworks_phase now appends merge=true to entries whose ATTRIBUTES contains Merge so manual-merge setups are auditable without re-parsing pbxproj (#388)
  • Add remove_build_setting to xc-project (and monolithic xc-mcp); deletes a build setting key from a target's (or the project's) XCBuildConfiguration.buildSettings dict for a given configuration (or All), with a no-op + clear message when the key isn't present; closes the unset gap where the only options were set_build_setting to an empty string (semantically different from unset) or hand-editing project.pbxproj (#387)
  • Add list_dependencies and remove_dependency tools to xc-project; lists each PBXTargetDependency edge for a target with uuid, proxyType, remoteGlobalID, remoteInfo, and containerPortal, and drops a specific edge plus its PBXContainerItemProxy without touching the Frameworks build phase or the dependency target — closing the round-trip with the existing add_dependency (#371)
  • Add list_frameworks_phase to xc-project; classifies each PBXFrameworksBuildPhase entry as fileRef, productRef, crossProject, or dangling, flagging crossProject entries with no matching PBXTargetDependency edge as the link-only asymmetry that can produce duplicate build-graph nodes; validate_project grows a paired checkReferenceProxyWithoutDependency warning (#373)
  • Extend validate_project with two structural checks for duplicate PBXTargetDependency edges and stale remoteInfo across proxies pointing at the same target; the first groups a target's dependencies by resolved remote identity and warns on collapsed-edge collisions, the second flags rename-without-refresh proxies whose cached remoteInfo strings disagree — both classes silently produce "Multiple targets in the build graph" failures, often only at archive time (#375)
  • Add dump_pif and why_target_id tools to xc-project plus a shared PIFCacheReader that reads Xcode's on-disk PIF (Project Interchange Format) cache; why_target_id resolves a 64-char target hash (raw or from the full target-<Name>-<hash>-SDKROOT:… error string) to the matching PIF target(s), surfacing duplicate-build-graph-node collisions plus the owning project and every consumer; dump_pif returns a cache summary or scoped raw JSON for workspace/project/target inspection — no hash reproduction needed, the 64-char ID is the top-level guid field Xcode already writes to disk after every build (#374)
  • Teach xc-project's add_dependency to wire cross-project PBXTargetDependency edges; when dependency_name isn't a native target of the consumer project, the tool now scans rootObject.projects for a referenced sub-.xcodeproj exposing a matching PBXNativeTarget and synthesizes a PBXContainerItemProxy(containerPortal: .fileReference(<projectRef>), remoteGlobalID: .string(<remoteUUID>), proxyType: .nativeTarget) plus a target-less PBXTargetDependency, eliminating the hand-edit that was previously required to add ordering edges like Core → GRDBCustom; optional cross_project_path disambiguates when multiple sub-projects expose the same target name (#376)
  • Add export_archive tool to xc-build (and monolithic xc-mcp) that wraps xcodebuild -exportArchive, synthesizing ExportOptions.plist from method / team_id / signing_style / provisioning_profiles / destination and writing it next to the export output for inspection; rejects the deprecated pre-Xcode-16 method names (app-store, ad-hoc, development) with a hint pointing at the modern spelling, always passes -allowProvisioningUpdates so missing distribution profiles regenerate on demand, and forwards -authenticationKeyID / -authenticationKeyIssuerID / -authenticationKeyPath when destination=upload to deliver straight to App Store Connect — closes the archive → export → upload loop that previously dead-ended at the .xcarchive bundle (#386)
  • Add archive tool to xc-build (and monolithic xc-mcp) wrapping xcodebuild archive with generic/platform=macOS or generic/platform=iOS; defaults match Xcode Cloud's pre-archive flags (configuration=Release, code_signing_allowed=false injects CODE_SIGNING_ALLOWED=NO/REQUIRED=NO plus empty CODE_SIGN_IDENTITY/ENTITLEMENTS, optional skip_macro_validation and skip_package_plugin_validation, 600s timeout); reuses the XcodebuildRunner.build(action: "archive", …) path so error parsing, partial-diagnostics formatting, and process-group lifecycle match build_macos, unblocking local repro of archive-only failures (mergeable-library duplicate symbols, cross-platform module leakage) that build_macos/build_sim cannot reach (#377)
  • Add find_build_settings to xc-project (and monolithic xc-mcp); bulk-queries every native target in a single pbxproj load and returns each (target, configuration, setting) pair matching the requested setting names, with an optional values substring filter and configuration scope; replaces the per-target get_build_settings loop pattern when auditing MERGEABLE_LIBRARY, MERGED_BINARY_TYPE, SUPPORTED_PLATFORMS, DEVELOPMENT_TEAM, or SWIFT_UPCOMING_FEATURE_* across an app and its frameworks (#378)
  • Add find_link_flag and list_run_script_phases to xc-project (and monolithic xc-mcp); find_link_flag substring-searches every target's OTHER_LDFLAGS and reports each (target, configuration, matching element) hit for diagnostics like stray -merge_framework or -Wl,-no_warn_duplicate_libraries injected by mergeable-library wiring; list_run_script_phases lists every PBXShellScriptBuildPhase across targets with name, position, shellPath, inputs/outputs, file lists, runOnlyForDeploymentPostprocessing, alwaysOutOfDate, dependencyFile, and the full shell-script body, optionally restricted to a single target or filtered by a substring of the body (#379)
  • Extend list_targets with optional bulk filters (product_type, has_dependency, missing_dependency, has_setting with optional value substring, missing_setting) so a single call can answer audits like "every unit-test target lacking a dependency on ThesisApp" or "every framework target whose SUPPORTED_PLATFORMS is unset"; when any filter is supplied the per-target line upgrades from - name (productType) to - name [id=… productType=… dependencies=[…]] so callers don't need a paired list_dependencies round-trip, while unfiltered output stays byte-compatible with the prior shape (#380)
  • Add set_copy_files_phase_subpath to xc-project (and monolithic xc-mcp); renames a PBXCopyFilesBuildPhase's dstPath in place, locating the phase by phase_name, dst_path, or as the target's sole Copy Files phase, so a rename like docxDefaultStyles (mitigating the case-insensitive APFS collision between a framework binary DocX and its sibling docx resource folder during iOS archive) preserves the phase's identity, files, name, destination, and any PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet linkages instead of forcing a remove + recreate + relink sweep; remove_copy_files_phase gains the same dst_path alternate identifier and phase_name is now optional, making unnamed phases (common in auto-generated app-side Copy Files phases) addressable; shared CopyFilesPhaseLocator helper centralizes the lookup (#383)
  • Add XC_MCP_HEADLESS_LAUNCH env var that suppresses focus-stealing GUI launches; when set to 1 or true, launch_mac_app and build_run_macos pass -g to open (background launch, no foreground steal) and open_sim skips launching Simulator.app entirely since simctl boot is sufficient for simctl-driven automation; new FocusPolicy helper in XCMCPCore centralizes the env-var check and arg construction, open_in_xcode deliberately bypasses the policy since surfacing a window is its purpose; ported from getsentry/XcodeBuildMCP commit 59d5ca3e (#381)
  • xc-project: scaffold_module should handle public imports and access control (#196)
  • Add tool to extract and query Swift module symbol graphs (#218)
  • Add macOS window interaction tools; scroll + click/type into a running app window (#349)
  • scaffold_*_project: use synchronized folder groups so add_file isn't needed for new sources (#286)
  • Port runtime UI-automation snapshot model (rs/1) from XcodeBuildMCP #416 to simulator tools (#332)

🗜️ Tweaks

  • xc-project: add_target gaps and scaffold_module composite tool (#170)

Week of May 24 – May 30, 2026

✨ Features

  • scaffold_module now defaults to a nested navigator layout matching Apple's convention; a single module PBXGroup (with path = <name>) contains Sources and Tests synchronized folders instead of two sibling root groups, with a new group_layout parameter (nested | sibling) for opt-out; also adds a move_group tool that reparents any PBXGroup or PBXFileSystemSynchronizedRootGroup under a different parent (optionally rewriting its path attribute), so navigator hierarchy fixups no longer require hand-editing pbxproj (#361)
  • Support PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet in xc-project; new add_synchronized_folder_phase_membership tool opts files from a synchronized root group into a target's Copy Files (or other) build phase, locating the phase by phase_namedst_path → the target's sole Copy Files phase, auto-creating the exception set when missing, and surgically inserting/extending the pbxproj section via a new PBXProjTextEditor.insertGroupBuildPhaseMembershipExceptionSetBlock helper; list_synchronized_folder_exceptions now also reports these phase-membership sets with phase name, dstPath, dstSubfolder, member files, and attributesByRelativePath instead of printing "Unknown exception set type" (#358)
  • Extend TestCrashDiagnostics to recover libdispatch / abort-class crash causes; trapSignatures and fatalLogPredicate now also match BUG IN CLIENT, Abort trap: 6, EXC_CRASH, SIGABRT, Swift runtime failure:, and AddressSanitizer / ThreadSanitizer / UndefinedBehaviorSanitizer errors, so a test_sim / test_macos run that aborts on a dispatch_assert_queue violation or sanitizer trip surfaces the cause instead of falling back to "no fatal-error message was recovered" (#355)
  • Surface test-crash cause from test_macos; when a test process dies with a bare signal trap rather than an assertion failure, a new TestCrashDiagnostics helper detects the crash, scrapes Swift Fatal error: / Precondition failed: / Objective-C exception lines from the captured test-host stderr, and queries the unified log (log show) scoped to the run's wall-clock window for fatal/exception messages, appending a Crash diagnosis section to the failure result (and pointing at show_mac_log when nothing is recovered) (#351)
  • Surface elapsed wall-clock time in MCP build tool results, so a multi-minute build reports how long it actually took instead of leaving the caller to guess (#302)
  • Add a skip_build parameter to build_debug_macos for no-rebuild relaunch; when set, the tool skips xcodebuild and goes straight to bundle preparation plus LLDB launch/attach, reusing the existing DYLD_FRAMEWORK_PATH / Launch Services activation so relaunching the same binary with new env/args is fast and reliable; errors clearly if no built product exists yet (#337)
  • Add a set_test_plan_skipped_tests tool that manages a .xctestplan's skippedTests exclusion list ("run everything EXCEPT these"), mirroring set_test_plan_skipped_tags but catching XCTest classes/methods that have no tags; takes tests (a class/suite name or Class/method()), an optional target_name (defaults to the plan's defaultOptions), and action add|remove, clearing the key when the last entry is removed; registered in both xc-project and the monolithic xc-mcp, and both set_test_plan_skipped_tags and the new tool were added to ServerToolDirectory so cross-server hints resolve (#354)
  • Add set_test_plan_target_parallelizable to write the per-target parallelizable boolean directly under each .xctestplan testTargets entry (or onto plan-level defaultOptions.parallelizable when target_name is omitted); the canonical mitigation for Swift Testing's default parallel execution tripping libdispatch's BUG IN CLIENT OF LIBDISPATCH: Block was expected to execute on queue [com.apple.main-thread] assertion in iOS app-hosted plans where a transitive XPC call (CloudKit, NSUbiquitousKeyValueStore, CoreSymbolication, …) dispatches a completion block to the main queue of a test host that isn't running a main runloop (#357)
  • Surface dyld load failures and guard against Team-ID signing mismatches; a new CodeSignInspector parses codesign -dvv Team IDs and flags ad-hoc/mismatched frameworks that dyld library validation will reject in a hardened-runtime app; build_debug_macos now appends the consistency warning plus parsed crash-report termination reasons on the launch-crash path, so a dyld __abort_with_payload shows the real "Library not loaded … different Team IDs" cause instead of a generic SIGABRT backtrace; AppBundlePreparer forces a disable-library-validation re-sign when an otherwise-unmodified skip_build bundle has a Team-ID mismatch; and debug_evaluate / debug_view_hierarchy now return a clear "process is running, interrupt it first" message instead of empty output on a running process (#345)

🐞 Fixes

  • debug_view_hierarchy bounded walks at max_depth > 12 produced no output file when the LLDB expression timed out; the in-target traversal expression opened /tmp/xcmcp-vh-<pid>.txt via fopen("w") (fully buffered) and streamed each node through fputs, so an expression --timeout abort discarded the stdio buffer before fclose; now disables _fp buffering via setvbuf(_fp, NULL, _IONBF, 0) immediately after fopen so each line reaches the kernel synchronously, leaving the partial walk on disk for inspection — making deep bounded walks usable on SwiftUI-heavy hierarchies (#369)

  • detect_unused_code (Periphery) aborted with "Cannot calculate full path for file element …" on .xcodeproj files containing self-referencing projectReferences; a new shared SelfProjectReference helper detects entries whose ProjectRef resolves to the containing project and strips the file reference, projectReferences entry, and empty Products group; wired into repair_project (honoring dry_run), validate_project (flags each self-reference as an error pointing to repair_project), and detect_unused_code (intercepts Periphery's raw error to name the offending self-reference and direct callers to repair_project) (#326)

  • add_synchronized_folder stored the folder argument verbatim into the PBXFileSystemSynchronizedRootGroup path when XcodeProj's fullPath(sourceRoot:) silently returned nil (parent reference not wired or non-.group sourceTree in the chain), doubling the on-disk path (e.g. under a Sync group with path = Sync, a folder at Sync/Sources got stored as path = Sync/Sources and resolved to <root>/Sync/Sync/Sources); the tool now walks the group hierarchy manually, accumulates path attributes of .group-sourceTree ancestors, and trims that prefix so the stored path is relative to its parent group — matching what Xcode emits via the IDE (#360)

  • MCP server stdio transport dies after request cancellation due to stale notifications/progress writes; once notifications/cancelled arrives the server must skip all responses, retire its progress reporters synchronously, ignore SIGPIPE process-wide, and rethrow CancellationError unchanged through the catch-all error wrapper so the SDK's cancellation arm fires; subprocesses now spawn in their own process group and are SIGKILLed as a group on cancel so SPM build-plugin grandchildren can't hold the pipes open (#300)

  • debug_lldb_command (e.g. thread backtrace) hangs when the process is stopped at a breakpoint; added a timeout so a stopped-process command can no longer wedge the debug session (#333)

  • build_debug_macos builds successfully but never launches the app; after xcodebuild prints ** BUILD SUCCEEDED **, grandchild daemons (SwiftPM resolver, build-system services) inherit and hold its stdout/stderr pipes open, so XcodebuildRunner's stream readers never finish and the 30s no-output watchdog fired XcodebuildError.stuckProcess, aborting the tool before the launch/attach step; the watchdog now recognizes a finished-build marker in the collected output and recovers it into a normal XcodebuildResult (exit code derived from the output) instead of erroring; also repaired the stale test-debug.sh harness to build the multicall xc-mcp product and invoke it via an xc-debug symlink (#331)

  • build_debug_macos hangs in LLDB attach/teardown; orphaned lldb-rpc-server wedges next launch; lldb spawns lldb-rpc-server as a child that survives a SIGKILL of lldb (reparents to launchd), and several teardown paths leaked it; LLDBSession.terminate() now captures lldb's direct child PIDs via pgrep -P before killing and SIGKILLs survivors; detach(pid:) treats a wedged-target timeout as partial success and always tears the session down; the launch path terminates the session on any error or cancellation mid-attach (#330)

  • add_synchronized_folder_exception targeted the wrong PBXFileSystemSynchronizedRootGroup when multiple synchronized folders shared the same leaf path (e.g. every module has a Sources folder), silently attaching the exception set to the first match instead of the one bound to the named target; SynchronizedFolderUtility now records each sync group's full project path and resolves by leaf, full path, or trailing-component suffix, narrows candidates to the target's fileSystemSynchronizedGroups, and errors on unresolved ambiguity; add_, remove_, and list_synchronized_folder_exception(s) all route through the resolver so full paths like Core/Sources disambiguate shared leaf names (#334)

  • WaitForProcessExitTests flaked in CI under parallel load; waitForProcessExit polls with Task.sleep on the Swift cooperative thread pool, which the full 1166-test parallel run starves via blocking calls in other suites, delaying the poll loop past the tests' tight detection budgets; gave the two exit-detection tests generous 15s timeouts and loosened the Timeout is bounded upper-bound assertion to 15s (it only needs to prove the call doesn't hang indefinitely); test-only change, no behavior change to waitForProcessExit (#336)

  • build_debug_macos cold-rebuilt on every call; a relative project_path (e.g. "Thesis.xcodeproj") was resolved against the server's live cwd lazily at build time, so cwd drift between calls hashed the same project to a different scoped DerivedData/<name>-<hash> root (sometimes a bogus jason-<hash>), defeating cache reuse; SessionManager now resolves project/workspace/package paths to a stable absolute path at input time in setDefaults, resolveBuildPaths, and resolvePackagePath (idempotent for absolute paths, normalizing any legacy relative values), so the same logical project always hashes to the same scoped root (#338)

  • Debug tools hung indefinitely when stopped at a conditional breakpoint on a high-frequency symbol; a breakpoint on a hot symbol (sqlite3_prepare_v2) with an inferior-function-calling condition floods LLDB's PTY faster than a (lldb) prompt ever returns, so the reader thread spun a CPU core and starved the cooperative pool, leaving the 30s timeout Task unscheduled (a >1h wedge); LLDBRunner.readUntilPrompt now stops the reader the instant any path resolves the continuation and enforces a 1 MB output cap that aborts with a structured error and poisons the session, bounding every command that routes through it (debug_process_status, debug_stack, debug_threads, debug_lldb_command); a new BreakpointConditionAdvisor warns debug_breakpoint_add / debug_lldb_command when a breakpoint targets a hot symbol or its condition calls inferior functions (strncmp, strstr); and a new debug_capture_backtrace tool sets an auto-continuing breakpoint that captures stacks non-interactively (bounded by timeout and the output cap) as the safe alternative to hand-rolling a conditional breakpoint on a hot symbol (#339)

  • Flaky high-frequency symbol advisor warning due to Set iteration order; BreakpointConditionAdvisor.matchedHighFrequencySymbol matched a breakpoint target against the highFrequencySymbols Set with first(where: exact || contains), so sqlite3_prepare_v2 could resolve to the substring entry sqlite3_prepare instead, depending on Swift's per-process Set hash seed; the matcher now prefers an exact match and only falls back to the longest contained symbol, making the result deterministic regardless of hash seed (#341)

  • Debugger (xc-debug) breakpoints vanished, the run/stop state desynced, and self wouldn't resolve at a breakpoint on macOS apps; after continue (sent via sendCommandNoWait) a breakpoint hit emitted an async stop notification that nothing drained, so the tracked state stayed .running and requireStopped wrongly rejected debug_stack / debug_variables / debug_evaluate — the resulting timeout poisoned and recreated the session, which is what made breakpoints disappear and compound commands hang; LLDBSession.syncedProcessState() now drains the PTY when state is .running so a process parked at a breakpoint is reported stopped, debug_evaluate gained optional thread / frame params so self/locals resolve against the breakpoint frame instead of a run-loop frame in mach_msg2_trap, and attach() now detects already being debugged and reports the target is under another debugger (and did not exit) rather than relaying LLDB's misleading exited with status = -1 (#342)

  • build_debug_macos mergeable-library debug builds crashed at launch with a dyld Symbol missing; with MERGEABLE_LIBRARY=YES Xcode embeds a thin reexport stub (~51 KB, no exported symbols) into Contents/Frameworks that shadows the full framework (~20 MB), so the app's LC_REEXPORT_DYLIB @rpath/… lookups resolved against the stub and dyld reported the symbol missing self-referentially; AppBundlePreparer now detects such stubs by binary-size delta and replaces them with a symlink to the full framework from BUILT_PRODUCTS_DIR, and also symlinks SPM package-product frameworks from the PackageFrameworks/ subdirectory (a second Library not loaded failure that surfaced once the stub was fixed) before re-signing the bundle (#344)

  • start_mac_log_cap / show_mac_log couldn't target a debug build whose process name contains spaces and parentheses (e.g. ThesisApp (debug) from build_debug_macos); the strict process_name validator rejected it outright and bundle_id matching fell back to the last dot-component (com.thesisapp.debugdebug), silently capturing zero lines; added PredicateFilterValidator.validateStringLiteral (permits spaces/parens, rejects only empty/newline/control characters) and escapeStringLiteral (escapes \ then " so a value stays a single quoted predicate literal and quote-injection is neutralized rather than rejected), and routed process_name through them in both tools (#335)

  • debug_view_hierarchy still timed out and poisoned the LLDB session on a running macOS app (a pcm-bwn regression); the interrupt→eval→resume path landed but never bounded the evaluation itself, so an AppKit call like _subtreeDescription could block past the 30s read-level timeout, which then poisoned the whole session and cascaded into every later debug call; tool-built objc expressions now carry LLDB's own --timeout (15s, plus --unwind-on-error / --ignore-breakpoints) via a shared objcExprCommand builder, so LLDB self-aborts a hung inferior call and returns a clean prompt well before the read timeout fires — a slow dump now fails the single call instead of wedging the session; routed viewHierarchy, toggleViewBorders, and evaluate through the bounded builder (#348)

  • test_sim appeared to hang on long cold iOS rebuilds and had to be cancelled manually; XcodebuildRunner.runProcess spawned xcodebuild without its own process group and, on timeout/stuck, only sent SIGTERM to the parent xcodebuild, which ignores it and leaves swift-frontend / build-system grandchildren running; those grandchildren hold the stdout/stderr pipes open, so the stream readers never see EOF, the watchdog's timeout throw can't propagate, and the call hangs (a cold build's steady progress output also keeps the output_timeout silence window from elapsing, leaving the wall-clock timeout as the only backstop — and that backstop was the one that wedged); xcodebuild now runs as a process-group leader and the watchdog SIGKILLs the whole group on timeout, stuck-detection, and external cancellation, and ProcessResult.runSubprocess closed the same latent gap so its timeout path SIGKILLs the group synchronously instead of relying on Subprocess's parent-only SIGTERM teardown (#350)

  • Runtime inspection returned empty output or timed out on a running macOS app; debug_view_hierarchy, debug_evaluate, and raw expr/po via debug_lldb_command all need a stopped process, but a running target silently yields empty output or blocks until the 30s command timeout; a new LLDBRunner.withProcessStopped helper transparently interrupts a running process, evaluates, then resumes it (resuming even on failure so a transient error can't freeze the app), while a process already stopped at a breakpoint is left untouched; debug_evaluate and debug_view_hierarchy route through it (appending an auto-resume note when they paused the app), debug_lldb_command detects expression-eval commands (expr/po/p/print/call) and does the same, and the macOS view-hierarchy dump now falls back mainWindowkeyWindow → first window instead of returning nil for a backgrounded app (#346)

  • set_test_plan_skipped_tags dropped "mode": "or" when adding to an existing per-target skippedTags block, so the block silently fell back to AND semantics and the per-target skip list no-opped (no real test carries every listed tag at once); the per-target write path now preserves an existing mode and defaults to "or" when none is set — matching plan-level defaultOptions and Xcode's own authoring behavior; the prior fix had inverted the rule and a test asserted mode == nil, locking the bug in, so the assertion was corrected and a regression test exercising the exact repro was added (#356)

  • test_sim / test_macos / test_device streamed no progress, so a long cold rebuild was indistinguishable from a hang and clients cancelled it manually; XcodebuildRunner.test(...) hardcoded onProgress: nil, dropping every output line the stream readers already received; threaded an optional onProgress callback through XcodebuildRunner.test, TestToolHelper.runAndFormat, and the three test tools' execute, and wired each test handler in the monolithic xc-mcp plus the xc-simulator / xc-build / xc-device servers to wrap the call in a ProgressReporter when the client passes a progressToken (reusing the cancellation/retirement semantics from swift_package_test), so a cold test run now streams its latest build/test line every ~2s (#353)

  • interact_* tools were unreachable from focused-server clients; the eight macOS Accessibility tools (interact_click, interact_set_value, interact_key, interact_focus, interact_menu, interact_get_value, interact_find, interact_ui_tree) were registered only on the monolithic xc-mcp binary, so any client wired to xc-debug / xc-build / etc. saw screenshot_mac_window but no input path — forcing agents into an osascript fallback that's blocked by the accessibility-permission gate; surfaced the eight tools through xc-debug (alongside screenshot_mac_window and the LLDB view tools, since they share the build → launch → drive → screenshot loop), and added them to ServerToolDirectory so cross-server hints from other focused servers point at xc-debug (#359)

  • build_sim / build_run_sim likewise streamed no progress on a cold build; their tools took no onProgress and the server handlers called them bare, so the build phase looked hung; BuildSimTool.execute / BuildRunSimTool.execute now accept an optional onProgress and forward it to xcodebuildRunner.build(...), and the build_sim / build_run_sim handlers in xc-mcp and xc-simulator wrap the call in a ProgressReporter when a progressToken is supplied (#352)

  • debug_view_hierarchy's timeout argument only raised the read-side timeout while the embedded expr --timeout stayed pinned at 15s, so a timeout: 120 call against a SwiftUI-heavy hierarchy still timed out at 15s and left the target SIGSTOP'd; ArgumentExtraction.getDouble now accepts JSON integer values in addition to .double (the client sends 120 not 120.0, so the integer was discarded and the embedded --timeout fell back to the hardcoded default), and LLDBRunner.withProcessStopped now sends SIGCONT unconditionally on the error path in addition to the queued continue (the fire-and-forget poisoning Task races with the catch block, so sendCommandNoWait could succeed without actually resuming the inferior) (#363)

  • debug_view_hierarchy bounded walk still hung against macOS SwiftUI hierarchies even after the eka-s03 / h0c-60y timeout-propagation fix; recent LLDBs silently promote expr -l objc to Objective-C++ ("Expression evaluation in pure Objective-C not supported"), and in that mode variadic ObjC selectors like appendFormat: won't parse beyond a single argument without Foundation in scope, so the bounded-traversal expression was failing at compile time and the "hang" was xc-debug waiting for a prompt that never came; the macOS expression now prepends @import Darwin; @import Foundation; @import AppKit; (Darwin brings <stdio.h>), and a parallel file-streaming variant emits per-node lines through libc fputs into a host-readable /tmp/xcmcp-vh-<pid>.txt so the dump never has to travel back across the LLDB IPC; the same expression is ~2 KB, which silently stalls when stuffed through the PTY in raw mode, so the tool now writes the expr command to a temp .lldb script and runs command source whenever the command exceeds 512 bytes, keeping only a short line on the PTY (#364)

  • debug_view_hierarchy / debug_evaluate failed with session is poisoned by a previous timeout on the very first call after a fresh build_debug_macos launch; LLDBSession.readUntilPrompt marked the session poisoned on every failure path (timeout, >1 MB flood), but drainPendingOutput, checkForEarlyCrash, and interruptProcess's stop poll all wrapped the read in try? to advertise tolerated failure, so a speculative drain that wedged on unterminated PTY data silently invalidated the freshly-launched session before the caller ever touched it; readUntilPrompt now takes timeout: / poisonOnFailure: parameters and the three tolerant call sites pass poisonOnFailure: false with a 2 s bound; markPoisoned now takes a reason: and logs at warning level on the first transition so a still-unknown upstream trigger (hot inferior call, async stop without a trailing prompt) surfaces in stderr instead of being swallowed (#366)

  • debug_evaluate hung after the auto-interrupt step when the inferior's main thread was parked in an uninterruptable syscall; LLDBSession.interruptProcess silently fell back to "assume stopped" when the async stop notification didn't arrive in 5 s, so the follow-up expr was dispatched against a still-running target and wedged LLDB's expression evaluator with no (lldb) prompt produced; added an opt-in requireExplicitStop parameter that cross-checks via process status (accepting a stopped state even when the notification is delayed) and throws a structured error otherwise, a non-poisoning sendCommandSpeculative helper, and a JIT/runtime warmup (expression -- (int)0) after auto-interrupt to prime ObjC bridge resolution before the user's AppKit-touching call; withProcessStopped opts into both; the partial-output timeout in readUntilPrompt now identifies an echoed-expr-without-prompt as an expression-evaluator hang and points at TCC syscall wedge / JIT bridge resolution; added a self-contained lul-evz-fixture to test-debug.sh (busy SwiftUI app + main thread parked in Thread.sleep__semwait_signal) that reproduces the failure without depending on an external project, and the harness EXIT trap now kills the inferior PID before tearing down the server so evaluate mode no longer leaves stray windows on the desktop (#368)

  • debug_evaluate / debug_view_hierarchy still timed out with "no output received" on the very first call after a fresh build_debug_macos launch (a regression-shaped repeat of rgu-xhg); LLDBSession.readUntilPrompt's GCD reader called blocking FileHandle.availableData and the timeout path resumed the continuation without waiting for the reader to exit, so a tolerated 2 s drainPendingOutput / checkForEarlyCrash read left the reader thread blocked on the PTY past readUntilPrompt's return; the next command's reader then raced the leaked reader for response bytes on the shared fd, and the leaked reader would consume them and discard them on its stale finished flag, leaving the next caller blocked until its 30 s window expired and SIGSTOP'd the inferior; the reader now uses poll() + non-blocking read() with a 50 ms tick so it can notice finished promptly, and the timeout path waits up to 500 ms for the reader's readerDone flag before resuming the continuation, guaranteeing no leaked reader survives past readUntilPrompt's return; added a LLDBCommandTimeoutTests regression test that exercises the tolerated-timeout-then-next-command flow, plus a self-contained evaluate mode in test-debug.sh (with a t57-fixture scaffold that builds a tiny SwiftUI app under .build/, so the repro doesn't depend on any external project) (#367)

  • debug_view_hierarchy poisoned the LLDB session on macOS apps with SwiftUI hosting views; _subtreeDescription on an NSHostingView tree could overrun the 15s expression timeout or hit the 1 MB flood guard, wedging every subsequent debug call until the app was relaunched; added max_depth, class_filter, and timeout parameters that route through a new bounded stack-based NSView/UIView traversal (one line per node with class, address, and frame, capped at 20 000 nodes) so SwiftUI-heavy hierarchies finish well under the expression budget; LLDBSession.objcExprCommand now takes an optional per-call timeoutMicroseconds, the per-command read timeout is raised to match and restored after the dump, and withProcessStopped falls back to SIGCONT when the post-body continue can't reach a session that was poisoned mid-body, so the user's app no longer stays SIGSTOP'd after a timeout (#362)

🗜️ Tweaks

  • Apply medium-priority /swift review fixes in Sources/Core; extended SimctlRunner.setStatusBar with cellularMode / wifiMode parameters and deleted the weakly-typed overrideStatusBar(udid:options: [String: Any]) variant so SimStatusBarTool calls through named arguments instead of a [String: Any] dictionary; modernized SessionManager's warmup launch from Task.detached to Task.immediateDetached so the background warmup starts synchronously off the actor without a scheduler hop (same detached .background semantics); also fixed an empty switch case in PredicateFilterValidator that sm format had introduced by stripping the trailing continue (#365)
  • macOS interact_* tools now settle the accessibility tree after a mutating action and return the refreshed UI tree inline; interact_click, interact_set_value, interact_focus, interact_menu, and interact_key poll via InteractRunner.settledUITree until two consecutive structural snapshots match (or an 800ms timeout), refresh the InteractSessionManager element cache, and append the settled tree so the next step gets stable element IDs without a separate interact_ui_tree call; interact_menu / interact_key became async and interact_key gained optional app-resolution args (only snapshots when a target app is named, since CGEvents post globally); simulator UI automation tools are unchanged (no accessibility tree source, coordinate input doesn't go stale); ported from XcodeBuildMCP #427 (#343)

Week of May 17 – May 23, 2026

✨ Features

  • xcstrings: add xcstrings_check_untranslated tool with state-aware detection; existing xcstrings_list_untranslated only checked presence and silently missed empty values, needs_review state, and partial plural/device variation coverage; new tool returns structured UntranslatedIssue rows with a reason enum (missing_localization, empty_value, state_not_translated, missing_variation_string_unit, empty_variation_value, etc.) across one file × N languages; ported from Ryu0118/xcstrings-crud PR #33 (#323)
  • Add a shared NextStepHints helper in Sources/Core/NextStepHints.swift that renders a sorted Next steps: block of suggested follow-up tool calls in MCP's tool({ key: "value", ... }) syntax; wired into debug_attach_sim (suggests debug_breakpoint_add / debug_continue / debug_stack keyed by the resolved pid), get_coverage_report (suggests get_file_coverage targeting the weakest-covered file), and get_file_coverage (suggests get_coverage_report); uses JSONEncoder with .withoutEscapingSlashes so file paths render cleanly; ported from XcodeBuildMCP PR #420 (#325)
  • Stream build_debug_macos build output to the MCP client via notifications/progress; the build's progress lines now flow through a ProgressReporter (reusing the cancellation/retirement semantics already used by swift_package_build / swift_package_test) when the client passes a progressToken, so a multi-minute cold build is no longer indistinguishable from a hang (#328)

🐞 Fixes

  • Speed up build_debug_macos launch by narrowing the pre-build -showBuildSettings pass; the macOS destination (platform=macOS[,arch=…]) is now passed to showBuildSettings so xcodebuild resolves only the macOS platform's settings instead of the entire SPM package graph for every target; falls back to a destination-less pass when an iOS-only scheme yields no settings so the friendly "scheme does not support macOS" error is preserved; also documents the XC_MCP_DISABLE_DERIVED_DATA_SCOPING=1 escape hatch in the tool description for the single-agent "just built in Xcode, now run under LLDB" workflow (#329)
  • Detect and remove self-referencing sub-project entries that block Periphery scans; a projectReferences entry whose ProjectRef points at the containing .xcodeproj (a project nested inside itself) makes detect_unused_code exit with Cannot calculate full path for file element; new shared SelfProjectReference helper finds and strips the file reference, the projectReferences entry, and the empty Products group; repair_project removes them (honoring dry_run), validate_project flags each as an [error] pointing to repair_project, and detect_unused_code now intercepts Periphery's raw error to name the offending self-reference and direct the caller to repair_project (#327)

🗜️ Tweaks

  • detect_unused_code: add passthrough retain_equatable_properties / retain_hashable_properties boolean params for periphery's new EquatableHashablePropertyRetainer mutator; suppresses false positives on stored properties of value types (struct/enum) where compiler-synthesized == / hash(into:) don't emit index references; defaults to false (matching periphery defaults); requires periphery with PR #1126 (post-3.7.4) (#324)

Week of May 10 – May 16, 2026

🐞 Fixes

  • Validate bundle_id, subsystem, and process_name against a strict allowlist (A-Z, a-z, 0-9, ., -, _) before interpolating into NSPredicate strings passed to log stream / log show; covers start_sim_log_cap, start_mac_log_cap, show_mac_log, and launch_app_logs_sim; prevents predicate injection (CWE-78) where a bundle_id containing " could leak logs from unrelated subsystems or break the predicate parser; the explicit predicate parameter remains as the escape hatch for callers that need raw NSPredicate syntax; ported from XcodeBuildMCP PR #407 (#322)
  • Audit debug_attach_sim for bundle_id vs pid mutual-exclusion bug from session defaults; ported from XcodeBuildMCP PR #411; confirmed not applicable to xc-mcp (SessionManager doesn't store bundle_id as a default and DebugAttachSimTool has no mutual-exclusion validation that could falsely reject); added DebugAttachSimToolTests with a Mirror-based contract test that will fail if a future change adds a bundleId-like session field (#321)

Week of May 3 – May 9, 2026

✨ Features

  • Auto-create workspace-scoped .xcresult bundles for test tools; auto bundles now live under ~/Library/Caches/xc-mcp/TestResults/<Project>-<hash>/<UUID>.xcresult and persist across runs (opportunistic 7-day prune); replaces the previous $TMPDIR + immediate-defer-delete behavior so callers can open the bundle in Xcode or feed it to coverage / attachment tools; opt-out via XC_MCP_DISABLE_TEST_RESULTS_SCOPING; ported from XcodeBuildMCP PR #401 (#312)
  • Surface the .xcresult bundle path in test tool output; formatTestToolResult now appends Result bundle: <path> to both the success text and the failure error message when the bundle exists on disk; ported from XcodeBuildMCP PR #397 (#313)
  • xcstrings: surface NFKC-equivalent key suggestions on lookup failure; XCStringsError.keyNotFound now carries suggestions: [String] and xcstrings_check_key returns false (key not found; did you mean: '…'?) so APOSTROPHE U+0027 vs RIGHT SINGLE QUOTATION MARK U+2019 substitutions are no longer silent; uses NFKC plus an explicit curly-quote fold since pure NFKC does not unify those codepoints; ported from Ryu0118/xcstrings-crud PR #29 (#315)

🐞 Fixes

  • SwiftSymbolsTool: cache decoded SymbolGraph in a private actor keyed by (module, platform, sdkPath, triple); three parallel SwiftSymbolsToolTests were racing three concurrent swift-symbolgraph-extract invocations on the GitHub Actions runner and all blew past the tool's 60s subprocess timeout; with cache + inflight Task dedup, only one extraction runs per key and concurrent callers await the same Task; also benefits production sessions that query the same module twice (#310)
  • SessionManagerWarmupTests: replace fixed sleep(500ms) with a poll-for-.completed loop (5s cap) plus a 100ms grace window; fixes a CI flake where the warmup ran at .background priority and was deferred under runner starvation, leaving runCount == 0 when the assertion fired; production dedup needs no change since SessionManager is a public actor and triggerWarmupIfNeeded is already atomic (#311)
  • xcstrings: respect shouldTranslate: false entries; non-translatable keys no longer count against listUntranslated, checkCoverage, or getStats coverage totals; ported from Ryu0118/xcstrings-crud@9803d77 (#308)
  • xcstrings: preserve isCommentAutoGenerated metadata across save/load; previously dropped on write, causing Xcode to re-evaluate auto comments (#307)
  • xcstrings: sort keys with localizedStandardCompare and write in Xcode's on-disk format ("key" : value, sorted nested keys, top-level sourceLanguage / strings / version order) so round-trip diffs against Xcode-saved catalogs are clean (#306)
  • SwiftSymbolsTool: bump swift-symbolgraph-extract subprocess timeout 60s180s and raise the three SwiftSymbolsToolTests integration tests' .timeLimit 2min5min; the SymbolGraphCache dedup landed in #310 was working (single shared inflight task) but cold-cache extraction of the Testing module on the GitHub Actions runner still exceeded 60s (#314)
  • xcstrings: stop escaping forward slashes in encoder output; XCStringsFileEncoder now configures JSONEncoder with .withoutEscapingSlashes so catalogs containing strings like "Domestic / Foreign" round-trip without \/ noise; ported from Ryu0118/xcstrings-crud PR #30 (#316)
  • clean(derived_data: true) now wipes xc-mcp's scoped DerivedData (the path actually passed to -derivedDataPath) in addition to matching entries in Xcode's standard DerivedData; previously it cleared only ~/Library/Developer/Xcode/DerivedData/ while builds kept reading stale macro-expansion artifacts from ~/Library/Caches/xc-mcp/DerivedData/<Project>-<hash>/ (#320)

🗜️ Tweaks

  • Migrate swift_lint / swift_format MCP tools and the swift_diagnostics / diagnostics lint step from swiftlint / swiftformat (Lockwood) to sm (swiftiomatic); consume the new sm lint --reporter json and sm format --reporter json envelopes so xc-mcp parses structured {file, line, column, severity, rule, message} violations and {changed, unchanged, skipped} format summaries instead of regex-matching plain stdout; tool names swift_lint / swift_format kept stable, diagnostics param renamed include_lintrun_lint; drops Lockwood-vs-Apple discrimination from BinaryLocator and removes legacy .swiftlint.yml / .swiftformat discovery (sm finds its own configuration) (#318)
  • Bump tuist/xcodeproj 9.10.19.12.0; pulls in additive XCBuildConfiguration support for xcconfigs inside PBXFileSystemSynchronizedRootGroup (Xcode 16+); audited XcodeBuildMCP PR #390 shell-injection sites and confirmed xc-mcp reads Info.plist via in-process PropertyListSerialization so the analogous surface doesn't exist here (#309)
  • Audit launch-arg vs xcodebuild extra-args separation against XcodeBuildMCP PR #403; confirmed our args parameter is consistently routed to launch-time use across all 7 build/run/launch tools and xcodebuild additional arguments come from narrowly-scoped session helpers; no conflation, no breaking change needed (#317)

Week of Apr 26 – May 2, 2026

✨ Features

  • Auto-scope xcodebuild -derivedDataPath to ~/Library/Caches/xc-mcp/DerivedData/<Project>-<hash>; prevents concurrent build collisions when multiple xc-mcp sessions target the same clone; opt-out via XC_MCP_DISABLE_DERIVED_DATA_SCOPING (#289)
  • Add toggle_software_keyboard / toggle_hardware_keyboard simulator tools; toggle the simulator's on-screen keyboard via Cmd+K / Cmd+Shift+K through AppleScript (#293)
  • Surface slowest passing tests in test output via XC_MCP_SHOW_TEST_TIMING; previously hidden when total > 50 with failures (#291)
  • Pre-warm SwiftPM cache on set_session_defaults; spawn a background swift build --build-tests when the cache is cold; auto-cancel when the user runs swift_package_*; opt-out via XC_MCP_DISABLE_WARMUP; status visible in show_session_defaults (#296)
  • Stream swift_package_build and swift_package_test progress to MCP clients via notifications/progress; throttled last-line snapshots so long swift-syntax compiles no longer look like hangs; activated when the client passes a progressToken (#298)

🐞 Fixes

  • Expand leading ~ in user-supplied paths; set_session_defaults and per-call project_path / workspace_path / package_path arguments now resolve ~/Developer/foo.xcodeproj correctly (#292)
  • Prevent MCP server disconnect when cancelling a long-running build/test; spawn child processes in their own process group and SIGKILL the whole group on cancel so SPM build plugins and grandchildren release the stdout/stderr pipes (#294)
  • swift_package_test / swift_package_build no longer abort after 5min on a cold cache; auto-extend timeout to 15min when .build/checkouts is empty; timeout errors now include the package path and a cold-cache hint (#295)
  • Stop MCP stdio transport from dying on user-cancel during long builds; ProgressReporter now retires synchronously via withTaskCancellationHandler so the unstructured poller can no longer emit a stale notifications/progress after the request is cancelled (#300)
  • Keep the xc-swift (and every focused) MCP server alive when the client half-closes the stdio pipe after a user-cancel; install signal(SIGPIPE, SIG_IGN) in the multicall entry point so a stale write returns EPIPE instead of terminating the process; also cancel ProgressReporter's poll task synchronously from onCancel (#303)
  • Preserve full multi-line Comment(rawValue:) bodies in swift_package_test failures; the parser now keeps consuming bare indented continuation lines after the first 􀄵 / detail marker so assertStringsEqualWithDiff-style diffs reach the agent intact (#304)
  • Stop xc-swift MCP server from disconnecting after a user-cancelled swift_package_build / swift_package_test; Swift.Error.asMCPError() now throws and rethrows CancellationError unchanged so the SDK skips the response per the MCP cancellation spec, instead of emitting an MCPError.internalError that Claude Code treated as a protocol violation and tore the stdio pipe down for (#305)

🗜️ Tweaks

  • Review XcodeBuildMCP commits for features to incorporate (#290)
  • Investigate -experimental-skip-non-inlinable-function-bodies for swift-syntax compile speedups; benchmarked at 2% on swiftiomatic; not adopted by default; added XC_MCP_SWIFT_EXTRA_ARGS env var for opt-in experimentation (#299)

Week of Apr 19 – Apr 25, 2026

✨ Features

  • Surface verbose compiler output on signal crashes; swift_package_build and swift_diagnostics auto-retry with -v to identify the crashing file and compiler backtrace (#283)
  • Add remove_run_script_phase tool; remove PBXShellScriptBuildPhase build phases from a target by name; refuses to remove ambiguous matches; cleans up orphaned build files (#284)

🐞 Fixes

  • add_package_product: detect SPM plugin products from local Package.swift and skip the Frameworks build phase; add kind parameter (auto | library | plugin) for explicit override (#287)
  • add_package_product: wire the package field on XCSwiftPackageProductDependency for products not yet linked to any target; add package_url / package_path parameters; discover owning package via local Package.swift checkouts (#288)

🗜️ Tweaks

  • Add swiftiomatic-plugins build-tool plugin for self-hosted lint-on-build; apply to XCMCPCore, XCMCPTools, and the xc-mcp executable (#285)

Week of Apr 12 – Apr 18, 2026

✨ Features

  • Add 9 Icon Composer tools (create_icon, export_icon, read_icon, add_icon_layer, remove_icon_layer, set_icon_fill, set_icon_effects, set_icon_layer_position, set_icon_appearances); full .icon bundle creation and editing with IconManifest Codable model (#278)
  • Handle Xcode 26 objectVersion 100 project format; update default from 56 to 77 with configurable object_version parameter; add post-migration validation checks to validate_project; add repair_project tool for fixing null build files and orphaned entries (#282)

🐞 Fixes

  • Fix screenshot_mac_window hanging for 20+ seconds; replace ScreenCaptureKit with CGWindowListCopyWindowInfo + screencapture -l (#275)
  • Fix PreviewCaptureTool captureMacOSWindow same ScreenCaptureKit hang; extract shared WindowCapture helper to Core (#274)
  • Fix add_file missing lastKnownFileType for .xcassets; fix scaffold tools not wiring source files or asset catalog into Xcode project build phases; fix AppIcon Contents.json missing scale field (#276)
  • Fix add_file silently dropping PBXBuildFile when build phase files is nil; affects resources, sources, and headers phases in real Xcode projects (#277)
  • Fix add_file missing lastKnownFileType for .icon files; fix path resolution for files above .xcodeproj but within repo root (#278)
  • Fix scaffold AppIcon.appiconset/Contents.json using invalid "platform": "macos" value; actool silently skips the icon (#279)

🗜️ Tweaks

  • Bump XcodeProj to 9.11.0; add debug_as_which_user parameter to create_scheme for debugAsWhichUser on LaunchAction
  • export_icon now returns inline base64 PNG image so the LLM client can see the rendered icon

Week of Apr 5 – Apr 11, 2026

✨ Features

  • Add ReportCrash throttle detection to search_crash_reports; warn when macOS stops generating .ips files after ~25 reports per process and list other processes with reports in the time window (#259)
  • Add memory diagnostic tools (memory_leaks, memory_heap, memory_vmmap, memory_stringdups, memory_malloc_history); wrap Xcode's hidden CLI tools for process memory analysis (#265)
  • Add symbolicate_address tool; batch crash address symbolication via atos (#266)
  • Add version_management tool; read/set marketing version and build numbers via agvtool (#262)
  • Add notarize tool; full macOS notarization workflow via notarytool and stapler (#263)
  • Add validate_asset_catalog tool; pre-build .xcassets validation via actool (#264)
  • Add open_in_xcode tool; open files at specific lines, projects, or workspaces via xed (#267)
  • Add 6 build diagnostics tools (check_output_file_map, extract_crash_traces, list_build_phase_status, read_serialized_diagnostics, diff_build_settings, show_build_dependency_graph); debug silent compilation failures by inspecting OutputFileMap entries, compiler crash traces, build phase status, .dia files, build setting diffs, and dependency graphs (#268)

🐞 Fixes

  • Fix swift_package_build crashing on large projects; stream subprocess output with tail-truncation instead of throwing on overflow; reduce default output limit from 10MB to 2MB (#272)
  • Fix multi-suite test count bug in BuildOutputParser; accumulate XCTest bundle counts, Swift Testing run counts, and parallel scheduling totals instead of overwriting (#261)
  • Fix add_framework silently ignoring embed: true for developer frameworks (XcodeKit, XCTest); fix add_to_copy_files_phase not auto-defaulting CodeSignOnCopy/RemoveHeadersOnCopy for phases with dstSubfolderSpec == .frameworks (#270)
  • Fix add_app_extension using wrong product type for Xcode extensions; add source_editor extension type mapping to com.apple.product-type.xcode-extension; add .xcodeExtension to remove_app_extension valid types (#271)
  • Fix swift_package_test truncating failure messages; capture all continuation lines instead of only the first; preserve parameterized test argument values (→ value) in test name (#273)

🗜️ Tweaks

  • Fix BuildOutputParser Swift Testing edge cases; handle (aka '...') verbose suffix, with N test cases parameterized results, and recorded an issue with N argument values parameterized issues; add 7 golden-file snapshot tests for BuildResultFormatter (#269)

Week of Mar 29 – Apr 4, 2026

✨ Features

  • Add remove_package_product and list_package_products tools; remove individual SPM product dependencies from targets and inspect per-target product linkage (#247)
  • add_target_to_test_plan: support selectedTests filtering with xctest_classes and suites parameters; restrict test plan entries to specific classes, methods, or Swift Testing functions (#252)
  • move_file now updates synchronized folder exception sets; renaming a file that appears in membershipExceptions no longer requires manual remove/add workaround (#254)

🐞 Fixes

  • Fix remove_package_product corrupting pbxproj; clean up PBXTargetDependency entries with productRef created by Xcode GUI (#248)
  • Fix add_framework creating bogus .framework file reference for static libraries (.a); reuse existing product reference or create proper archive.ar entry in BUILT_PRODUCTS_DIR (#250)
  • Fix test_macos only_testing filter failing for Swift Testing backtick-escaped single-word method names; auto-wrap Swift keywords like class and import in backticks (#251)
  • Fix build_macos suppressing all compiler warnings; add show_warnings parameter to list project-local warnings on successful builds (#238)
  • Fix synchronized folder exception tools corrupting pbxproj; use text-based PBXProjTextEditor instead of XcodeProj round-trip serializer (#256)
  • Fix add_framework silently failing to add local product frameworks; detect existing BUILT_PRODUCTS_DIR references before classifying bare names as system frameworks
  • Fix add_framework creating stale PBXFileReference for cross-project framework products; reuse existing PBXReferenceProxy entries instead of creating unresolvable group-relative references

🗜️ Tweaks

  • Remove unnecessary @unchecked Sendable from BuildOutputParser; replace [String: Any] JSON parsing with Decodable models in XCResultParser and CrashReportParser; fix 21 swiftlint warnings

Week of Mar 22 – Mar 28, 2026

✨ Features

  • Support project-level build settings in set_build_setting; omit target_name to apply settings at the project level instead of a specific target
  • Add build_settings parameter to all build/test tools; pass xcodebuild build setting overrides (e.g. SWIFT_ENABLE_EXPLICIT_MODULES=NO) as highest-precedence positional arguments (#243)
  • Add MCP tool annotations to all 197 tools; explicit readOnlyHint/destructiveHint/openWorldHint so clients can auto-approve safe operations (#244)
  • Add timeout parameter to build_macos and build_run_macos tools; return partial diagnostics on timeout instead of an empty error (#245)

🐞 Fixes

  • Fix add_swift_package crashing with SIGTRAP on projects with sub-project references; work around XcodeProj sortProjectReferences force-unwrap of nil PBXFileElement.name by backfilling from path
  • Fix remove_target leaving orphaned PBXContainerItemProxy and PBXTargetDependency entries in pbxproj; also search all target types instead of only native targets (#237)
  • Fix PluralVariation and DeviceVariation field types to match xcstrings JSON format; add VariationValue wrapper for correct decoding of plural/device variations (#239)
  • Fix device log capture requiring sudo; switch from log collect --device-udid to log stream background process (#233)
  • Fix list_files misleading membershipExceptions label; fix remove_synchronized_folder_exception not finding auto-created exception sets; add add_package_product tool for linking existing SPM products to targets (#227)
  • Fix start_device_log_cap unable to capture device logs; switch to idevicesyslog from libimobiledevice with CoreDevice-to-hardware UDID resolution (#235)
  • Fix stop_device_log_cap failing to collect logs from physical devices; correct log collect --start date format from ISO8601 to yyyy-MM-dd HH:mm:ss (#232)
  • Fix remove_swift_package leaving stale PBXBuildFile and PBXSwiftPackageProductDependency entries in pbxproj (#234)

🗜️ Tweaks

  • Extract TestToolHelper to deduplicate test tool validation/bundle/formatting logic across TestSimTool, TestMacOSTool, TestDeviceTool; replace [String: Any] with Decodable types in DeviceCtlRunner; fix lint warnings in BuildResultFormatter, StartDeviceLogCapTool, ScaffoldModuleTool
  • Upgrade to Swift 6.3, macOS 26, MCP SDK 0.12.0, swift-subprocess 0.4.0; migrate TerminationStatus.unhandledException to .signaled, .combineWithOutput to .combinedWithOutput, .text pattern matching to 3-element tuple
  • Swift review fixes from 2026-03-21 changes (#236)

Week of Mar 15 – Mar 21, 2026

✨ Features

  • Add show_mac_log tool to query historical macOS unified logs via log show; filter by bundle ID, process name, subsystem, or custom predicate with configurable time range and tail lines (#216)
  • Add swift_symbols tool to extract and query public APIs of Swift modules via swift-symbolgraph-extract; filter by name, symbol kind, and platform (#217)
  • Investigate dead code detection tooling; evaluated Periphery alternatives and confirmed it as the best option (#171)
  • Share session defaults across all focused MCP servers; set_session_defaults in one server applies everywhere (#208)
  • Add get_performance_metrics, set_performance_baseline, and show_performance_baselines tools; extract measure(metrics:) results from xcresult bundles and create/update .xcbaseline plists for automatic regression detection (#205)
  • Add set_test_plan_skipped_tags tool; add or remove skippedTags at plan-level or per-target in .xctestplan files (#225)
  • build_macos: truncate cascade errors when root cause is a PhaseScriptExecution failure; collapse "Unable to find module dependency" noise into a single summary line (#230)
  • BuildGuard: wait for build lock with 5-minute timeout instead of failing immediately; concurrent agents now queue instead of erroring (#231)

🐞 Fixes

  • Fix build_run_sim false "build appears stuck" error; increase no-output timeout from 30s to 120s for simulator builds where linking/signing produces long output gaps; auto-boot shutdown simulators before install/launch (#223, #222)
  • Fix build_device failing to find connected device by UDID; improve device lookup to match partial UDIDs (#212)
  • Wait for process exit after SIGTERM in swift_package_stop, LogCapture.stopCapture, and LLDBRunner.terminate(); poll with kill -0 and escalate to SIGKILL (#221)
  • Fix debug_evaluate and debug_lldb_command returning empty output at breakpoints; drain stale PTY output before sending commands (#226)
  • Fix stop_mac_app failing to kill debugger-attached processes in TX state; detach LLDB before sending SIGTERM/SIGKILL (#224)
  • Fix list_files misleading membershipExceptions label; fix remove_synchronized_folder_exception not finding auto-created exception sets; add add_package_product tool for linking existing SPM products to targets (#227)
  • Fix test_macos failing entire run when one only_testing target is invalid; pre-validate entries against available test targets and filter out invalid ones with a warning (#229)
  • Fix stop_device_log_cap failing to collect logs from physical devices; correct log collect --start date format from ISO8601 to yyyy-MM-dd HH:mm:ss; surface error details when log collect writes diagnostics to stdout
  • Fix device log capture requiring sudo; switch from log collect --device-udid to log stream background process (#233)
  • Fix remove_swift_package leaving stale PBXBuildFile and PBXSwiftPackageProductDependency entries in pbxproj (#234)
  • Fix start_device_log_cap unable to capture device logs; log stream has no device flags; switch to idevicesyslog from libimobiledevice with CoreDevice-to-hardware UDID resolution (#235)

🗜️ Tweaks

  • Review XcodeBuildMCP v2.3.0 changes for applicability; no gaps in list-schemes or simulator init, confirmed SIGTERM fix needed (#220)
  • Upgrade MCP Swift SDK from 0.10.2 to 0.11.0; adds 2025-11-25 spec coverage, icons/metadata, elicitation, HTTP transport (#219)

Week of Mar 8 – Mar 14, 2026

✨ Features

  • Add show_performance_baselines tool to xc-build; read and display existing .xcbaseline plists with human-readable metric names, filtering by target, test class, and metric type
  • Add get_performance_metrics and set_performance_baseline tools to xc-build; extract measure(metrics:) results from xcresult bundles and create/update .xcbaseline plists for automatic regression detection
  • build_macos / test_macos: add errors_only parameter to suppress warnings from output; show only compiler errors, linker errors, and build summary (#195)
  • Add sample_mac_app and profile_app_launch profiling tools; extract PIDResolver to Core; parse sample output into agent-friendly summaries
  • Integrate Swift Backtrace API (SE-0419); attach symbolicated backtraces to unexpected MCPError.internalError on macOS 26+
  • Add scaffold_module composite tool; create a framework module with test target, sync folders, dependencies, embedding, and test plan entry in one call (#177)
  • create_scheme: accept build_targets array for multiple build action entries; first target is primary for launch/test
  • detect_unused_code: filter out Periphery's superfluousIgnoreComment warnings; these are an unresolvable cycle on assign-only properties with // periphery:ignore comments (#198)
  • build_macos: add for_testing parameter to run build-for-testing; compiles test targets without executing them. test_macos/test_sim/test_device: add test_plan parameter to target non-default test plans. list_test_plan_targets: add test_plan and all_plans parameters to query plans not attached to a scheme
  • build_device now returns the built .app path in its output; enables seamless build_deviceinstall_app_devicelaunch_app_device pipeline
  • Add deploy_device and build_deploy_device composite tools; stop → install → launch (or build → stop → install → launch) in a single call (bpv-4ka)

🐞 Fixes

  • Fix build_device false "build appears stuck" error; increase no-output timeout from 30s to 120s for device builds where code signing produces long output gaps
  • Fix list_devices showing "Type: Unknown" for iPad Mini; read marketingName from hardwareProperties instead of deviceProperties
  • build_macos / build_run_macos / test_macos / build_debug_macos now reject iOS-only projects with a clear error instead of silently building; checks SUPPORTED_PLATFORMS and suggests xc-simulator tools
  • Fix add_synchronized_folder_exception creating duplicate exception sets; append to existing set for the same target instead of creating a second one. Fix remove_synchronized_folder_exception only checking the first exception set for a target
  • Fix sample_mac_app bundle ID lookup and output capture; use NSRunningApplication instead of pgrep and -file flag for reliable sample output
  • Fix detect_unused_code result_file returning stale entries from prior scans
  • Fix test_macos only_testing failing to match Swift Testing functions with backtick-escaped names containing spaces; auto-normalize identifiers by wrapping in backticks and appending ()
  • Fix stop_app_device failing with "Missing expected argument --pid"; resolve bundle identifier to PID via devicectl device info processes before terminating (cfo-jj0)
  • Fix start_device_log_cap producing empty log files; replace nonexistent devicectl device info syslog with log collect --device-udid + log show collection-based approach (m5k-jma)

Week of Mar 1 – Mar 7, 2026

✨ Features

  • Add detect_unused_code tool wrapping Periphery CLI; finds unused declarations, redundant imports, assign-only properties, and redundant public accessibility in Swift projects (#171)
  • test_macos now returns XCTest measure() timing data in results; average, relative standard deviation, and individual values (#169)
  • test_macos now surfaces per-test results with skip reasons and performance metrics; no more "1 passed" when 2 tests were silently skipped (#180)
  • test_macos error output now lists failed test names prominently; no more scanning 3600+ lines to find 4 failures (#185)
  • test_macos output now always includes both passed and failed counts in the summary line; grep-friendly even with -quiet builds
  • search_crash_reports now supports report_path for reading a specific crash report directly; agents can search → get path → read full report without re-searching (#186)
  • search_crash_reports now shows symbolicated crashing thread stack trace; top 15 frames with image names, symbols, and source locations (#182)
  • Add JSON output mode (format: "json") and field selection (fields) to discovery tools; show_build_settings, list_schemes, list_test_plan_targets, discover_projs (#163)
  • Add get_coverage_report and get_file_coverage tools; per-target and function-level coverage from xcresult bundles
  • Add validate_project tool; catches dangling copy-files refs, orphaned build files, unreferenced phases, and inconsistent embedding (#134)
  • Add remove_framework tool; remove framework dependencies from one or all targets, cleaning up link phases, embed phases, and orphaned file references (#158)
  • detect_unused_code: add group_by parameter for per-target, per-kind, and per-directory summaries (#188)
  • Add sample_mac_app, profile_app_launch, and SampleOutputParser; profiling and performance capture tools for xc-build with parsed call-stack summaries
  • sample_mac_app parses raw sample output into agent-friendly summaries; heaviest functions table, call paths, idle thread filtering
  • Integrate Swift Backtrace API (SE-0419); attach symbolicated backtraces to unexpected MCPError.internalError on macOS 26+ (#184)

🐞 Fixes

  • Fix detect_unused_code checklist format exceeding MCP token limit on large projects; replace separate checklist mode with always-on disk checklist and compact summary output (#183)
  • Fix test_macos false-positive stuck timeout on XCUI performance tests; increase default no-output timeout from 30s to 120s for test commands (#178)
  • Fix list_test_plan_targets returning "no test plans found" for schemes with inline test targets; falls back to scheme <TestAction> testable references (#179)
  • Document only_testing method-level filter limitation for XCUI test targets; xcodebuild silently runs 0 tests; suggest class-level filtering in error message (#181)
  • Fix subprocess orphan processes on MCP abort/timeout; configure teardownSequence (SIGTERM → 5s → SIGKILL) so cancelled builds/tests don't hold the SPM lock (#171)
  • Fix validate_project crash from PBXBuildFile Hashable violation; use ObjectIdentifier instead of Set<PBXBuildFile>
  • Fix formatTestToolResult exit-code override suppressing failures when no tests ran; guard with totalTestCount > 0 (#166)
  • Fix swift_package_test reporting passing tests as MCP error -32603; override non-zero exit code when parsed output confirms all tests passed (#160)
  • Fix add_file creating duplicate PBXFileReference entries and miscomputing paths for groups with a path property; uses sourceRoot when file is outside the group's resolved path, deduplicates existing refs (#159)
  • Fix remove_file removing files from all targets when multiple targets have files with the same name; now matches by full path via fullPath(sourceRoot:) instead of filename (#156)
  • Fix add_swift_package returning "already exists" instead of linking product to a new target; now links the product and detects duplicates (#154)
  • Fix start_mac_log_cap process name derivation; resolve actual executable name from app bundle Info.plist instead of lowercased bundle ID suffix; case-insensitive fallback when bundle not found (#186)
  • Fix detect_unused_code result_file returning stale entries from prior scans; delete old checklist when a new scan overwrites the cache file
  • Fix detect_unused_code checklist not reconciling with already-removed code; strengthen agent instructions to mark items done immediately after each resolution (#187)
  • Fix add_file path doubling when adding files to groups with a filesystem path; file reference now computed relative to the group location (#155)
  • Default ONLY_ACTIVE_ARCH=YES for Debug in all target-creation tools; prevents cross-compilation failures with SPM dependencies (#151)
  • Fix add_target, add_app_extension, add_swift_package, add_framework, and create_xcodeproj issues found during extension setup; orphan targets, missing framework linking, wrong sourceTree for developer frameworks, macOS TARGETED_DEVICE_FAMILY, ALWAYS_SEARCH_USER_PATHS (#150)
  • Fix add_target only creating Debug/Release configs; now matches all project-level build configurations (#176)
  • Fix add_target creating groups at project root; add parent_group parameter for nesting under existing groups (#174)
  • Fix add_target adding extraneous build settings; minimize to PRODUCT_BUNDLE_IDENTIFIER, PRODUCT_NAME, GENERATE_INFOPLIST_FILE (#173)
  • Fix add_to_copy_files_phase missing CodeSignOnCopy/RemoveHeadersOnCopy attributes; add attributes parameter with auto-defaults for Embed Frameworks (#175)
  • Fix add_file rejecting slash-separated group paths like Components/TableView; unify group path resolution across all tools (#172)
  • Fix sample_mac_app failing for apps with spaces/parens in name; use NSRunningApplication for bundle ID lookup and -file flag for reliable sample output capture

🗜️ Tweaks

  • Isolate session defaults by PPID so parallel MCP clients don't clobber each other
  • Enhance test plan error hints with scheme awareness; suggests the correct scheme when a test target isn't in the specified scheme
  • Trim verbose tool descriptions; reduce token overhead for 7 tools (#163)
  • Evaluate MCP vs CLI architecture; concluded MCP should be kept with incremental improvements (#161)
  • Investigate XcodeBuildMCP coverage tools; no new tools needed; our get_coverage_report and get_file_coverage already cover the use cases (#168)
  • Investigate XcodeBuildMCP xcresult/stderr output fixes; no action needed; our combined-stream approach avoids the issues by design
  • Review XcodeBuildMCP v2.1.0 changes; no actionable items for xc-mcp (#164)
  • Review Sentry XcodeBuildMCP session defaults hardening; our Swift actor approach already covers the key patterns (#94)
  • Review tuist/xcodeproj 9.10.0–9.10.1 changes (#162)
  • Port crash-to-test association from xcsift; BuildOutputParser now tracks which test was running when a crash occurs and reports it in failed test diagnostics (#153)
  • Bump XcodeProj dependency to 9.10.1 (#152)

Week of Feb 22 – Feb 28, 2026

✨ Features

  • Migrate from Foundation Process to swift-subprocess; safer process I/O; no more pipe deadlocks by design (#101)
  • Add validate_project tool; catches stale embeds, missing links, and orphaned build phases before they bite you (#135)
  • Add project configuration generation tools; create and modify test plans and schemes programmatically (#102)
  • Add crash report search/inspect tool; dig through ~/Library/Logs/DiagnosticReports without opening Console.app (#127)
  • Add check_build tool for single-target compilation; type-check one target without rebuilding the world (#126)
  • Debug tools now check process state before sending commands; no more mysterious hangs on crashed processes (#133)
  • build_debug_macos detects early process crashes (dyld, SIGABRT) instead of silently hanging (#132)
  • test_macos surfaces crash diagnostics when the test host fails to bootstrap (#100)
  • Add test plan management tools to xc-project; create, query, and modify .xctestplan files (#122)
  • Improve UI test and test plan ergonomics; less boilerplate, more doing (#121)
  • Validate only_testing identifiers before running xcodebuild; catch typos before a 2-minute build (#118)
  • Suppress non-project warnings in build output; see your warnings, not Apple's (#98)
  • Add in-place target renaming with cross-reference updates (#117)
  • xc-swift auto-detects package_path from working directory; one less thing to specify (#99)
  • Add --build-tests flag to swift_package_build (#113)
  • Add environment variable and skip filter support to swift_package_test (#112)
  • Add swift_format and swift_lint tools to xc-swift server (#95)
  • Register test_sim in the Build server so you don't need a separate simulator server (#97)
  • Support XCLocalSwiftPackageReference deletion via XcodeProj 9.9.0 (#108)
  • launch_mac_app / build_run_macos return PID and detect early exit (#139)
  • debug_attach_sim supports macOS apps by bundle_id without requiring a simulator (#140)
  • Add get_test_attachments tool; extract screenshots and data files from .xcresult bundles with test ID and failure filtering (#144)
  • Add persistent custom env vars to session defaults; set_session_defaults(env: {...}) deep-merges and applies to all build/test/run commands (#148)
  • Suggest correct scheme when test target isn't in the specified scheme; no more guessing which scheme has your tests (#145)
  • start_mac_log_cap fixes bundle_id predicate reliability; adds level parameter and stream health checks (#147)

🐞 Fixes

  • Fix pipe deadlock; drain stdout/stderr before waitUntilExit; was causing intermittent server hangs (#119)
  • Fix xcodebuild process not killed on MCP cancellation; orphaned builds no more (#104)
  • Fix build_debug_macos falsely reporting crash on successful launch (#137)
  • Fix process interrupt via debug_lldb_command racing with async stop notification (#136)
  • Reload shared session file on access so focused servers stay in sync (#138)
  • Fix debug_stack thread backtrace syntax (#131)
  • Fix stop_mac_app hanging when process is crashed or under LLDB (#130)
  • test_macos no longer reports success when only_testing filter matches zero tests (#129)
  • Use parsed build output status instead of exit code alone; catches builds that "succeed" with errors (#115)
  • Fix list_files missing files from synchronized folder targets (#106)
  • Fix list_test_plan_targets failing on relative project paths (#120)
  • Fix search_crash_reports requiring process_name or bundle_id when neither should be mandatory (#123)
  • Add timeout support to SwiftRunner; prevents runaway swift commands (#125)
  • Fix build_run_macos / launch_mac_app crashing on non-embedded frameworks; symlink from BUILT_PRODUCTS_DIR instead of relying on DYLD_FRAMEWORK_PATH (#141)
  • Fix build_debug_macos timeout with stop_at_entry; resolve actual executable name instead of .app folder name (#142)
  • Fix debug_detach rejecting valid PID parameter; getInt now handles JSON numbers decoded as doubles (#143)
  • Fix get_test_attachments parsing manifest with wrong keys; returns Unnamed/unknown for all attachments (#146)
  • Fix create_xcodeproj creating orphan default target; add_swift_package not linking to Frameworks build phase; add_framework using wrong sourceTree for developer frameworks; add_target setting TARGETED_DEVICE_FAMILY for macOS; missing ALWAYS_SEARCH_USER_PATHS = NO

🗜️ Tweaks

  • Redesign integration tests for speed; split slow tests behind RUN_SLOW_TESTS flag (#96)
  • Bump XcodeProj to 9.10.1; picks up Xcode 26 dstSubfolder support and CommentedString perf fix (#149)

Week of Feb 15 – Feb 21, 2026

✨ Features

  • Add build_debug_macos; launch macOS apps under LLDB, the way Xcode's Run button does (#4)
  • Add 8 new LLDB debug tools; watchpoints, memory reads, symbol lookup, view hierarchy dumps, and more (#1)
  • Add interact_ tools for macOS app UI interaction via Accessibility API; click, type, inspect UI trees (#60)
  • Add screenshot_mac_window tool; capture any window via ScreenCaptureKit without needing a simulator (#58)
  • SwiftUI preview capture tool; render #Preview macros to images on a simulator (#59)
  • Add macOS log capture tools; stream and filter unified logs per-app (#8)
  • Share session defaults across MCP servers; set once, use everywhere (#32)
  • Add rename_target tool with cross-reference updates across schemes, dependencies, and build phases (#111)
  • Add remove_group, remove_target_from_synchronized_folder, and exception set tools (#103, #128, #107)
  • Add tool to share existing synchronized folder with another target (#72)
  • Improve test_macos ergonomics; timeout parameter, better error surfacing from xcresults (#109)
  • Doctor, gesture presets, Xcode state sync, next-step hints, and workflow management; a batch of quality-of-life tools (#81)

🐞 Fixes

  • Fix LLDB sessions hanging; switch from pipes to PTY for stdin, matching how real terminals work (#33)
  • Fix debug tools hanging due to ephemeral LLDB sessions that vanish mid-command (#11)
  • Fix build_debug_macos SIGABRT crash; launch via Launch Services instead of direct LLDB process launch (#9)
  • Fix build_debug_macos producing no useful error on failure (#38)
  • Fix debug_attach_sim and debug_breakpoint_add hanging on macOS native apps (#27)
  • Fix preview_capture compiler crash from #Preview in additional source files (#63)
  • Fix preview_capture build config to avoid ASTMangler crash and launch failure (#62)
  • Fix preview_capture for local Swift packages missing deployment target (#71)
  • Fix add_target setting wrong bundle identifier key (#79)
  • Fix add_target missing productReference and Products group entry (#80)
  • Fix add_framework creating duplicate file references instead of reusing BUILT_PRODUCTS_DIR (#73, #74)
  • Fix list_files to include synchronized folder contributions (#105)
  • Fix rename_target gaps found during real-world module rename (#110)
  • Fix test_macos error output truncation (#78)
  • Surface warning when testmanagerd crashes during test run (#77)
  • Fix swift-frontend SILGen crash when building IceCubesApp fixture (#65, #64)
  • Fix IceCubesApp full build crash with Xcode 26 (#70)
  • Fix Alamofire fixture build with Xcode 26 (#69)
  • Fix buildRunScreenshot installing wrong platform build (#68)
  • Fix integration test failures for preview_capture and IceCubesApp (#67, #66, #91)
  • Fix reading test stdout from XCUI tests (#76)

🗜️ Tweaks

  • Extract ProcessRunner, ProcessKiller, LogCaptureBuilder, and BuildSettingExtractor utilities; less duplication, cleaner tool code (#88, #84, #82)
  • Migrate manual argument extraction to ArgumentExtraction helpers (#89)
  • Replace DispatchQueue usage with structured concurrency (#83)
  • Replace Date() timing with ContinuousClock (#86)
  • Add typed throws to XCStringsParser mutation methods (#90)
  • Deduplicate batch parseEntries in XCStrings tools (#85)
  • Add reserveCapacity to batch array loops (#87)
  • Extract shared findSyncGroup and clean up sync folder tools (#116)
  • Expose 6 implemented-but-unregistered project tools (#124)
  • Fix preview_capture end-to-end rendering (#61)
  • Fix PreviewCaptureTool swiftlint warnings (#92)

Week of Feb 1 – Feb 7, 2026

🗜️ Tweaks

  • Review xcsift coverage scanner optimization (#2)
  • Review xcsift Swift Testing PR (#14)
  • Review xcsift issue-52 fix (#34)
  • Review xcstrings-crud BatchWriteResult fix (#39)

Week of Jan 25 – Jan 31, 2026

✨ Features

  • Integrate xcsift build output parsing; structured errors, warnings, and notes instead of raw xcodebuild noise (#17)
  • Add granular test selection to test tools; run specific test classes or methods (#30)
  • Add code coverage collection to test tools (#44)
  • Add Copy Files build phase management; embed frameworks, copy resources, all the fun stuff (#23)
  • Add remove_synchronized_folder tool (#12)

🐞 Fixes

  • Fix MCP tools corrupting unrelated PBXCopyFilesBuildPhase sections; the worst kind of silent data loss (#29)
  • Fix set_build_setting dropping dstSubfolder fields from PBXCopyFilesBuildPhase (#31)
  • Fix add_synchronized_folder deleting scheme files on save (#24)
  • Fix synchronized folder path handling for nested groups (#6)
  • Fix scheme deletion; use writePBXProj instead of write to preserve .xcscheme files (#40)

🗜️ Tweaks

  • Eliminate ~100 duplicate files across focused servers (#25)
  • Implement review consolidation changes (#20)

Week of Jan 18 – Jan 24, 2026

✨ Features

  • Implement Homebrew package; brew install toba/tap/xc-mcp and you're off (#42)
  • Add --no-sandbox flag to xc-project, xc-build, and xc-strings servers (#13)
  • Integrate xcstrings-crud as the xc-strings MCP server; full localization string catalog management (#43)
  • Improve MCP tool timeout handling with streaming progress; no more silent hangs on long builds (#16)
  • Error handling improvements across all tools (#37)

🗜️ Tweaks

  • Split xc-mcp into 7 focused MCP servers; smaller token footprint, faster client setup (#5)
  • Complete all 11 implementation phases: Foundation, Simulator, Device, macOS Build, Discovery, Log Capture, Simulator Extended, LLDB Debugging, UI Automation, Swift Package Manager, Utilities (#52 through 50)
  • Migrate tests to Swift Testing framework with parameterized test cases (#10)
  • Add typed throws to select methods (#7)
  • Refactor NSString path manipulation to URL (#3)
  • Add DocC documentation (#41)
  • Add XCStrings test coverage (#36)
  • Clean up unused files and update README (#28)
  • Create Swift code review skill (#26)
  • Code optimization pass (#45)
  • Fix swiftlint warnings (#22)
  • Document unexposed capabilities (#15)
  • Unified Xcode MCP Server milestone completed (#19)