Skip to content

Fix: destination parameter bug#98

Open
romanr wants to merge 6 commits into
conorluddy:mainfrom
romanr:v.3.2.2
Open

Fix: destination parameter bug#98
romanr wants to merge 6 commits into
conorluddy:mainfrom
romanr:v.3.2.2

Conversation

@romanr

@romanr romanr commented Jan 10, 2026

Copy link
Copy Markdown

This prevents not being able to run macOS builds at all because "iOS" was injected as destination regardless of destination parameter passed.
Fixes: prevent stale iOS simulator destinations from being applied, avoid hidden overrides when the caller provided a destination.

  • fix argument handling so caller-supplied parameters are preserved and tracked, while cached values only fill genuine gaps; destination/sdk/config now resolve through an explicit/compatible path rather than being overwritten by prior runs (build.ts:73-118).
  • Updated to correctly report when smart defaults were applied, based on whether the caller actually provided each parameter (build.ts:193-218).
  • destination resolution: explicit destinations always win; cached destinations are only reused when platform-compatible; macOS and device SDKs avoid simulator carry-over; simulator suggestions are platform-filtered and derived from runtime tokens without hardcoded platform switches (build.ts:321-449).

…to latest 1.25.1 which depends on zod v4 which has breaking changes.
…ssue and produces streaming output. Which caused xc-mcp to not return anything at all. Enhanced xcodebuild output with immediate error/warning visibility and streaming command execution.
Copilot AI review requested due to automatic review settings January 10, 2026 20:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes a critical bug where iOS simulator destinations were incorrectly applied to macOS builds, preventing macOS builds from running. The fix introduces explicit parameter tracking, platform-compatible destination resolution, and enhanced build output visibility.

Changes:

  • Introduced explicit parameter tracking to distinguish caller-provided vs. cached values, preventing stale destinations from overriding explicit parameters
  • Implemented platform-aware destination resolution that filters simulator suggestions by platform and avoids simulator carry-over for macOS/device builds
  • Enhanced build output with top-level errors/warnings arrays (first 10 each) and added fatal pattern detection with early termination

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
src/tools/xcodebuild/build.ts Core fix: parameter tracking, destination resolution logic, platform-compatible caching, streaming command execution with fatal pattern detection
src/utils/command.ts Added executeCommandStreaming function with timeout and fatal pattern detection capabilities
src/utils/response-cache.ts Enhanced extractBuildSummary to include first 10 errors and warnings arrays for immediate visibility
tests/__tests__/tools/xcodebuild-build.test.ts New comprehensive test suite validating destination resolution, parameter precedence, and platform matching
tests/__tests__/utils/response-cache.test.ts Added tests for errors/warnings arrays including 10-item limit validation
README.md Updated documentation to reflect immediate error/warning visibility without secondary calls
package.json, package-lock.json, src/index.ts Version updates and dependency pinning

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
summary: { errorCount: 0, warningCount: 1, duration: 5000 }
}
// Only use xcodebuild-get-details for full logs or >10 errors/warnings
\`\`\`

Copilot AI Jan 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code block uses escaped backticks (\\\\`) instead of regular markdown code fence backticks. This will render incorrectly in markdown - the backslashes should be removed so it displays as a proper code block.

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +92
const provided = {
configuration: Object.prototype.hasOwnProperty.call(rawArgs, 'configuration'),
destination: Object.prototype.hasOwnProperty.call(rawArgs, 'destination'),
sdk: Object.prototype.hasOwnProperty.call(rawArgs, 'sdk'),
derivedDataPath: Object.prototype.hasOwnProperty.call(rawArgs, 'derivedDataPath'),
};

Copilot AI Jan 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Object.prototype.hasOwnProperty.call() is the correct safe approach to check property existence. However, the check only verifies if the property exists on the object, not if it's explicitly defined by the caller. For example, if destructuring provides a default value (e.g., 'configuration = "Debug"' on line 78), the property would still exist in rawArgs. Consider documenting this behavior or ensuring that defaults are not applied during destructuring if you want to distinguish between explicitly-provided and defaulted parameters.

