Development Workflow

August 20, 2026 ยท View on GitHub

๐Ÿšจ IMPORTANT: All Changes Must Use Pull Requests

Direct commits to main are not allowed. All changes must go through the Pull Request (PR) process to ensure:

  • Code review and quality control
  • Proper version management
  • CI/CD validation
  • Documentation updates

๐Ÿ“‹ Standard Development Workflow

1. Create Feature Branch

# Create and switch to feature branch
git checkout -b feature/your-feature-name

# Or for bug fixes
git checkout -b fix/issue-description

# Or for documentation updates  
git checkout -b docs/update-description

2. Make Your Changes

# Make code changes, add tests, update docs
# Commit frequently with clear messages

git add .
git commit -m "Add feature X with tests and documentation

- Implement core functionality
- Add comprehensive unit tests  
- Update command documentation
- Include usage examples"

3. Push Feature Branch

# Push your feature branch to GitHub
git push origin feature/your-feature-name

4. Create Pull Request

  1. Go to GitHub Repository
  2. Click "New Pull Request"
  3. Select your feature branch
  4. Fill out the PR template:
    • Clear title describing the change
    • Detailed description of what was changed and why
    • Testing information - what tests were added/run
    • Breaking changes - if any
    • Documentation updates - what docs were updated

5. PR Review Process

  • Automated checks will run (build, tests, linting)
  • Code review by maintainers
  • Address feedback if requested
  • Merge once approved and all checks pass

6. After Merge

# Switch back to main and pull latest
git checkout main
git pull origin main

# Delete the feature branch (cleanup)
git branch -d feature/your-feature-name
git push origin --delete feature/your-feature-name

๐Ÿท๏ธ Release Process

Creating a New Release

Only maintainers can create releases. The process is:

  1. Ensure all changes are merged to main via PRs

  2. Create and push a version tag:

# Create version tag (semantic versioning)
git tag v1.1.0

# Push the tag (triggers release workflow)
git push origin v1.1.0
  1. Automated Release Workflow:
    • โœ… Updates version numbers in project files
    • โœ… Builds the release binaries
    • โœ… Creates GitHub release with ZIP file
    • โœ… Updates release notes

Version Numbering

We follow Semantic Versioning:

  • Major (v2.0.0): Breaking changes
  • Minor (v1.1.0): New features, backward compatible
  • Patch (v1.0.1): Bug fixes, backward compatible

๐Ÿ”’ Branch Protection Rules

The main branch is protected with:

  • Require pull request reviews - Changes must be reviewed
  • Require status checks - CI/CD must pass
  • Require up-to-date branches - Must be current with main
  • No direct pushes - All changes via PR only

๐Ÿงช Testing Requirements & Organization

Three-Tier Test Architecture

PptMcp uses a production-ready three-tier testing approach with organized directory structure:

tests/
โ”œโ”€โ”€ PptMcp.Core.Tests/
โ”‚   โ”œโ”€โ”€ Unit/           # Fast tests, no PowerPoint required (~2-5 sec)
โ”‚   โ”œโ”€โ”€ Integration/    # Medium speed, requires PowerPoint (~1-15 min)
โ”‚   โ””โ”€โ”€ RoundTrip/      # Slow, comprehensive workflows (~3-10 min each)
โ”œโ”€โ”€ PptMcp.Diagnostics.Tests/
โ”‚   โ””โ”€โ”€ Integration/Diagnostics/ # Research tests, manual only (excluded from CI)
โ”œโ”€โ”€ PptMcp.McpServer.Tests/
โ”‚   โ”œโ”€โ”€ Unit/           # Fast tests, no server required  
โ”‚   โ”œโ”€โ”€ Integration/    # Medium speed, requires MCP server
โ”‚   โ””โ”€โ”€ RoundTrip/      # Slow, end-to-end protocol testing
โ””โ”€โ”€ PptMcp.CLI.Tests/
    โ”œโ”€โ”€ Unit/           # Fast tests, no PowerPoint required
    โ””โ”€โ”€ Integration/    # Medium speed, requires PowerPoint & CLI

Development Workflow Commands

During Development (Fast Feedback):

# Quick validation - run tests for specific feature
dotnet test --filter "Feature=Slide&RunType!=OnDemand"
dotnet test --filter "Feature=Shape&RunType!=OnDemand"

Before Commit (Comprehensive):

