|
| 1 | +# Project Config + Session Defaults Plan |
| 2 | + |
| 3 | +## Goal |
| 4 | +Add a project-level config file at `.xcodebuildmcp/config.yaml` that: |
| 5 | +1. Seeds session defaults at server startup (no client call required). |
| 6 | +2. Allows `session-set-defaults` to persist provided defaults back to that config when a flag is set. |
| 7 | + |
| 8 | +Scope is limited to **cwd-only** resolution, **patch-only persistence** (provided keys only), and **warn+ignore** on invalid config. |
| 9 | + |
| 10 | +## Decisions (Confirmed) |
| 11 | +- Config location: **only** `process.cwd()/.xcodebuildmcp/config.yaml` (no find-up). |
| 12 | +- Persistence: **only** keys provided in the `session-set-defaults` call (plus necessary deletions for mutual exclusivity). |
| 13 | +- Invalid config: **warn and ignore**, continue startup. |
| 14 | + |
| 15 | +## Config Format |
| 16 | + |
| 17 | +Proposed YAML: |
| 18 | + |
| 19 | +```yaml |
| 20 | +schemaVersion: 1 |
| 21 | +sessionDefaults: |
| 22 | + projectPath: "./MyApp.xcodeproj" |
| 23 | + workspacePath: "./MyApp.xcworkspace" |
| 24 | + scheme: "MyApp" |
| 25 | + configuration: "Debug" |
| 26 | + simulatorName: "iPhone 16" |
| 27 | + simulatorId: "<UUID>" |
| 28 | + deviceId: "<UUID>" |
| 29 | + useLatestOS: true |
| 30 | + arch: "arm64" |
| 31 | + suppressWarnings: false |
| 32 | + derivedDataPath: "./.derivedData" |
| 33 | + preferXcodebuild: false |
| 34 | + platform: "iOS" |
| 35 | + bundleId: "com.example.myapp" |
| 36 | +``` |
| 37 | +
|
| 38 | +Notes: |
| 39 | +- `schemaVersion` supports future evolution. |
| 40 | +- The config file is **not** exclusive to session defaults; future sections (e.g., `server`, `logging`, `discovery`) are expected. |
| 41 | +- Relative paths resolve against the workspace root (cwd). |
| 42 | + |
| 43 | +## Precedence (Operational) |
| 44 | +We seed the in-memory session defaults from config at startup, so after boot it behaves like normal session defaults. |
| 45 | +Operationally, the only precedence that matters during tool calls is: |
| 46 | +1. Tool call args (existing behavior in `createSessionAwareTool`). |
| 47 | +2. In-memory session defaults (initially seeded from config; can be changed by `session-set-defaults`). |
| 48 | + |
| 49 | +## Implementation Plan |
| 50 | + |
| 51 | +### 1) New shared schema for session defaults |
| 52 | +**File:** `src/utils/session-defaults-schema.ts` |
| 53 | +- Define a Zod schema that mirrors `SessionDefaults`. |
| 54 | +- Used by both config loading and `session-set-defaults` to avoid drift. |
| 55 | + |
| 56 | +### 2) New project config loader/writer |
| 57 | +**File:** `src/utils/project-config.ts` |
| 58 | +Responsibilities: |
| 59 | +- Resolve config path: `path.join(cwd, '.xcodebuildmcp', 'config.yaml')`. |
| 60 | +- Read YAML via `FileSystemExecutor`. |
| 61 | +- Parse and validate with Zod. |
| 62 | +- **Allow unknown top-level keys** (use `.passthrough()` in Zod) so non-session sections can exist without failing validation. |
| 63 | +- Normalize mutual exclusivity: |
| 64 | + - If both `projectPath` and `workspacePath` are set, keep `workspacePath`. |
| 65 | + - If both `simulatorId` and `simulatorName` are set, keep `simulatorId`. |
| 66 | +- Resolve relative paths for: `projectPath`, `workspacePath`, `derivedDataPath`. |
| 67 | +- Persist changes when requested: |
| 68 | + - Merge provided keys into `sessionDefaults`. |
| 69 | + - Remove keys that were cleared due to exclusivity. |
| 70 | + - Overwrite YAML file (comments not preserved). |
| 71 | + |
| 72 | +Suggested API: |
| 73 | +```ts |
| 74 | +export type LoadProjectConfigOptions = { |
| 75 | + fs: FileSystemExecutor; |
| 76 | + cwd: string; |
| 77 | +}; |
| 78 | +
|
| 79 | +export type LoadProjectConfigResult = |
| 80 | + | { found: false } |
| 81 | + | { found: true; path: string; config: ProjectConfig; notices: string[] }; |
| 82 | +
|
| 83 | +export async function loadProjectConfig( |
| 84 | + options: LoadProjectConfigOptions, |
| 85 | +): Promise<LoadProjectConfigResult>; |
| 86 | +
|
| 87 | +export type PersistSessionDefaultsOptions = { |
| 88 | + fs: FileSystemExecutor; |
| 89 | + cwd: string; |
| 90 | + patch: Partial<SessionDefaults>; |
| 91 | + deleteKeys?: (keyof SessionDefaults)[]; |
| 92 | +}; |
| 93 | +
|
| 94 | +export async function persistSessionDefaultsToProjectConfig( |
| 95 | + options: PersistSessionDefaultsOptions, |
| 96 | +): Promise<{ path: string }>; |
| 97 | +``` |
| 98 | + |
| 99 | +### 3) Startup injection |
| 100 | +**File:** `src/server/bootstrap.ts` |
| 101 | +- Accept `fileSystemExecutor` and `cwd` in `BootstrapOptions` (default to `getDefaultFileSystemExecutor()` and `process.cwd()`). |
| 102 | +- Load project config at the top of `bootstrapServer()`. |
| 103 | +- On success: `sessionStore.setDefaults(normalizedDefaults)`. |
| 104 | +- On parse/validation error: log warning and continue. |
| 105 | + |
| 106 | +### 4) Persist flag in `session-set-defaults` |
| 107 | +**File:** `src/mcp/tools/session-management/session_set_defaults.ts` |
| 108 | +- Extend schema with `persist?: boolean`. |
| 109 | +- Use `createTypedToolWithContext` to access `{ fs, cwd }`. |
| 110 | +- Apply defaults to `sessionStore` as usual. |
| 111 | +- If `persist === true`, call `persistSessionDefaultsToProjectConfig()` with: |
| 112 | + - `patch`: only keys provided in the tool call (excluding `persist`). |
| 113 | + - `deleteKeys`: keys removed due to exclusivity rules. |
| 114 | +- Add a notice in response: `Persisted defaults to <path>`. |
| 115 | + |
| 116 | +### 5) Clear defaults key parity |
| 117 | +**File:** `src/mcp/tools/session-management/session_clear_defaults.ts` |
| 118 | +- Expand `keys` list to match full `SessionDefaults` surface. |
| 119 | + |
| 120 | +### 6) Documentation updates |
| 121 | +- Update `docs/SESSION_DEFAULTS.md` to mention config auto-load + `persist` flag. |
| 122 | +- Update tool description in `src/mcp/tools/session-management/index.ts`. |
| 123 | + |
| 124 | +### 7) Dependency |
| 125 | +- Add `yaml` package for parsing/serializing. |
| 126 | + |
| 127 | +## Tests |
| 128 | +- This change **must** be built TDD (red → green): write failing tests first, then implement code until tests pass. |
| 129 | +- Add unit tests for `project-config` loader and persistence using `createMockFileSystemExecutor`. |
| 130 | +- Update `session_set_defaults.test.ts` to cover `persist` path and mutual exclusivity deletions. |
| 131 | + |
| 132 | +## Risks / Notes |
| 133 | +- Overwriting YAML drops comments and custom formatting. |
| 134 | +- Explicitly **cwd-only** prevents automatic discovery from subdirectories. |
| 135 | +- Warn+ignore avoids startup failures but can hide misconfigurations; add clear log messaging. |
0 commit comments