Copilot uses AI. Check for mistakes.
Comment thread README.md
{
buildId: "build-abc123",
success: true,
warnings: ["...warning: unused variable 'foo'..."], // first 10 warnings

Copilot AI Jan 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code comment says "first 10 warnings" but the actual implementation in response-cache.ts slices both errors and warnings to the first 10. The comment should be updated to mention both errors and warnings for consistency.

Suggested change
warnings: ["...warning: unused variable 'foo'..."], // first 10 warnings
warnings: ["...warning: unused variable 'foo'..."], // first 10 warnings (errors similarly truncated)

Copilot uses AI. Check for mistakes.
Comment on lines +146 to +152
const augmentedErrors = [...summary.errors];
if (result.fatalMatch) {
augmentedErrors.unshift(`Detected fatal xcodebuild output: ${result.fatalMatch}`);
}
if (result.timedOut) {
augmentedErrors.unshift(`Build aborted after ${timeoutMs}ms (timeout)`);
}

Copilot AI Jan 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error augmentation logic uses unshift to add fatal errors at the beginning, but since errors array is already sliced to 10 items in extractBuildSummary, this could result in more than 10 errors being returned. Consider slicing augmentedErrors after all additions to maintain the 10-item limit mentioned in the documentation.

Copilot uses AI. Check for mistakes.
Comment thread src/utils/command.ts
Comment on lines +72 to +147
export async function executeCommandStreaming(
command: string,
options: StreamingOptions = {}
): Promise<StreamingCommandResult> {
const {
timeout = 60000,
maxBuffer = 10 * 1024 * 1024,
fatalPatterns = [],
onFatalMatch,
} = options;

return new Promise<StreamingCommandResult>((resolve, reject) => {
const child = spawn(command, { shell: true, timeout });

let stdout = '';
let stderr = '';
let timedOut = false;
let fatalMatch: string | undefined;

const checkPatterns = (chunk: string) => {
if (fatalMatch) return;
for (const pattern of fatalPatterns) {
const match = chunk.match(pattern);
if (match) {
fatalMatch = match[0];
onFatalMatch?.(match[0]);
child.kill();
break;
}
}
};

const timeoutId = setTimeout(() => {
timedOut = true;
child.kill();
}, timeout);

child.stdout?.on('data', data => {
const text = data.toString();
stdout += text;
checkPatterns(text);
if (stdout.length > maxBuffer) {
child.kill();
clearTimeout(timeoutId);
reject(
new McpError(
ErrorCode.InternalError,
`Command output exceeded max buffer size of ${maxBuffer} bytes`
)
);
}
});

child.stderr?.on('data', data => {
const text = data.toString();
stderr += text;
checkPatterns(text);
});

child.on('close', code => {
clearTimeout(timeoutId);
resolve({
stdout: stdout.trim(),
stderr: stderr.trim(),
code: code ?? 0,
timedOut,
fatalMatch,
});
});

child.on('error', error => {
clearTimeout(timeoutId);
reject(new McpError(ErrorCode.InternalError, `Failed to execute command: ${error.message}`));
});
});
}

Copilot AI Jan 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new executeCommandStreaming function introduced in this PR lacks unit test coverage. Since tests/tests/utils/command.test.ts exists and tests other command utilities, consider adding comprehensive tests for executeCommandStreaming including timeout scenarios, fatal pattern detection, and buffer overflow handling.

Copilot uses AI. Check for mistakes.
Comment thread README.md

## Example: Optimal Login Flow

\`\`\`typescript

Copilot AI Jan 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code block uses escaped backticks (\\\\`) instead of regular markdown code fence backticks. This will render incorrectly in markdown - the backslashes should be removed so it displays as a proper code block.

Copilot uses AI. Check for mistakes.
}

// Destructure errors/warnings from summary for top-level placement
const { errors: _ignoredErrors, warnings, ...summaryRest } = adjustedSummary;