# Full local validation - runs in 10-20 minutes (excludes VBA)
dotnet test --filter "Category=Integration&RunType!=OnDemand&Feature!=VBA&Feature!=VBATrust"

Session/Batch Code Changes (MANDATORY):

# When modifying PptSession.cs or PptBatch.cs
dotnet test --filter "RunType=OnDemand"

Test Categories & Guidelines

โš ๏ธ No Unit Tests - See docs/ADR-001-NO-UNIT-TESTS.md for architectural rationale

Integration Tests (Category=Integration)

  • โœ… Test business logic with real PowerPoint COM interaction
  • โœ… Medium speed (10-20 minutes for full suite)
  • โœ… Requires PowerPoint installation
  • โœ… These ARE our unit tests (PowerPoint COM cannot be mocked)
  • โœ… Run specific features during development
  • โœ… Slow execution (3-10 minutes each)
  • โœ… Verifies actual PowerPoint state changes
  • โœ… Comprehensive scenario coverage

Adding New Tests

When creating tests, follow these placement guidelines:

// Unit Test Example
[Trait("Category", "Unit")]
[Trait("Speed", "Fast")]
[Trait("Layer", "Core")]
public class CommandLogicTests 
{
    // Tests business logic without PowerPoint
}

// Integration Test Example  
[Trait("Category", "Integration")]
[Trait("Speed", "Medium")]
[Trait("Feature", "Slide")]
[Trait("RequiresPowerPoint", "true")]
public class SlideCommandsTests
{
    // Tests single PowerPoint operations
}

// Round Trip Test Example
[Trait("Category", "RoundTrip")]
[Trait("Speed", "Slow")]
[Trait("Feature", "EndToEnd")]
[Trait("RequiresPowerPoint", "true")]
public class VbaWorkflowTests
{
    // Tests complete workflows: import โ†’ run โ†’ verify โ†’ export
}

PR Testing Requirements

Before creating a PR, ensure:

# Required - Integration tests pass (excludes VBA)
dotnet test --filter "Category=Integration&RunType!=OnDemand&Feature!=VBA&Feature!=VBATrust"

# Code builds without warnings
dotnet build -c Release

# Code follows style guidelines (automatic via EditorConfig)

For Complex Features:

  • โœ… Add integration tests for all PowerPoint operations
  • โœ… Test round-trip persistence (create โ†’ save โ†’ reload โ†’ verify)
  • โœ… Update documentation
  • โœ… No unit tests needed (see ADR-001-NO-UNIT-TESTS.md)

Source Agent Client (src\PptMcp.Agent)

The repository also contains an official Node-based source component for multi-phase orchestration on top of the MCP server.

Use this workflow when changing src\PptMcp.Agent\**:

dotnet build src\PptMcp.McpServer\PptMcp.McpServer.csproj -c Release

Set-Location src\PptMcp.Agent
npm install
npm run check
npm test

If the change affects end-to-end orchestration behavior, also run a real smoke scenario with node .\src\cli.mjs run --task ... on a Windows desktop with PowerPoint installed.

๐Ÿ”ง CLI Command Code Generation

Architecture Overview

The CLI uses Roslyn source generators to automatically generate command classes from Core's service definitions, ensuring 1:1 parity with MCP tools:

Core Generator (ServiceRegistryGenerator)
  โ†“
  Generates ServiceRegistry.{Category} classes
  Generates RouteFromSettings() bridge method
  Emits _CliCategoryMetadata manifest
  โ†“
CLI Generator (CliSettingsGenerator)  
  โ†“
  Reads 22 category manifest
  Generates 22 Command classes (inheriting ServiceCommandBase<T>)
  Generates CliCommandRegistration.RegisterCommands()
  โ†“
Program.cs calls CliCommandRegistration.RegisterCommands(config)

How It Works

1. Core Generator Output (ServiceRegistry.{Category}.g.cs):

  • Nested class CliSettings with all [Argument] properties
  • Method RouteFromSettings() that maps CliSettings โ†’ service command
  • Constants: CliCommandName, ValidActions, RequiresSession

