From b03e9767fb48550bdf15b421ce30651b24ce8e2f Mon Sep 17 00:00:00 2001 From: Johannes Wachter Date: Fri, 9 Jan 2026 22:54:00 +0100 Subject: [PATCH 1/2] Refactor dependency injection and parameter handling - Improve service configuration with explicit service IDs - Make tool parameters nullable with proper validation - Remove unused MessageTruncator service registration - Update JunitXmlParser to use MessageTruncator with prefixes - Clean up PhpunitRunner constructor formatting --- config/config.php | 12 ++++-------- src/Capability/RunFileTool.php | 6 +++--- src/Capability/RunMethodTool.php | 11 +++++++++-- src/Parser/JunitXmlParser.php | 5 ++++- src/Runner/PhpunitRunner.php | 6 ++++-- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/config/config.php b/config/config.php index ba2418f..1dbd694 100644 --- a/config/config.php +++ b/config/config.php @@ -10,7 +10,6 @@ */ use MatesOfMate\Common\Process\ProcessExecutor; -use MatesOfMate\Common\Truncator\MessageTruncator; use MatesOfMate\PHPUnitExtension\Capability\ListTestsTool; use MatesOfMate\PHPUnitExtension\Capability\RunFileTool; use MatesOfMate\PHPUnitExtension\Capability\RunMethodTool; @@ -21,6 +20,7 @@ use MatesOfMate\PHPUnitExtension\Parser\JunitXmlParser; use MatesOfMate\PHPUnitExtension\Runner\PhpunitRunner; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; return static function (ContainerConfigurator $container): void { $services = $container->services() @@ -29,17 +29,13 @@ ->autoconfigure(); // Core infrastructure - $services->set(ProcessExecutor::class) + $services->set('matesofmate_phpunit.process_executor', ProcessExecutor::class) ->arg('$vendorPaths', ['%mate.root_dir%/vendor/bin/phpunit']); - $services->set(PhpunitRunner::class); + $services->set(PhpunitRunner::class) + ->arg('$executor', service('matesofmate_phpunit.process_executor')); $services->set(JunitXmlParser::class); $services->set(ToonFormatter::class); - $services->set(MessageTruncator::class) - ->arg('$prefixes', [ - 'Failed asserting that ', - 'Expectation failed for ', - ]); $services->set(ConfigurationDetector::class) ->arg('$projectRoot', '%mate.root_dir%'); diff --git a/src/Capability/RunFileTool.php b/src/Capability/RunFileTool.php index 9a79e13..039f5dc 100644 --- a/src/Capability/RunFileTool.php +++ b/src/Capability/RunFileTool.php @@ -39,13 +39,13 @@ public function __construct( description: 'Run PHPUnit tests from a specific file. Returns token-optimized TOON format. Available modes: "default" (summary + failures/errors), "summary" (just totals and status), "detailed" (full error messages without truncation). Use for: testing changes to a single test file, debugging specific test class, focused test execution.' )] public function execute( - string $file, + ?string $file = null, ?string $filter = null, bool $stopOnFailure = false, string $mode = 'default', ): string { - if (!file_exists($file)) { - throw new \InvalidArgumentException("Test file not found: {$file}"); + if (null === $file) { + throw new \InvalidArgumentException('The "file" parameter is required for phpunit-run-file tool.'); } $args = $this->buildPhpunitArgs( diff --git a/src/Capability/RunMethodTool.php b/src/Capability/RunMethodTool.php index 7cdd089..c3b7569 100644 --- a/src/Capability/RunMethodTool.php +++ b/src/Capability/RunMethodTool.php @@ -39,10 +39,17 @@ public function __construct( description: 'Run a single PHPUnit test method. Returns token-optimized TOON format. Available modes: "default" (summary + failure/error details), "summary" (just totals and status), "detailed" (full error messages without truncation). Use for: debugging a specific failing test, verifying a single test fix, isolated test execution.' )] public function execute( - string $class, - string $method, + ?string $class = null, + ?string $method = null, string $mode = 'default', ): string { + if (null === $class) { + throw new \InvalidArgumentException('The "class" parameter is required for phpunit-run-method tool.'); + } + if (null === $method) { + throw new \InvalidArgumentException('The "method" parameter is required for phpunit-run-method tool.'); + } + $filter = \sprintf('%s::%s$', preg_quote($class, '/'), preg_quote($method, '/')); $args = $this->buildPhpunitArgs(filter: $filter); diff --git a/src/Parser/JunitXmlParser.php b/src/Parser/JunitXmlParser.php index 331e3ca..1034b01 100644 --- a/src/Parser/JunitXmlParser.php +++ b/src/Parser/JunitXmlParser.php @@ -23,7 +23,10 @@ class JunitXmlParser { public function __construct( - private readonly MessageTruncator $truncator = new MessageTruncator(), + private readonly MessageTruncator $truncator = new MessageTruncator([ + 'Failed asserting that ', + 'Expectation failed for ', + ]), ) { } diff --git a/src/Runner/PhpunitRunner.php b/src/Runner/PhpunitRunner.php index 177342e..b6ac6aa 100644 --- a/src/Runner/PhpunitRunner.php +++ b/src/Runner/PhpunitRunner.php @@ -12,6 +12,7 @@ namespace MatesOfMate\PHPUnitExtension\Runner; use MatesOfMate\Common\Process\ProcessExecutor; +use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * Runs PHPUnit tests and generates JUnit XML output. @@ -22,8 +23,9 @@ */ class PhpunitRunner { - public function __construct(private readonly ProcessExecutor $executor) - { + public function __construct( + private readonly ProcessExecutor $executor, + ) { } /** From 9e53fda7c88233a721bb5d0862bbbe36e785cc0a Mon Sep 17 00:00:00 2001 From: Johannes Wachter Date: Sun, 11 Jan 2026 21:41:51 +0100 Subject: [PATCH 2/2] Add Schema attributes and fix MCP parameter handling - Make file, class, and method parameters optional with runtime validation - Add Schema attributes with descriptions and constraints - Add file path pattern validation (Test\.php$) - Add class name pattern validation for namespaced classes - Add method name pattern validation for test methods - Add mode enum validation for all tools - Improve MCP schema documentation for AI clients - Fix unused import in PhpunitRunner - Fix code style in config file --- AGENTS.md | 371 +++++++++++++++++-------------- CLAUDE.md | 370 +++++++++++------------------- README.md | 198 +---------------- config/config.php | 1 + src/Capability/RunFileTool.php | 15 ++ src/Capability/RunMethodTool.php | 13 ++ src/Capability/RunSuiteTool.php | 14 ++ src/Runner/PhpunitRunner.php | 1 - 8 files changed, 375 insertions(+), 608 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9d5790e..ed953d7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,94 +1,14 @@ # AGENTS.md -Guidelines for AI agents helping users create Symfony AI Mate extensions from this template. - -## Agent Role - -When assisting with this repository, you are helping developers **create new MCP extensions** for their frameworks, CMSs, or tools. This is a template repository - users will customize it for their needs. - -## Key Responsibilities - -### 1. Guide Template Customization -Help users replace placeholder content: -- Replace `Example`/`ExampleExtension` with their framework name -- Update `composer.json` package name to `matesofmate/{framework}-extension` -- Update namespace from `MatesOfMate\ExampleExtension\` to `MatesOfMate\{Framework}Extension\` -- Replace `@your-username` in CODEOWNERS with actual GitHub username - -### 2. Tool/Resource Development -Assist with creating MCP capabilities: -- Tools: Executable actions marked with `#[McpTool]` -- Resources: Static context data marked with `#[McpResource]` -- Service registration in `config/services.php` -- Comprehensive tests in `tests/Capability/` - -### 3. Quality Assurance -Ensure code meets standards: -- Run `composer lint` before commits -- Run `composer test` to verify functionality -- Check that all examples use `\JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT` -- Verify proper file headers are present - -### 4. Documentation Support -Help maintain clear documentation: -- Update README.md with framework-specific installation steps -- Document tool capabilities and when AI should use them -- Provide usage examples for end users - -## Template-Specific Standards - -### Code Style Conventions -- ✅ **No** `declare(strict_types=1)` - Omitted by design -- ✅ **No** `final` classes - Allow extensibility -- ✅ All JSON encoding uses `\JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT` -- ✅ File headers include MatesOfMate copyright - -### Tool Implementation Checklist -When creating new tools: -- [ ] Clear, descriptive `#[McpTool]` name: `{framework}-{action}` -- [ ] Helpful description explaining when AI should use it -- [ ] Returns JSON string for structured data -- [ ] Registered in `config/services.php` -- [ ] Has corresponding test in `tests/Capability/` -- [ ] Test validates JSON output structure - -### Resource Implementation Checklist -When creating new resources: -- [ ] Custom URI scheme: `{framework}://path` -- [ ] Descriptive name: `{framework}_{name}` -- [ ] Returns array with `uri`, `mimeType`, `text` keys -- [ ] `text` value is JSON string (for JSON mimeType) -- [ ] Registered in `config/services.php` -- [ ] Has corresponding test validating structure - -## Workflow Guidelines - -### When User Starts New Extension -1. Confirm framework/CMS they're building for -2. Help search/replace all `Example` references -3. Update `composer.json` with correct package name -4. Guide CODEOWNERS update -5. Ensure tests pass: `composer test && composer lint` - -### When Adding New Tools -1. Discuss tool purpose and when AI should use it -2. Create class in `src/Capability/` -3. Add `#[McpTool]` attribute with clear description -4. Implement method returning JSON -5. Register in `config/services.php` -6. Create test validating behavior -7. Run quality checks - -### When Adding New Resources -1. Identify what static context would be helpful -2. Create class in `src/Capability/` -3. Add `#[McpResource]` attribute with URI and name -4. Implement method returning proper structure -5. Register in `config/services.php` -6. Create test validating return structure -7. Run quality checks - -## Development Commands Reference +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +PHPUnit extension for Symfony AI Mate providing AI assistants with token-optimized test execution tools. This extension executes PHPUnit tests and returns results in TOON (Token-Oriented Object Notation) format, achieving 40-50% token reduction compared to raw PHPUnit output. + +## Common Commands + +### Development Workflow ```bash # Install dependencies @@ -97,112 +17,217 @@ composer install # Run all tests composer test -# Run tests with coverage -composer test -- --coverage-html coverage/ +# Run specific test +vendor/bin/phpunit tests/Unit/Capability/RunSuiteToolTest.php +vendor/bin/phpunit --filter testExecute -# Check all quality tools +# Check code quality (validates composer.json, runs Rector, PHP CS Fixer, PHPStan) composer lint -# Auto-fix code style and refactoring +# Auto-fix code style and apply automated refactorings composer fix +``` -# Individual tools +### Individual Quality Tools + +```bash +# PHP CS Fixer (code style) +vendor/bin/php-cs-fixer fix --dry-run --diff # Check only +vendor/bin/php-cs-fixer fix # Apply fixes + +# PHPStan (static analysis at level 8) vendor/bin/phpstan analyse -vendor/bin/php-cs-fixer fix --dry-run --diff -vendor/bin/rector process --dry-run -vendor/bin/phpunit tests/Capability/SpecificTest.php -``` -## Common Mistakes to Prevent - -### ❌ Don't -- Don't add `declare(strict_types=1)` to PHP files -- Don't make classes `final` -- Don't use `json_encode()` without error flags -- Don't forget to register new capabilities in `config/services.php` -- Don't skip tests -- Don't use generic tool descriptions like "A tool for doing things" - -### ✅ Do -- Keep classes extensible (non-final) -- Use `\JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT` for JSON encoding -- Write specific, actionable tool descriptions -- Register all capabilities in service container -- Test all tools and resources -- Run `composer lint` before committing - -## Communication Style - -- **Practical and hands-on** - Show code examples -- **Encouraging** - Building MCP extensions is new for many -- **Specific** - Refer to exact file paths and line numbers -- **Quality-focused** - Remind about running tests and linting - -## Before Publishing Checklist - -Help users verify before publishing their extension: -- [ ] All `Example`/`ExampleExtension` references replaced -- [ ] `composer.json` has correct package name -- [ ] CODEOWNERS updated with real GitHub username -- [ ] README.md has framework-specific documentation -- [ ] All tools have clear descriptions -- [ ] All tests pass: `composer test` -- [ ] All quality checks pass: `composer lint` -- [ ] LICENSE file updated with correct name/org -- [ ] Tagged release (e.g., `v0.1.0`) -- [ ] Submitted to Packagist - -## Commit Message Guidelines - -**CRITICAL**: Never include AI attribution in commit messages. - -### Format +# Rector (automated refactoring to PHP 8.2) +vendor/bin/rector process --dry-run # Preview changes +vendor/bin/rector process # Apply changes ``` -Short descriptive summary -- Conceptual change or improvement -- Another concept addressed -- Additional improvements made -``` +## Architecture -### Rules -- ❌ **NEVER** add "Co-Authored-By: Claude" or similar AI attribution -- ❌ **NEVER** mention "coded by claude-code" or AI assistance -- ✅ Describe CONCEPTS and improvements, not file names -- ✅ Use natural language explaining what changed -- ✅ Keep summary under 50 characters -- ✅ Focus on WHY and WHAT, not technical details +### Component Structure -### Good Examples -``` -Add entity relationship discovery +**MCP Tools** (`src/Capability/`): +- `RunSuiteTool` - Execute full PHPUnit test suite +- `RunFileTool` - Execute tests in specific file +- `RunMethodTool` - Execute single test method +- `ListTestsTool` - Discover all available tests in project +- `BuildsPhpunitArguments` (trait) - Shared argument building logic + +**Core Services**: +- `Runner/PhpunitRunner` - Executes PHPUnit with JUnit XML logging via ProcessExecutor +- `Parser/JunitXmlParser` - Parses JUnit XML into structured TestResult +- `Parser/TestResult` - DTO containing test counts, failures, errors, warnings, time +- `Formatter/ToonFormatter` - Converts results to TOON format with multiple modes +- `Config/ConfigurationDetector` - Auto-detects phpunit.xml/phpunit.xml.dist +- `Discovery/TestDiscovery` - Finds test files and methods using Symfony Finder + +### Data Flow -- Enable AI to understand entity associations -- Include bidirectional relationship mapping -- Provide inverse side information ``` +Tool → PhpunitRunner → ProcessExecutor (common package) + ↓ + PHPUnit CLI with --log-junit + ↓ + JunitXmlParser → TestResult + ↓ + ToonFormatter → TOON output +``` + +### Output Modes + +The ToonFormatter supports five output modes: +- `default` - Summary + failures/errors with truncated messages (~40-50% token reduction) +- `summary` - Just totals and status (tests, passed, failed, errors, time) +- `detailed` - Full error messages without truncation +- `by-file` - Errors grouped by file path (basename) +- `by-class` - Errors grouped by test class (short class name) + +### Common Package Integration + +Uses `matesofmate/common` package for shared functionality: + +**ProcessExecutor** - CLI tool execution with PHP binary reuse +- Configured with vendor path: `%mate.root_dir%/vendor/bin/phpunit` +- Default timeout: 300 seconds +- Always uses `--log-junit` to generate JUnit XML output + +**ConfigurationDetector** - Auto-detects config files in order: +1. phpunit.xml +2. phpunit.xml.dist +### Service Registration + +All services registered in `config/config.php` with: +- Autowiring enabled +- Autoconfiguration enabled (discovers #[McpTool] attributes) +- Custom process executor with vendor path injection +- Project root parameter injection for configuration detection and test discovery + +## Code Quality Standards + +### PHP Requirements +- PHP 8.2+ minimum +- No `declare(strict_types=1)` by convention +- No final classes (extensibility) +- JSON encoding: Always use `\JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT` + +### Quality Tools Configuration +- **PHPStan**: Level 8, includes phpstan-phpunit extension +- **PHP CS Fixer**: `@Symfony` + `@Symfony:risky` rulesets with ordered class elements +- **Rector**: PHP 8.2, code quality, dead code removal, early return, type declarations +- **PHPUnit**: Version 10.0+ + +### File Header Template + +All PHP files must include: +```php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ ``` -Improve tool error handling -- Add graceful degradation for missing dependencies -- Provide actionable error messages -- Include recovery suggestions +### DocBlock Annotations + +**@author annotation**: Required on all class-level DocBlocks: +```php +/** + * Description of the class. + * + * @author Johannes Wachter + */ +class YourClass ``` -### Bad Examples +**@internal annotation**: Mark implementation details not for external use: +```php +/** + * Internal parser for JUnit XML output. + * + * @internal + * @author Johannes Wachter + */ +class JunitXmlParser ``` -Update ExampleTool.php -Co-Authored-By: Claude Code +Use @internal for: +- Parser, formatter, runner classes +- Helper traits +- Internal DTOs (RunResult, TestResult) +- Classes not intended for extension consumers + +## Discovery Mechanism + +Symfony AI Mate auto-discovers tools via `composer.json`: + +```json +{ + "extra": { + "ai-mate": { + "scan-dirs": ["src/Capability"], + "includes": ["config/config.php"] + } + } +} ``` +## Testing Philosophy + +### Test Structure +- Tests mirror `src/` structure in `tests/Unit/` +- Extend `PHPUnit\Framework\TestCase` +- Test method names: `testExecute`, `testFormatDefault`, `testParseJunitXml`, etc. + +### Key Testing Areas +- Tool parameter validation (configuration paths, filter patterns, stop-on-failure flags) +- JUnit XML parsing correctness +- TOON format output validation +- Configuration detection logic +- Test discovery functionality + +### Integration Testing +- Service registration and dependency injection +- Attribute-based discovery (#[McpTool]) +- Process executor integration with common package + +## Common Development Patterns + +### Adding New Tools + +1. Create tool class in `src/Capability/` with `#[McpTool]` attribute +2. Inject required services via constructor (PhpunitRunner, parsers, formatters) +3. Use `BuildsPhpunitArguments` trait if needed for argument construction +4. Register service in `config/config.php` +5. Add corresponding test in `tests/Unit/Capability/` + +### Adding New Output Modes + +1. Add mode to enum in `#[Schema]` attribute on tool parameters +2. Implement format method in `ToonFormatter` (e.g., `formatCustomMode()`) +3. Add match arm in `ToonFormatter::format()` method +4. Add test case in `ToonFormatterTest` + +## Commit Message Convention + +Keep commit messages clean without AI attribution. + +**Format:** ``` -Add new feature - coded by claude-code +Short summary (50 chars or less) + +- Conceptual change description +- Another concept or improvement ``` -### When Creating Commits for Users -Always help users create clean commit messages that: -- Explain what conceptually changed -- Avoid technical file paths or implementation details -- Never include AI attribution or mentions +**Rules:** +- ❌ NO AI attribution (no "Co-Authored-By: Claude", etc.) +- ✅ Short, descriptive summary line +- ✅ Bullet list describing concepts/improvements +- ✅ Focus on the WHY and WHAT diff --git a/CLAUDE.md b/CLAUDE.md index f01728a..7e9ef72 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,63 +4,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -This is a **PHPUnit extension for symfony/ai-mate** that provides token-optimized testing tools for AI assistants. +PHPUnit extension for Symfony AI Mate providing AI assistants with token-optimized test execution tools. This extension executes PHPUnit tests and returns results in TOON (Token-Oriented Object Notation) format, achieving 40-50% token reduction compared to raw PHPUnit output. -**Key Features:** -- TOON (Token-Oriented Object Notation) format for 40-50% token reduction vs. raw PHPUnit output -- Test execution via Symfony Process component using current PHP binary -- JUnit XML parsing for structured results -- Auto-detection of PHPUnit configuration files - -**Core Components:** -- **Tools** (`src/Capability/`): MCP tools for running tests and listing tests -- **Runner** (`src/Runner/`): PHPUnit process execution with PHP binary detection -- **Parser** (`src/Parser/`): JUnit XML parsing into structured data -- **Formatter** (`src/Formatter/`): TOON output formatting using helgesverre/toon library -- **Discovery** (`src/Discovery/`): Test file and method discovery -- **Config** (`src/Config/`): PHPUnit configuration detection - -**Available Tools:** -- `phpunit_run_suite` - Run entire test suite with optional filtering -- `phpunit_run_file` - Run tests from a specific file -- `phpunit_run_method` - Run a single test method -- `phpunit_list_tests` - List all available tests in the project - -## Response Format - -All tools return **raw TOON-formatted strings** for maximum token efficiency: - -``` -summary{tests,passed,failed,errors,time}: -100|95|5|0|12.4s - -status: FAILED - -failures[5]{class,method,message,file,line}: -UserTest|testCreate|Expected 200 got 401|UserTest.php|45 -... -``` - -## Formatter Modes - -Tools support multiple output modes via the `mode` parameter: - -- **`toon` (default)**: Compact token-optimized format (~40-50% token reduction) -- **`summary`**: Just totals and status (ultra-compact) -- **`detailed`**: Full error details without truncation -- **`by-file`**: Errors grouped by file path -- **`by-class`**: Errors grouped by test class - -**Example**: -```php -phpunit_run_suite(mode: 'summary') // Just totals -phpunit_run_suite(mode: 'detailed') // Full details -phpunit_run_suite(mode: 'by-file') // Grouped by file -``` - -## Essential Commands +## Common Commands ### Development Workflow + ```bash # Install dependencies composer install @@ -68,8 +17,9 @@ composer install # Run all tests composer test -# Run tests with coverage report -composer test -- --coverage-html coverage/ +# Run specific test +vendor/bin/phpunit tests/Unit/Capability/RunSuiteToolTest.php +vendor/bin/phpunit --filter testExecute # Check code quality (validates composer.json, runs Rector, PHP CS Fixer, PHPStan) composer lint @@ -79,6 +29,7 @@ composer fix ``` ### Individual Quality Tools + ```bash # PHP CS Fixer (code style) vendor/bin/php-cs-fixer fix --dry-run --diff # Check only @@ -88,166 +39,88 @@ vendor/bin/php-cs-fixer fix # Apply fixes vendor/bin/phpstan analyse # Rector (automated refactoring to PHP 8.2) -vendor/bin/rector process --dry-run # Preview changes -vendor/bin/rector process # Apply changes - -# PHPUnit (run specific test) -vendor/bin/phpunit tests/Capability/ExampleToolTest.php -vendor/bin/phpunit --filter testMethodName +vendor/bin/rector process --dry-run # Preview changes +vendor/bin/rector process # Apply changes ``` ## Architecture -### Core Concepts +### Component Structure -**Tools vs Resources:** -- **Tools** (`#[McpTool]`): Executable actions invoked by AI (e.g., list entities, analyze code) -- **Resources** (`#[McpResource]`): Static/semi-static data provided to AI (e.g., configuration, routes) +**MCP Tools** (`src/Capability/`): +- `RunSuiteTool` - Execute full PHPUnit test suite +- `RunFileTool` - Execute tests in specific file +- `RunMethodTool` - Execute single test method +- `ListTestsTool` - Discover all available tests in project +- `BuildsPhpunitArguments` (trait) - Shared argument building logic -**Discovery Mechanism:** -The `extra.ai-mate` section in `composer.json` defines: -- `scan-dirs`: Directories to scan for `#[McpTool]` and `#[McpResource]` attributes -- `includes`: Service configuration files to load +**Core Services**: +- `Runner/PhpunitRunner` - Executes PHPUnit with JUnit XML logging via ProcessExecutor +- `Parser/JunitXmlParser` - Parses JUnit XML into structured TestResult +- `Parser/TestResult` - DTO containing test counts, failures, errors, warnings, time +- `Formatter/ToonFormatter` - Converts results to TOON format with multiple modes +- `Config/ConfigurationDetector` - Auto-detects phpunit.xml/phpunit.xml.dist +- `Discovery/TestDiscovery` - Finds test files and methods using Symfony Finder -### Directory Structure +### Data Flow ``` -src/Capability/ # All tools and resources go here -config/services.php # Symfony DI configuration for registering capabilities -tests/Capability/ # Tests mirror src/Capability/ structure +Tool → PhpunitRunner → ProcessExecutor (common package) + ↓ + PHPUnit CLI with --log-junit + ↓ + JunitXmlParser → TestResult + ↓ + ToonFormatter → TOON output ``` -### Service Registration Pattern +### Output Modes -In `config/services.php`: -```php -$services = $container->services() - ->defaults() - ->autowire() // Auto-inject dependencies - ->autoconfigure(); // Auto-register MCP attributes +The ToonFormatter supports five output modes: +- `default` - Summary + failures/errors with truncated messages (~40-50% token reduction) +- `summary` - Just totals and status (tests, passed, failed, errors, time) +- `detailed` - Full error messages without truncation +- `by-file` - Errors grouped by file path (basename) +- `by-class` - Errors grouped by test class (short class name) -$services->set(YourTool::class); -``` - -All classes in `src/Capability/` with `#[McpTool]` or `#[McpResource]` attributes are automatically discovered if registered as services. +### Common Package Integration -### Tool Implementation Pattern +Uses `matesofmate/common` package for shared functionality: -```php -use Mcp\Capability\Attribute\McpTool; - -class YourTool -{ - public function __construct( - private readonly SomeService $service, - ) { - } +**ProcessExecutor** - CLI tool execution with PHP binary reuse +- Configured with vendor path: `%mate.root_dir%/vendor/bin/phpunit` +- Default timeout: 300 seconds +- Always uses `--log-junit` to generate JUnit XML output - #[McpTool( - name: 'framework-action-name', // Format: {framework}-{action} - description: 'Precise description of when AI should use this tool' - )] - public function execute(string $param): string - { - // Return JSON for structured data - return json_encode($result, \JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT); - } -} -``` +**ConfigurationDetector** - Auto-detects config files in order: +1. phpunit.xml +2. phpunit.xml.dist -**Key points:** -- Tool names use lowercase with hyphens: `example-list-entities` -- Descriptions are critical - AI uses them to decide when to invoke tools -- Return JSON strings for structured data -- Use constructor injection for dependencies +### Service Registration -### Resource Implementation Pattern - -```php -use Mcp\Capability\Attribute\McpResource; - -class YourResource -{ - #[McpResource( - uri: 'myframework://config', // Custom URI scheme - name: 'framework_config', - mimeType: 'application/json' - )] - public function getConfig(): array - { - return [ - 'uri' => 'myframework://config', - 'mimeType' => 'application/json', - 'text' => json_encode($data, \JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT), - ]; - } -} -``` - -**Key points:** -- Must return array with `uri`, `mimeType`, and `text` keys -- URI uses custom scheme (e.g., `example://`, `symfony://`) -- Typically return `application/json` or `text/plain` +All services registered in `config/config.php` with: +- Autowiring enabled +- Autoconfiguration enabled (discovers #[McpTool] attributes) +- Custom process executor with vendor path injection +- Project root parameter injection for configuration detection and test discovery ## Code Quality Standards -### Important Design Decisions - -⚠️ **Template-specific conventions** (users can customize when creating their extensions): - -- **No strict types declarations** - All PHP files omit `declare(strict_types=1)` by design -- **No final classes** - All classes are non-final to allow extensibility -- **JSON error handling** - Always use `\JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT` with `json_encode()` +### PHP Requirements +- PHP 8.2+ minimum +- No `declare(strict_types=1)` by convention +- No final classes (extensibility) +- JSON encoding: Always use `\JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT` -### PHP CS Fixer Configuration -- Follows `@Symfony` ruleset with risky rules enabled -- Enforces specific class element ordering (traits → constants → properties → methods) -- Requires MatesOfMate organisation header comment -- Uses parallel processing for performance -- Excludes only `var/` and `vendor/` directories +### Quality Tools Configuration +- **PHPStan**: Level 8, includes phpstan-phpunit extension +- **PHP CS Fixer**: `@Symfony` + `@Symfony:risky` rulesets with ordered class elements +- **Rector**: PHP 8.2, code quality, dead code removal, early return, type declarations +- **PHPUnit**: Version 10.0+ -### PHPStan Configuration -- **Level 8** (maximum strictness) -- Analyzes both `src/` and `tests/` -- PHPDoc types are not treated as certain (forces proper type declarations) -- PHPUnit extension enabled -- Empty `ignoreErrors` section available for adding exceptions - -### Rector Configuration -- Targets **PHP 8.2+** -- Applies: UP_TO_PHP_82, code quality, dead code removal, early return, type declarations -- PHPUnit 10.0 rules enabled - -## Testing Conventions - -- Tests live in `tests/` mirroring `src/` structure -- Extend `PHPUnit\Framework\TestCase` -- Use descriptive test method names: `testReturnsValidJson`, `testContainsExpectedKeys` -- Test JSON output validity and structure for tools -- Test return array structure for resources - -## CI/CD - -GitHub Actions workflow (`.github/workflows/ci.yml`) runs automatically: -- **Lint job**: Validates composer.json, runs Rector, PHP CS Fixer, PHPStan -- **Test job**: Runs PHPUnit on PHP 8.2 and 8.3 - -## When Creating New Extensions - -1. Replace all `example`/`Example`/`ExampleExtension` references with your framework name -2. Update `composer.json` package name to `matesofmate/{framework}-extension` and description -3. Update `.github/CODEOWNERS` - replace `@your-username` with your GitHub handle (keep `@wachterjohannes`) -4. Create tools in `src/Capability/` with clear, descriptive tool names and descriptions -5. Register services in `config/services.php` -6. Write tests in `tests/Capability/` covering tool/resource behavior -7. Update README.md with framework-specific installation and usage instructions -8. Ensure all quality checks pass: `composer lint && composer test` -9. Tag release (e.g., `v0.1.0`) and submit to Packagist - -## File Header Template - -All PHP files must include this copyright header: +### File Header Template +All PHP files must include: ```php + * @author Johannes Wachter */ -class RunSuiteTool -{ -} +class YourClass ``` -**@internal annotation**: Use @internal for classes, methods, or properties that are implementation details and should not be used by extension consumers: +**@internal annotation**: Mark implementation details not for external use: ```php /** - * Parses JUnit XML output into structured data. + * Internal parser for JUnit XML output. * * @internal - * @author Your Name + * @author Johannes Wachter */ class JunitXmlParser -{ -} ``` Use @internal for: -- Implementation detail classes (parsers, formatters, internal DTOs) -- Private helper methods exposed for testing -- Framework-specific adapters -- Classes in `src/` subdirectories not meant for direct use +- Parser, formatter, runner classes +- Helper traits +- Internal DTOs (RunResult, TestResult) +- Classes not intended for extension consumers -## Commit Message Convention +## Discovery Mechanism -**Important**: Keep commit messages clean without AI attribution. +Symfony AI Mate auto-discovers tools via `composer.json`: -**Format**: +```json +{ + "extra": { + "ai-mate": { + "scan-dirs": ["src/Capability"], + "includes": ["config/config.php"] + } + } +} ``` -Short summary (50 chars or less) -- Conceptual change description -- Another concept or improvement -- More changes as needed -``` +## Testing Philosophy -**✅ Good Examples**: -``` -Add Doctrine entity discovery tool +### Test Structure +- Tests mirror `src/` structure in `tests/Unit/` +- Extend `PHPUnit\Framework\TestCase` +- Test method names: `testExecute`, `testFormatDefault`, `testParseJunitXml`, etc. -- Enable AI to discover entity metadata -- Support association mapping queries -- Include field type information -``` +### Key Testing Areas +- Tool parameter validation (configuration paths, filter patterns, stop-on-failure flags) +- JUnit XML parsing correctness +- TOON format output validation +- Configuration detection logic +- Test discovery functionality -``` -Improve error handling for API tools +### Integration Testing +- Service registration and dependency injection +- Attribute-based discovery (#[McpTool]) +- Process executor integration with common package -- Add graceful degradation for missing services -- Provide helpful error messages -- Include recovery suggestions -``` +## Common Development Patterns -**❌ Bad Examples**: -``` -Update tool files +### Adding New Tools -Co-Authored-By: Claude Code -``` +1. Create tool class in `src/Capability/` with `#[McpTool]` attribute +2. Inject required services via constructor (PhpunitRunner, parsers, formatters) +3. Use `BuildsPhpunitArguments` trait if needed for argument construction +4. Register service in `config/config.php` +5. Add corresponding test in `tests/Unit/Capability/` +### Adding New Output Modes + +1. Add mode to enum in `#[Schema]` attribute on tool parameters +2. Implement format method in `ToonFormatter` (e.g., `formatCustomMode()`) +3. Add match arm in `ToonFormatter::format()` method +4. Add test case in `ToonFormatterTest` + +## Commit Message Convention + +Keep commit messages clean without AI attribution. + +**Format:** ``` -Implement features - coded by claude-code +Short summary (50 chars or less) + +- Conceptual change description +- Another concept or improvement ``` -**Rules**: -- ❌ NO AI attribution (no "Co-Authored-By: Claude", "coded by claude-code", etc.) +**Rules:** +- ❌ NO AI attribution (no "Co-Authored-By: Claude", etc.) - ✅ Short, descriptive summary line -- ✅ Bullet list describing concepts/improvements, not file names -- ✅ Natural language explaining what changed -- ✅ Focus on the WHY and WHAT, not technical details +- ✅ Bullet list describing concepts/improvements +- ✅ Focus on the WHY and WHAT diff --git a/README.md b/README.md index 6ade76c..8e528ed 100644 --- a/README.md +++ b/README.md @@ -15,185 +15,10 @@ Token-optimized PHPUnit testing tools for AI assistants. This extension provides ```bash composer require --dev matesofmate/phpunit-extension +vendor/bin/mate discover ``` -The extension is automatically discovered by Symfony AI Mate. - -## Available Tools - -### `phpunit_run_suite` - -Run the entire PHPUnit test suite. - -**Parameters:** -- `configuration` (optional): Path to phpunit.xml configuration file -- `filter` (optional): Filter pattern for test names -- `stopOnFailure` (optional): Stop execution on first failure - -**Examples:** -```php -// Run all tests -phpunit_run_suite() - -// Run with filter -phpunit_run_suite(filter: "UserTest") - -// Stop on first failure -phpunit_run_suite(stopOnFailure: true) - -// Custom configuration -phpunit_run_suite(configuration: "phpunit.custom.xml") -``` - -### `phpunit_run_file` - -Run PHPUnit tests from a specific file. - -**Parameters:** -- `file` (required): Path to test file -- `filter` (optional): Filter pattern for method names -- `stopOnFailure` (optional): Stop execution on first failure - -**Examples:** -```php -// Run all tests in file -phpunit_run_file("tests/Service/UserServiceTest.php") - -// Run specific method in file -phpunit_run_file("tests/Api/AuthTest.php", filter: "testLogin") - -// Stop on first failure -phpunit_run_file("tests/Unit/CalculatorTest.php", stopOnFailure: true) -``` - -### `phpunit_run_method` - -Run a single PHPUnit test method. - -**Parameters:** -- `class` (required): Fully qualified class name -- `method` (required): Test method name - -**Examples:** -```php -// Run specific test method -phpunit_run_method("App\\Tests\\UserServiceTest", "testCreateUser") - -// Run another test -phpunit_run_method("App\\Tests\\Api\\AuthTest", "testLoginWithValidCredentials") -``` - -### `phpunit_list_tests` - -List all available PHPUnit tests in the project. - -**Parameters:** -- `directory` (optional): Specific directory to scan (defaults to configured test directories) - -**Examples:** -```php -// List all tests -phpunit_list_tests() - -// List tests in specific directory -phpunit_list_tests(directory: "tests/Unit") -``` - -## Output Format (TOON) - -TOON (Token-Oriented Object Notation) provides minimal token usage while maintaining readability: - -**Successful run:** -``` -summary{tests,passed,failed,errors,warnings,skipped,time}: -42|42|0|0|0|0|1.234s - -status:OK -``` - -**Failed run:** -``` -summary{tests,passed,failed,errors,warnings,skipped,time}: -156|152|3|1|0|0|4.892s - -failures[3]{class,method,message,file,line}: -UserServiceTest|testCreateUser|Expected 200 got 401|UserServiceTest.php|45 -OrderTest|testCalculateTotal|99.99 !== 100.00|OrderTest.php|112 -PaymentTest|testRefund|Null returned|PaymentTest.php|78 - -errors[1]{class,method,exception,file,line}: -DatabaseTest|testConnection|PDOException: Connection refused|DatabaseTest.php|23 - -status:FAILED -``` - -**Test listing:** -``` -tests[4]{file,class,method}: -tests/UserTest.php|App\Tests\UserTest|testCreate -tests/UserTest.php|App\Tests\UserTest|testUpdate -tests/UserTest.php|App\Tests\UserTest|testDelete -tests/OrderTest.php|App\Tests\OrderTest|testCalculateTotal -``` - -### Token Efficiency - -Compared to standard JSON output: - -**JSON (186 tokens):** -```json -{ - "tests": 42, - "passed": 39, - "failed": 2, - "time": "1.234s", - "failures": [ - {"class": "UserTest", "method": "testCreate", "message": "Expected 200"} - ] -} -``` - -**TOON (~110 tokens - 41% reduction):** -``` -summary{tests,passed,failed,time}: -42|39|2|1.234s - -failures[1]{class,method,message}: -UserTest|testCreate|Expected 200 -``` - -## How It Works - -### Architecture - -The extension uses a layered architecture: - -1. **Runner Layer** - Executes PHPUnit via Symfony Process (uses current PHP binary) -2. **Parser Layer** - Extracts structured data from JUnit XML output -3. **Formatter Layer** - Converts results to TOON format using helgesverre/toon -4. **Tools Layer** - MCP tools with `#[McpTool]` attributes - -### Process Flow - -``` -User Request - ↓ -MCP Tool (RunSuiteTool, RunFileTool, etc.) - ↓ -PhpunitRunner (Symfony Process with current PHP binary + PHPUnit) - ↓ -JUnit XML Output - ↓ -JunitXmlParser (Extract failures, errors, summary) - ↓ -ToonFormatter (Convert to TOON format) - ↓ -Return to AI Assistant -``` - -### PHP Binary Detection - -The extension automatically uses the same PHP binary that's currently running (`PHP_BINARY`), ensuring consistency between your environment and test execution. +The extension is automatically enabled by Symfony AI Mate. ## Development @@ -210,22 +35,6 @@ composer lint composer fix ``` -### Testing - -The extension includes comprehensive tests: - -- Unit tests for all core components -- Integration tests for tool execution -- PHPStan level 8 compliance -- PHP CS Fixer code style enforcement -- Rector PHP 8.2+ modernization - -### CI/CD - -GitHub Actions automatically runs on every push and pull request: -- **Lint**: Validates composer.json, runs Rector, PHP CS Fixer, PHPStan -- **Test**: Runs PHPUnit on PHP 8.2 and 8.3 - ## Requirements - PHP 8.2 or higher @@ -234,7 +43,7 @@ GitHub Actions automatically runs on every push and pull request: ## Contributing -Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details. +Contributions are welcome! Please see [CONTRIBUTING.md](https://github.com/MatesOfMate/.github/blob/main/CONTRIBUTING.md) for details. ## License @@ -243,6 +52,7 @@ MIT License - see [LICENSE](LICENSE) file for details. ## Resources - [Symfony AI Mate Documentation](https://symfony.com/doc/current/ai/components/mate.html) +- [PHPUnit Documentation](https://phpunit.de/documentation.html) - [TOON Format Specification](https://github.com/HelgeSverre/toon-php) - [MatesOfMate Organization](https://github.com/matesofmate) diff --git a/config/config.php b/config/config.php index 1dbd694..dd5a450 100644 --- a/config/config.php +++ b/config/config.php @@ -20,6 +20,7 @@ use MatesOfMate\PHPUnitExtension\Parser\JunitXmlParser; use MatesOfMate\PHPUnitExtension\Runner\PhpunitRunner; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + use function Symfony\Component\DependencyInjection\Loader\Configurator\service; return static function (ContainerConfigurator $container): void { diff --git a/src/Capability/RunFileTool.php b/src/Capability/RunFileTool.php index 039f5dc..2d770b6 100644 --- a/src/Capability/RunFileTool.php +++ b/src/Capability/RunFileTool.php @@ -16,6 +16,7 @@ use MatesOfMate\PHPUnitExtension\Parser\JunitXmlParser; use MatesOfMate\PHPUnitExtension\Runner\PhpunitRunner; use Mcp\Capability\Attribute\McpTool; +use Mcp\Capability\Attribute\Schema; /** * Runs PHPUnit tests from a specific file with token-optimized output. @@ -39,9 +40,23 @@ public function __construct( description: 'Run PHPUnit tests from a specific file. Returns token-optimized TOON format. Available modes: "default" (summary + failures/errors), "summary" (just totals and status), "detailed" (full error messages without truncation). Use for: testing changes to a single test file, debugging specific test class, focused test execution.' )] public function execute( + #[Schema( + description: 'Path to the PHP test file (relative to project root)', + pattern: 'Test\.php$' + )] ?string $file = null, + #[Schema( + description: 'Filter pattern to match specific test names or methods' + )] ?string $filter = null, + #[Schema( + description: 'Stop execution upon first failure or error' + )] bool $stopOnFailure = false, + #[Schema( + description: 'Output format mode', + enum: ['default', 'summary', 'detailed'] + )] string $mode = 'default', ): string { if (null === $file) { diff --git a/src/Capability/RunMethodTool.php b/src/Capability/RunMethodTool.php index c3b7569..b1af1b1 100644 --- a/src/Capability/RunMethodTool.php +++ b/src/Capability/RunMethodTool.php @@ -16,6 +16,7 @@ use MatesOfMate\PHPUnitExtension\Parser\JunitXmlParser; use MatesOfMate\PHPUnitExtension\Runner\PhpunitRunner; use Mcp\Capability\Attribute\McpTool; +use Mcp\Capability\Attribute\Schema; /** * Runs a single PHPUnit test method with token-optimized output. @@ -39,8 +40,20 @@ public function __construct( description: 'Run a single PHPUnit test method. Returns token-optimized TOON format. Available modes: "default" (summary + failure/error details), "summary" (just totals and status), "detailed" (full error messages without truncation). Use for: debugging a specific failing test, verifying a single test fix, isolated test execution.' )] public function execute( + #[Schema( + description: 'Fully qualified test class name (e.g., "Tests\\Unit\\CalculatorTest")', + pattern: '^[a-zA-Z_\\x80-\\xff][a-zA-Z0-9_\\x80-\\xff\\\\]*$' + )] ?string $class = null, + #[Schema( + description: 'Test method name (e.g., "testAddition")', + pattern: '^test[a-zA-Z0-9_]+$|^[a-zA-Z_][a-zA-Z0-9_]*$' + )] ?string $method = null, + #[Schema( + description: 'Output format mode', + enum: ['default', 'summary', 'detailed'] + )] string $mode = 'default', ): string { if (null === $class) { diff --git a/src/Capability/RunSuiteTool.php b/src/Capability/RunSuiteTool.php index 3a85744..e067a44 100644 --- a/src/Capability/RunSuiteTool.php +++ b/src/Capability/RunSuiteTool.php @@ -16,6 +16,7 @@ use MatesOfMate\PHPUnitExtension\Parser\JunitXmlParser; use MatesOfMate\PHPUnitExtension\Runner\PhpunitRunner; use Mcp\Capability\Attribute\McpTool; +use Mcp\Capability\Attribute\Schema; /** * Runs the full PHPUnit test suite with token-optimized output. @@ -39,9 +40,22 @@ public function __construct( description: 'Run the full PHPUnit test suite. Returns token-optimized TOON format. Available modes: "default" (summary + failures/errors with truncated messages), "summary" (just totals and status), "detailed" (full error messages without truncation), "by-file" (errors grouped by file path), "by-class" (errors grouped by test class). Use for: running all tests, CI validation, checking overall test health.' )] public function execute( + #[Schema( + description: 'Path to PHPUnit configuration file (defaults to auto-detection)' + )] ?string $configuration = null, + #[Schema( + description: 'Filter pattern to match specific test names or methods' + )] ?string $filter = null, + #[Schema( + description: 'Stop execution upon first failure or error' + )] bool $stopOnFailure = false, + #[Schema( + description: 'Output format mode', + enum: ['default', 'summary', 'detailed', 'by-file', 'by-class'] + )] string $mode = 'default', ): string { $args = $this->buildPhpunitArgs( diff --git a/src/Runner/PhpunitRunner.php b/src/Runner/PhpunitRunner.php index b6ac6aa..4445c50 100644 --- a/src/Runner/PhpunitRunner.php +++ b/src/Runner/PhpunitRunner.php @@ -12,7 +12,6 @@ namespace MatesOfMate\PHPUnitExtension\Runner; use MatesOfMate\Common\Process\ProcessExecutor; -use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * Runs PHPUnit tests and generates JUnit XML output.