Copilot AI Jan 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The destructuring operation extracts 'errors' from adjustedSummary and renames it to '_ignoredErrors', but then uses the separate 'augmentedErrors' array for the top-level errors field. This is correct but the naming could be clearer. Consider renaming '_ignoredErrors' to '_summaryErrors' to better communicate that these are the summary's error list (which is superseded by augmentedErrors).

Suggested change
const { errors: _ignoredErrors, warnings, ...summaryRest } = adjustedSummary;
const { errors: _summaryErrors, warnings, ...summaryRest } = adjustedSummary;

Copilot uses AI. Check for mistakes.
Comment thread src/index.ts
{
name: 'xc-mcp',
version: '3.2.0',
version: '3.2.1',

Copilot AI Jan 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version mismatch detected: package.json declares version 3.2.2, but src/index.ts declares version 3.2.1. These versions should be synchronized to avoid confusion.

Suggested change
version: '3.2.1',
version: '3.2.2',

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +251
describe('xcodebuild-build parameter resolution', () => {
const projectPath = 'SpaceTime.xcodeproj';
const scheme = 'SpaceTime';

const successResult = {
stdout: '',
stderr: '',
code: 0,
timedOut: false,
};

beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();

mockBuildXcodebuildCommand.mockImplementation((action: string, path: string, opts: any) =>
JSON.stringify({ action, path, opts })
);
mockExecuteCommandStreaming.mockResolvedValue(successResult);
mockExtractBuildSummary.mockReturnValue({
success: true,
errors: [],
warnings: [],
errorCount: 0,
warningCount: 0,
buildSizeBytes: 0,
});
mockResponseCacheStore.mockReturnValue('build-id');
mockCreateConfigManager.mockReturnValue({
recordSuccessfulBuild: jest.fn(),
});
});

async function loadTool() {
const mod = await import('../../../src/tools/xcodebuild/build.js');
return mod.xcodebuildBuildTool;
}

function responseFrom(result: any) {
return JSON.parse(result.content[0].text);
}

it('uses explicit destination and does not override it', async () => {
mockGetPreferredBuildConfig.mockResolvedValue({
scheme,
configuration: 'Release',
destination: 'platform=iOS Simulator,id=CACHED',
});

const xcodebuildBuildTool = await loadTool();

const result = await xcodebuildBuildTool({
projectPath,
scheme,
destination: 'generic/platform=macOS,name=Any Mac',
sdk: 'macosx',
});

const lastCall = mockBuildXcodebuildCommand.mock.calls.at(-1)![2] as any;
expect(lastCall.destination).toBe('generic/platform=macOS,name=Any Mac');
expect(mockGetPreferredSimulator).not.toHaveBeenCalled();

const response = responseFrom(result);
expect(response.intelligence.usedSmartDestination).toBe(false);
});

it('skips cached simulator destination when building for macOS', async () => {
mockGetPreferredBuildConfig.mockResolvedValue({
scheme,
configuration: 'Debug',
destination: 'platform=iOS Simulator,id=CACHED',
});

const xcodebuildBuildTool = await loadTool();

const result = await xcodebuildBuildTool({
projectPath,
scheme,
sdk: 'macosx',
});

const lastCall = mockBuildXcodebuildCommand.mock.calls.at(-1)![2] as any;
expect(lastCall.destination).toBeUndefined();

const response = responseFrom(result);
expect(response.intelligence.usedSmartDestination).toBe(false);
});

it('reuses cached destination when platform matches sdk', async () => {
mockGetPreferredBuildConfig.mockResolvedValue({
scheme,
configuration: 'Debug',
destination: 'platform=tvOS Simulator,id=TV-1',
});

const xcodebuildBuildTool = await loadTool();

const result = await xcodebuildBuildTool({
projectPath,
scheme,
sdk: 'tvossimulator',
});

const lastCall = mockBuildXcodebuildCommand.mock.calls.at(-1)![2] as any;
expect(lastCall.destination).toBe('platform=tvOS Simulator,id=TV-1');

const response = responseFrom(result);
expect(response.intelligence.usedSmartDestination).toBe(true);
});

it('selects a platform-matched simulator when no cached destination', async () => {
mockGetPreferredBuildConfig.mockResolvedValue({
scheme,
configuration: 'Debug',
});

mockGetPreferredSimulator.mockResolvedValue(null);
mockGetSimulatorList.mockResolvedValue({
devices: {
'com.apple.CoreSimulator.SimRuntime.tvOS-18-0': [
{
udid: 'TV-UDID',
name: 'Apple TV',
deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K',
state: 'Shutdown',
isAvailable: true,
availability: 'available',
bootHistory: [],
},
],
'com.apple.CoreSimulator.SimRuntime.iOS-18-0': [
{
udid: 'PHONE',
name: 'iPhone',
deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-16',
state: 'Shutdown',
isAvailable: true,
availability: 'available',
bootHistory: [],
},
],
},
runtimes: [],
devicetypes: [],
lastUpdated: new Date(),
preferredByProject: new Map(),
});

const xcodebuildBuildTool = await loadTool();

const result = await xcodebuildBuildTool({
projectPath,
scheme,
sdk: 'tvossimulator',
});

const lastCall = mockBuildXcodebuildCommand.mock.calls.at(-1)![2] as any;
expect(lastCall.destination).toBe('platform=tvOS Simulator,id=TV-UDID');

const response = responseFrom(result);
expect(response.intelligence.usedSmartDestination).toBe(true);
});

it('prioritizes caller-provided configuration over cached', async () => {
mockGetPreferredBuildConfig.mockResolvedValue({
scheme,
configuration: 'Debug',
});

const xcodebuildBuildTool = await loadTool();

await xcodebuildBuildTool({
projectPath,
scheme,
configuration: 'Release',
});

const lastCall = mockBuildXcodebuildCommand.mock.calls.at(-1)![2] as any;
expect(lastCall.configuration).toBe('Release');
});

it('uses cached configuration when caller does not provide one and marks smart usage', async () => {
mockGetPreferredBuildConfig.mockResolvedValue({
scheme,
configuration: 'Release',
});

const xcodebuildBuildTool = await loadTool();

const result = await xcodebuildBuildTool({
projectPath,
scheme,
});

const lastCall = mockBuildXcodebuildCommand.mock.calls.at(-1)![2] as any;
expect(lastCall.configuration).toBe('Release');

const response = responseFrom(result);
expect(response.intelligence.usedSmartConfiguration).toBe(true);
});
});