2. CLI Generator (CliSettingsGenerator.cs):

  • Hard-coded list of 33 categories (Slide, Shape, Text, etc.)
  • For each category, generates command class:
    internal sealed class SheetCommand : ServiceCommandBase<ServiceRegistry.Sheet.CliSettings>
    {
        protected override string? GetSessionId(Settings s) => s.SessionId;
        protected override string? GetAction(Settings s) => s.Action;
        protected override IReadOnlyList<string> ValidActions => ServiceRegistry.Sheet.ValidActions;
        protected override (string, object?) Route(Settings s, string action) 
            => ServiceRegistry.Sheet.RouteFromSettings(action, s);
    }
    
  • Generates CliCommandRegistration.RegisterCommands():
    public static void RegisterCommands(IConfigurator config)
    {
        config.AddCommand<SlideCommand>("slide")
            .WithDescription(...);
        // ... 32 more commands
    }
    

Adding a New Command Category

When adding a new service category to Core:

  1. Add [ServiceCategory] interface in Core
  2. Update CliSettingsGenerator.cs - add tuple to the categories array:
    ("commandname", "RegistryClassName", requiresSession: true)
    
  3. Rebuild - generators automatically produce:
    • ServiceRegistry class in Core
    • Command class in CLI.Generated
    • Registration entry in CliCommandRegistration
  4. Test - verify pptcli COMMAND_NAME --help works

Why Hard-Coded Categories?

The categories are currently hard-coded in the CLI generator because:

Why NOT dynamic discovery via GetTypeByMetadataName?

  • Source generators can only see syntax in their own compilation
  • Core's generated types are compiled assembly references, not syntax
  • GetTypeByMetadataName cannot find types that aren't in the compilation being analyzed
  • Would require cross-assembly semantic analysis (not supported by Roslyn incremental generators)

Current approach (hard-coded list):

  • โœ… Works reliably across assembly boundaries
  • โœ… Simple and explicit
  • โœ… Zero runtime cost
  • โœ… Easy to verify (list = what exists in code)
  • โš ๏ธ Manual sync needed when Core adds new categories (but caught by build)

Future improvement: Could emit a manifest file from Core and parse it in CLI generator using source file inclusion.

For Complex Features:

  • โœ… Add integration tests for all PowerPoint operations
  • โœ… Test round-trip persistence (create โ†’ save โ†’ reload โ†’ verify)
  • โœ… Update documentation
  • โœ… No unit tests needed (see ADR-001-NO-UNIT-TESTS.md)

๐Ÿ“‹ MCP Server Configuration Management

CRITICAL: Keep server.json in Sync

When modifying MCP Server functionality, you must update src/PptMcp.McpServer/.mcp/server.json:

When to Update server.json:

  • โœ… Adding new MCP tools - Add tool definition to "tools" array
  • โœ… Modifying tool parameters - Update inputSchema and properties
  • โœ… Changing tool descriptions - Update description fields
  • โœ… Adding new capabilities - Update "capabilities" section
  • โœ… Changing requirements - Update "environment"."requirements"

server.json Synchronization Checklist:

# After making MCP Server code changes, verify:

# 1. Tool definitions match actual implementations
Compare-Object (Get-Content "src/PptMcp.McpServer/.mcp/server.json" | ConvertFrom-Json).tools (Get-ChildItem "src/PptMcp.McpServer/Tools/*.cs")

# 2. Build succeeds with updated configuration
dotnet build src/PptMcp.McpServer/PptMcp.McpServer.csproj

# 3. Test MCP server starts without errors
dnx PptMcp.McpServer --yes

server.json Structure:

{
  "version": "2.0.0",          // โ† Updated by release workflow
  "tools": [                   // โ† Must match Tools/*.cs implementations
    {
      "name": "file",    // โ† Must match [McpServerTool] attribute
      "description": "...",    // โ† Keep description accurate
      "inputSchema": {         // โ† Must match method parameters
        "properties": {
          "action": { ... },   // โ† Must match actual actions supported
          "filePath": { ... }   // โ† Must match parameter types
        }
      }
    }
  ]
}

Common server.json Update Scenarios:

  1. Adding New Tool:

    // In Tools/NewTool.cs
    [McpServerTool]
    public async Task<string> NewTool(string action, string parameter)
    
    // Add to server.json tools array
    {
      "name": "ppt_newtool",
      "description": "New functionality description",
      "inputSchema": { ... }
    }
    
  2. Adding Action to Existing Tool:

    // In existing tool method
    case "new-action":
      return HandleNewAction(parameter);
    
    // Update inputSchema properties.action enum
    "action": {
      "enum": ["list", "create", "new-action"]  // โ† Add new action
    }
    

๐Ÿ“ PR Template Checklist

