--- name: "project-conventions" description: "Core conventions and patterns for ExcelMcp" --- ## Context ExcelMcp is a Windows-only toolset for programmatic Excel automation via COM interop, with TWO equal entry points: MCP Server (for AI assistants) and CLI (for scripting/agents). .NET/C# codebase. ## Patterns ### Exception Propagation (CRITICAL) Core commands NEVER wrap `batch.Execute()` in try-catch that returns error results. Let exceptions propagate naturally — `batch.Execute()` handles them via `TaskCompletionSource`. ```csharp // ✅ CORRECT return await batch.Execute((ctx, ct) => { // operation return ValueTask.FromResult(new OperationResult { Success = true }); }); // ❌ WRONG — double-wraps, loses stack context try { return await batch.Execute(...); } catch (Exception ex) { return new OperationResult { Success = false, ErrorMessage = ex.Message }; } ``` ### COM Object Cleanup ALL dynamic COM objects must be released in `finally` blocks using `ComUtilities.Release(ref obj!)`. NEVER use catch blocks to swallow exceptions. ### Success Flag Invariant `Success == true` ⟹ `ErrorMessage == null || ErrorMessage == ""`. Set Success in try block, always false in catch. ### Error Handling - Core Commands: Let exceptions propagate through batch.Execute() - MCP Server: Return JSON with `isError: true` for business errors; throw McpException for validation - CLI: Wrap Core calls in try-catch, display with `AnsiConsole.MarkupLine` - When evolving failure envelopes, preserve the legacy `error` field for compatibility and add richer diagnostics additively (`errorMessage`, `isError`, `exceptionType`, `hresult`, `innerError`) instead of renaming contracts in place. - If CLI/MCP parity needs richer diagnostics, add them to the shared `ServiceResponse` transport first so both entry points receive the same failure detail. ### MCP Schema Discoverability - For hand-written MCP tools, add `[Description("...")]` attributes to each exposed parameter that needs to appear in the published schema. - XML `/// ` comments are not enough by themselves for MCP client discoverability; verify the live schema with an integration test that calls `ListToolsAsync()`. - When fixing LLM discoverability bugs, preserve runtime aliases if needed, but make the canonical parameter pair explicit in both the parameter descriptions and the shared skill doc. ### Testing - Framework: xUnit with integration tests ONLY (no unit tests) - Test location: `tests/ExcelMcp.Core.Tests/`, `tests/ExcelMcp.McpServer.Tests/`, `tests/ExcelMcp.CLI.Tests/` - Run command: `dotnet test tests/ExcelMcp.Core.Tests --filter "Feature=&RunType!=OnDemand"` - TDD: Write test FIRST → RED → implement → GREEN - NEVER share test files between tests — each test creates unique files - ALWAYS verify actual Excel state, not just success flags (round-trip validation) - For range write bugs, test payload shape explicitly: rectangular wide writes, jagged rows, and create-sheet-then-write-non-A1 flows. Don’t infer COM limits from jagged input failures. - When hardening diagnostics regressions, centralize failure-envelope assertions in the shared test harness so every regression checks the same contract: `success=false`, `isError=true`, `error == errorMessage`, expected `exceptionType`, and scenario-specific presence/absence of `errorCategory`, `hresult`, and `innerError`. - For CLI parity tests, assert exit code `1` on business-error paths (for example, missing sheet or invalid input). If the setup command itself returns non-JSON stdout, treat that as startup/harness noise and surface raw stdout/stderr in the helper exception instead of misclassifying it as a contract failure. ### Bug Report Triage For Tests - Check the live tool surface before treating a report as a missing-feature bug; verify `ServiceAction` coverage and current MCP tool docs first. - Classify each report item before writing tests: regression in promised behavior, discoverability/documentation gap, or new feature request. - Regressions get exact failing workflow tests first at Core and MCP layers. - Existing capabilities with weak discoverability get positive end-to-end coverage before any API expansion. - New features get acceptance tests only after the public API shape is agreed. ### Code Style - Analyzer: `TreatWarningsAsErrors=true` with .NET analyzers - Naming: PascalCase for public, camelCase for params → auto-converts to snake_case in MCP - One public class per file, file name = class name - Partial classes for 15+ methods (split by feature domain) - No emojis in LLM-consumed content (XML docs, skill .md files) ### File Structure ``` src/ExcelMcp.ComInterop/ — COM patterns, STA threading, sessions src/ExcelMcp.Core/ — Excel business logic, commands src/ExcelMcp.Service/ — Session management, command routing src/ExcelMcp.McpServer/ — MCP protocol tools src/ExcelMcp.CLI/ — Command-line interface src/ExcelMcp.Generators*/ — Source generators tests/ — Integration tests (no unit tests) skills/shared/ — Single source of truth for docs/prompts ``` ### Formatting Surface Split Before classifying a formatting bug as missing functionality, check both `range` and `range_format`. - `range` owns value/formula-adjacent display formats such as `set-number-format` - `range_format` owns visual styling and layout actions such as `format-range`, `auto-fit-columns`, and `auto-fit-rows` If the capability already exists under one of those tools, treat it as a discoverability/API-shape issue first, not a backend feature gap. For real batching work, prefer existing list-of-objects patterns over ad hoc JSON blobs. `IRangeEditCommands.Sort(... List ...)` is a precedent that the shared surface can carry structured collections cleanly. ## Anti-Patterns - **Unit tests** — NEVER write unit tests. Integration tests only for COM interop. - **RefreshAll()** — NEVER use. Use individual `queryTable.Refresh(false)` (synchronous). - **Catch-and-swallow** — NEVER catch exceptions in Core commands to return error results. - **Dual test fixtures** — NEVER use both `IClassFixture` AND `[Collection("...")]`. - **Manual ScreenUpdating** — ExcelWriteGuard handles this automatically. - **Suppressing EnableEvents** — Data Model operations depend on them. - **Assuming Excel has a hidden range-width cap** — if `set-values` fails with `ArgumentOutOfRangeException`, inspect for jagged `List>` input before blaming COM. ## Triage Pattern Before assigning a bug to Core, check three things in order: 1. Existing integration coverage for the exact shape or a close analogue. 2. Whether the capability already exists under a different tool or action name. 3. Whether the failure is more likely in MCP/service argument binding, docs/skills discoverability, or true COM/Core behavior. Use this especially for reports that claim a hard product limit or a missing feature. Wide-range failures and formatting gaps are often mis-triaged when tests or tool surfaces already cover the scenario elsewhere.