Copilot AI Jan 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new fatal pattern detection and timeout handling features introduced in executeCommandStreaming and build.ts lack test coverage. Consider adding test cases for scenarios where timeout occurs (timedOut: true) or when a fatal pattern is matched (fatalMatch is set).

Copilot uses AI. Check for mistakes.
Comment on lines +440 to +441
const baseName = lastSegment.split('-')[0];
if (!baseName) return undefined;

Copilot AI Jan 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The platformLabelFromRuntime function assumes the runtime format has a segment with a dash separator (e.g., 'tvOS-18-0'), but if the runtime string doesn't contain a dash, baseName will be the entire lastSegment. Consider adding defensive handling for runtimes that don't follow the expected format to avoid malformed platform labels like 'com Simulator' or 'SimRuntime Simulator'.

Suggested change
const baseName = lastSegment.split('-')[0];
if (!baseName) return undefined;
if (!lastSegment) return undefined;
const hasDash = lastSegment.includes('-');
const baseName = hasDash ? lastSegment.split('-')[0] : lastSegment;
if (!baseName) return undefined;
// If there is no dash in the last segment, make sure the base name looks like a platform
// token before constructing a label. This avoids labels like "com Simulator" or
// "SimRuntime Simulator" for unexpected runtime formats.
if (!hasDash) {
const lower = baseName.toLowerCase();
const looksLikePlatform =
lower.includes('mac') ||
lower.includes('iphone') ||
lower.includes('ios') ||
lower.includes('tvos') ||
lower.includes('appletv') ||
lower.includes('watch') ||
lower.includes('vision');
if (!looksLikePlatform) {
return undefined;
}
}

Copilot uses AI. Check for mistakes.
@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown

Code Review — PR #98: Fix destination parameter bug

Overview