When creating a PR, verify:

  • Code builds with zero warnings
  • All tests pass (unit tests minimum)
  • New features have tests
  • Documentation updated (README, etc.)
  • MCP server.json updated (if MCP Server changes) โ† NEW
  • Breaking changes documented
  • Follows existing code patterns
  • Commit messages are clear

๐Ÿšซ What NOT to Do

  • โŒ Don't commit directly to main
  • โŒ Don't create releases without PRs
  • โŒ Don't skip tests
  • โŒ Don't ignore build warnings
  • โŒ Don't update version numbers manually (release workflow handles this)

๐Ÿ’ก Tips for Good PRs

Commit Messages

โœ… Good: "Add slide transition batch command with error handling"
โŒ Bad: "fix stuff"

PR Titles

โœ… Good: "Add batch operations for slide transitions"
โŒ Bad: "Update code"

PR Size

  • Keep PRs focused - One feature/fix per PR
  • Break large changes into smaller, reviewable chunks
  • Include tests and docs in the same PR as the feature

๐Ÿ”ง Local Development Setup

# Clone the repository
git clone https://github.com/trsdn/mcp-server-ppt.git
cd PptMcp

# Install dependencies
dotnet restore

# Run all tests
dotnet test

# Build release version
dotnet build -c Release

# Test the built executable
.\src\PptMcp.CLI\bin\Release\net10.0\pptcli.exe --version

โœ‚๏ธ Trimming and Native AOT Compatibility

Why Trimming Is Not Supported

PptMcp cannot be trimmed due to fundamental architectural constraints of PowerPoint COM automation. The IL trimmer removes unused code at publish time, but PowerPoint COM interop requires dynamic code paths that the trimmer cannot statically analyze.

Technical Constraints

1. Runtime COM Activation

// This code CANNOT be trimmed - PowerPoint type comes from Windows Registry at runtime
Type? pptType = Type.GetTypeFromProgID("PowerPoint.Application");
dynamic ppt = Activator.CreateInstance(pptType)!;

The trimmer cannot know:

  • What types will be returned by GetTypeFromProgID (it's a Windows Registry lookup)
  • What members will be called on the dynamic object

2. Late-Bound COM Calls

// All PowerPoint operations use dynamic dispatch - the trimmer can't trace these calls
dynamic presentation = ppt.Presentations.Open(filePath);
dynamic slide = presentation.Slides.Item(1);
slide.Shapes[1].TextFrame.TextRange.Text = "Hello";

3. PowerPoint is External

  • PowerPoint is not a .NET assembly - it's an out-of-process COM server
  • The .NET runtime uses the Dynamic Language Runtime (DLR) for all PowerPoint calls
  • No static type information exists for the trimmer to analyze

What We DID Modernize

While the PowerPoint automation core cannot be trimmed, we modernized the OLE Message Filter to use .NET source-generated COM interop:

ComponentBeforeAfter
IOleMessageFilter[ComImport][GeneratedComInterface]
OleMessageFilterclass[GeneratedComClass] partial class
CoRegisterMessageFilter[DllImport][LibraryImport]

Benefits:

  • โœ… Compile-time marshalling code generation
  • โœ… No runtime IL stub generation for the message filter
  • โœ… Better diagnostics and debugging

Suppressed Warnings

The following warnings are suppressed in Directory.Build.props because they cannot be fixed:

WarningReason
IL2026Reflection/dynamic code incompatible with trimming
IL3050Code incompatible with Native AOT
CA1416Windows-only APIs (this is a Windows-only project)

Can We Ever Support Trimming?

No, unless one of these happens:

  1. PowerPoint exposes a .NET API - Microsoft would need to create a managed PowerPoint SDK
  2. We abandon COM - Would require a completely different architecture (file-based only, no live automation)
  3. PowerPoint is replaced - Use a different presentation engine with .NET bindings

The current architecture is the standard approach for PowerPoint automation in .NET and is used by thousands of applications. Trimming is simply not compatible with COM automation.

Alternatives for Smaller Binaries

If deployment size is a concern:

  • Use framework-dependent deployment (default) - smallest option (~15 MB)
  • The .NET runtime is typically already installed on Windows machines with PowerPoint
  • Self-contained deployment is only needed for isolated environments

๐Ÿ“ž Need Help?

  • Read the docs: Contributing Guide
  • Ask questions: Create a GitHub Issue with the question label
  • Report bugs: Use the bug report template

Remember: Every change, no matter how small, must go through a Pull Request!

This ensures code quality, proper testing, and maintains the project's reliability for all users.