Contributing
Development Setup
Section titled “Development Setup”-
Clone the repository:
Terminal window git clone https://github.com/sitharaj88/xcode-pilot-mcp.gitcd xcode-pilot-mcp -
Install dependencies:
Terminal window npm install -
Run the quality gate:
Terminal window npm run checkThis runs typecheck, lint, format check, and tests in sequence.
-
Build:
Terminal window npm run build
Available Scripts
Section titled “Available Scripts”| Script | Command | Description |
|---|---|---|
npm run build | tsc && copy-templates | Compile TypeScript and copy templates |
npm run dev | tsc --watch | Watch mode for development |
npm test | vitest run | Run all tests once |
npm run test:coverage | vitest run --coverage | Run tests with coverage report |
npm run lint | eslint . | Check for lint issues |
npm run lint:fix | eslint . --fix | Auto-fix lint issues |
npm run format | prettier --write . | Format all files |
npm run format:check | prettier --check . | Check formatting |
npm run typecheck | tsc --noEmit | Type check without emitting |
npm run check | All of the above | Full quality gate |
Adding a New Tool
Section titled “Adding a New Tool”-
Create the handler file:
src/tools/<category>/<tool-name>.tsimport { executeCommand } from "../../executor.js";import { textResponse } from "../../utils/response.js";import { validateAbsolutePath } from "../../utils/validation.js";import type { Environment } from "../../types.js";interface MyToolParams {requiredParam: string;optionalParam?: string;}export async function myTool(params: MyToolParams,env: Environment,) {validateAbsolutePath(params.requiredParam);const args = ["my-command", params.requiredParam];if (params.optionalParam) {args.push("--flag", params.optionalParam);}const result = await executeCommand(env.xcrunPath, args);if (!result.success) {return { content: [{ type: "text" as const, text: result.stderr }], isError: true };}return textResponse(result.stdout);} -
Register the tool in the category index:
src/tools/<category>/index.tsimport { z } from "zod";import { withErrorHandling } from "../../utils/response.js";import { myTool } from "./my-tool.js";// Add to the existing register function:server.tool("my_tool","Description of what the tool does",{requiredParam: z.string().describe("What this parameter is for"),optionalParam: z.string().optional().describe("Optional flag"),},withErrorHandling(async (params) => myTool(params, env)),); -
Write tests:
tests/tools/<category>.test.tsimport { describe, it, expect, beforeEach, vi } from "vitest";const { execFile } = vi.hoisted(() => ({execFile: vi.fn(),}));vi.mock("node:child_process", () => ({ execFile, spawn: vi.fn() }));import { myTool } from "../../src/tools/<category>/my-tool.js";const env = {xcodePath: "/Applications/Xcode.app/Contents/Developer",xcrunPath: "/usr/bin/xcrun",xcodebuildPath: "/usr/bin/xcodebuild",simctlAvailable: true,devicectlAvailable: true,};describe("myTool", () => {beforeEach(() => vi.clearAllMocks());it("executes the command correctly", async () => {execFile.mockImplementation((_c, _a, _o, cb) => {cb(null, "expected output", "");});const result = await myTool({ requiredParam: "/path/to/file" }, env);expect(result.content[0].text).toContain("expected output");});it("validates absolute path", async () => {await expect(myTool({ requiredParam: "relative/path" }, env),).rejects.toThrow();});}); -
Run the quality gate:
Terminal window npm run checkAll tests must pass, no lint errors, no type errors, formatting correct.
Coding Standards
Section titled “Coding Standards”TypeScript
Section titled “TypeScript”- Strict mode enabled — no
any, no implicitundefined - ES modules with
.jsextensions in imports - Target ES2022 — modern JS features are fine
Naming Conventions
Section titled “Naming Conventions”| Item | Convention | Example |
|---|---|---|
| Tool names | snake_case | xcode_build, simulator_list |
| File names | kebab-case | build-settings.ts, log-stream.ts |
| Functions | camelCase | xcodeBuild, logStream |
| Interfaces | PascalCase | Environment, ExecResult |
| Constants | SCREAMING_SNAKE | MAX_BUFFER_SIZE |
Tool Descriptions
Section titled “Tool Descriptions”- Start with a verb: “Build”, “List”, “Run”, “Create”
- Be specific about what the tool does
- Mention the underlying CLI tool if relevant
- Keep it under 100 characters
Formatting
Section titled “Formatting”- Prettier handles all formatting
- 100 character line width
- Double quotes for strings
- Trailing commas everywhere
- 2-space indentation
Linting
Section titled “Linting”- ESLint with TypeScript plugin and Prettier integration
- No unused variables or imports
- No explicit
any - Pre-commit hooks enforce lint and format via Husky + lint-staged
Testing
Section titled “Testing”Framework
Section titled “Framework”- Vitest for all tests
- Mock
child_processusingvi.hoisted()+vi.mock() - Never call real Xcode tools in tests
What to Test
Section titled “What to Test”For each tool:
- Successful execution — correct command args, proper output parsing
- Input validation — invalid paths, missing required params
- Error handling — command failures, timeouts
- Edge cases — empty output, special characters
Coverage
Section titled “Coverage”Minimum 15% statement coverage is enforced by vitest config, but aim higher. The current test suite has 112 tests across 15 files.
Pull Request Guidelines
Section titled “Pull Request Guidelines”- One feature per PR — keep changes focused
- Include tests — all new tools must have test coverage
- Run
npm run check— must pass before submitting - Write a clear description — explain what and why
- Clean history — squash WIP commits before review
PR Checklist
Section titled “PR Checklist”- New tool has a handler file in
src/tools/<category>/ - Tool is registered in the category
index.tswith Zod schema - Tests are written in
tests/tools/<category>.test.ts -
npm run checkpasses (typecheck, lint, format, tests) - PR description explains the purpose and approach