This PR fixes a real bug where cached iOS simulator destinations were being injected into macOS builds, making macOS targets unbuildable. It also adds streaming command execution with early-exit on fatal patterns, and promotes errors/warnings to the top-level build response. Good motivation, but there are significant concerns.


Issues

🔴 55-second build timeout will break large real-world projects

File: src/tools/xcodebuild/build.ts

const timeoutMs = 55_000; // Stay under MCP transport limits

The old timeout was 10 minutes. This change caps all builds at 55 seconds. In practice, even medium-sized iOS projects regularly take 2–5 minutes on a clean build, and anything with many dependencies or a cold cache can exceed 10 minutes. This will cause xcodebuild-build to report timedOut: true and return a failure for builds that would have succeeded.

The comment "Stay under MCP transport limits" doesn't hold up — MCP transport has no 55-second constraint. If there's a specific transport issue, please document it. Otherwise, consider restoring a longer timeout (5–10 minutes) and keeping early-exit only for the fatal patterns.

A better approach:

const timeoutMs = 600_000; // 10 minutes for builds

...with early-exit still triggered by fatalPatterns. The fatal pattern detection and the timeout are orthogonal concerns — you don't need to reduce the timeout to get early exit on fatal errors.


🔴 Version inconsistency in src/index.ts

File: src/index.ts vs package.json

The server reports version: '3.2.1' but package.json bumps to 3.2.2. These should match.


🟡 SDK pinned to exact version 1.17.1

File: package.json

- "@modelcontextprotocol/sdk": "^1.17.1",
+ "@modelcontextprotocol/sdk": "1.17.1",

Pinning to an exact version prevents consumers from getting security patches and bug fixes in patch releases. The ^ semver range is the standard for production dependencies. If there's a specific reason to pin (e.g., a regression in 1.17.x), it should be documented. Note that PR #99 bumps the SDK to ^1.25.2, so this pin will likely conflict if both PRs target the same base.


🟡 provided tracking via hasOwnProperty is verbose

File: src/tools/xcodebuild/build.ts

const provided = {
  configuration: Object.prototype.hasOwnProperty.call(rawArgs, 'configuration'),
  ...
};

This is technically correct but fragile against MCP serialization (some transport layers strip undefined keys). A simpler and equally correct check:

const provided = {
  configuration: rawArgs.configuration !== undefined,
  destination: rawArgs.destination !== undefined,
  ...
};

Code Quality — What's Good

resolveDestination refactor is the right fix

The new explicit resolution chain — caller wins → cached only if platform-compatible → macOS/device SDKs bail early → simulator fallback — correctly prevents the stale iOS destination from leaking into macOS builds. The logic is clear and the helper functions (deriveSdkInfo, derivePlatformFromToken, platformsCompatible) are well-decomposed.

executeCommandStreaming design is sound

The streaming implementation correctly:

  • Kills the process on fatal pattern match (not just timeouts)
  • Buffers stdout/stderr incrementally
  • Returns timedOut and fatalMatch fields for callers to inspect
  • Rejects on buffer overflow (prevents OOM on runaway processes)

One nit: the timeout option is passed to both spawn (which node.js ignores for shell: true) and separately managed via setTimeout. The spawn({ timeout }) call is effectively a no-op — safe to remove for clarity.

✅ Errors/warnings at top level improve LLM ergonomics

Surfacing the first 10 errors/warnings directly in the build response is a solid UX improvement for AI agents — avoids requiring a follow-up xcodebuild-get-details call for the common case.

provided.destination tracking fixes the smart-default transparency

The old smartDestinationUsed: !destination && smartDestination !== destination was comparing variables that could both be undefined. The new !provided.destination && !!finalConfig.destination is correct.


Overlap with PR #97

This PR shares substantial code with PR #97 (same author) — the executeCommandStreaming utility, README changes, and build.ts streaming integration are nearly identical. If this PR is merged, PR #97 should be closed as superseded.


Summary

Status
55s build timeout 🔴 Will break large projects — restore 10 min, keep fatal-exit
Version mismatch 3.2.1 vs 3.2.2 🔴 Fix before merge
SDK exact-version pin 🟡 Prefer ^1.17.1 unless there's a regression reason
resolveDestination bug fix ✅ Correct and well-structured
executeCommandStreaming ✅ Sound design (minor: spawn timeout no-op)
Errors/warnings at top level ✅ Good UX improvement
provided tracking 🟡 Works, could simplify

@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown

Code Review — PR #98: Fix destination parameter bug

Overall: 🔶 Needs changes (critical timeout issue + minor items)

What this does

  • Overhauls resolveDestination() so an explicit destination always wins over cached values, and macOS/device SDK builds never inherit a cached iOS simulator destination.
  • Introduces executeCommandStreaming with fatal-pattern early termination.
  • Surfaces top-level errors[] / warnings[] (first 10) in the build response.
  • Adds a provided sentinel map to distinguish "arg not passed" from "arg passed as undefined".

🚨 Critical — 55-second build timeout will break real-world iOS builds

const timeoutMs = 55_000; // Stay under MCP transport limits

Replacing a 10-minute timeout with 55 seconds will cause routine builds to fail. Clean iOS simulator builds regularly take 2–5 minutes; clean builds of mid-size projects exceed that. The comment says "stay under MCP transport limits" but MCP stdio transport has no timeout on individual tool calls — this constraint doesn't apply here. Please either restore the previous 600 000 ms default or make the timeout configurable (e.g. an optional timeoutMs parameter).


Bug — double timeout on executeCommandStreaming

const child = spawn(command, { shell: true, timeout }); // spawn's own timeout
// ...
const timeoutId = setTimeout(() => {            // manual timeout
  timedOut = true;
  child.kill();
}, timeout);

Both mechanisms use the same timeout value. Node.js spawn's internal timeout sends SIGTERM after timeout ms, fires the close event, then timedOut is still false because the manual setTimeout may not have fired yet (or fires concurrently in unpredictable order). This means result.timedOut can incorrectly be false even when the process was killed by the spawn timer. Remove the timeout option from spawn and rely solely on the manual setTimeout.


Issue — MCP SDK pinned to exact version

-"@modelcontextprotocol/sdk": "^1.17.1",
+"@modelcontextprotocol/sdk": "1.17.1",

Pinning to an exact version blocks patch updates. Prefer keeping the ^ range.


Issue — version inconsistency between src/index.ts and package.json

src/index.ts sets the server version to '3.2.1' while package.json is bumped to '3.2.2'. These should match.


Potential issue — maxBuffer rejection doesn't stop streaming

child.stdout?.on('data', data => {
  if (stdout.length > maxBuffer) {
    child.kill();
    clearTimeout(timeoutId);
    reject(...);
  }
});

After reject() is called the Promise is settled, but the close event will still fire and call resolve(...). This is harmless (second resolution is ignored) but it leaves the close handler reading timedOut/fatalMatch state after the rejection path, which is confusing. Consider setting a rejected flag and bailing in close.


Strengths

Destination resolution logic is excellent:

  • explicitDestination always wins — the core bug is fixed.
  • derivePlatformFromToken / platformsCompatible are clean, pure functions with no hardcoded platform lists (except the token mapping itself, which is unavoidable).
  • buildDestinationFromUdid correctly falls back to id=<udid> when no runtime label can be derived.
  • runtimeMatchesPlatform is a simple, correct string-inclusion check.

Test coverage — the 6 new tests in xcodebuild-build.test.ts directly cover the fixed scenarios: explicit destination wins, macOS SDK skips cached simulator, platform-matched cached destination reused, platform-filtered simulator selection, caller configuration priority, smart configuration reporting.

extractBuildSummary returning errors[]/warnings[] is a genuine UX improvement — agents get immediate build error visibility without a second round-trip.

provided sentinel map — using Object.prototype.hasOwnProperty.call is more robust than !== undefined for distinguishing absent vs. explicitly-undefined parameters. ✅


Minor

_ignoredErrors in the destructure comment could just be _errors for brevity. The comment on configurationLearned: buildSuccess explains the intent clearly — keep it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants