#!/usr/bin/env php * * This file is part of a Moko Consulting project. * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: MokoStandards.Scripts.Validate * INGROUP: MokoStandards * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /validate/auto_detect_platform.php * BRIEF: Automatic platform detection and validation - PHP implementation */ declare(strict_types=1); require_once __DIR__ . '/../../vendor/autoload.php'; require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; use MokoEnterprise\{ CLIApp, ProjectTypeDetector, PluginFactory, PluginRegistry, AuditLogger, MetricsCollector }; /** * Automatic Platform Detection and Validation * * Detects whether a repository is a Joomla/WaaS component, Dolibarr/CRM module, * or generic repository, then validates against appropriate schema */ class AutoDetectPlatform extends CLIApp { private const DETECTION_THRESHOLD = 0.5; // 50% confidence required private ProjectTypeDetector $typeDetector; private PluginFactory $pluginFactory; private array $detectionResults = [ 'client' => ['score' => 0, 'indicators' => []], 'joomla' => ['score' => 0, 'indicators' => []], 'dolibarr' => ['score' => 0, 'indicators' => []], 'nodejs' => ['score' => 0, 'indicators' => []], 'python' => ['score' => 0, 'indicators' => []], 'terraform' => ['score' => 0, 'indicators' => []], 'wordpress' => ['score' => 0, 'indicators' => []], 'mobile' => ['score' => 0, 'indicators' => []], 'api' => ['score' => 0, 'indicators' => []], 'mcp-server' => ['score' => 0, 'indicators' => []], 'documentation' => ['score' => 0, 'indicators' => []], 'generic' => ['score' => 0, 'indicators' => []], ]; private string $detectedPlatform = 'generic'; private string $schemaFile = ''; private ?object $detectedPlugin = null; protected function setupArguments(): array { return [ 'repo-path:' => 'Path to repository to analyze (default: current directory)', 'schema-dir:' => 'Path to schema definitions directory (default: definitions/default)', 'output-dir:' => 'Directory for output reports (default: var/logs/validation)', ]; } protected function run(): int { $repoPath = $this->getOption('repo-path', '.'); $schemaDir = $this->getOption('schema-dir', 'definitions/default'); $outputDir = $this->getOption('output-dir', 'var/logs/validation'); // Make paths absolute $repoPath = $this->getAbsolutePath($repoPath); $schemaDir = $this->getAbsolutePath($schemaDir); $outputDir = $this->getAbsolutePath($outputDir); if (!is_dir($repoPath)) { $this->log("Repository path not found: {$repoPath}", 'ERROR'); return 3; } if (!is_dir($schemaDir)) { $this->log("Schema directory not found: {$schemaDir}", 'ERROR'); return 3; } $this->log("Analyzing repository: {$repoPath}", 'INFO'); // Initialize plugin system $logger = new AuditLogger('auto_detect_platform'); $metrics = new MetricsCollector(); $this->pluginFactory = new PluginFactory($logger, $metrics); $this->typeDetector = new ProjectTypeDetector($logger); // Use the new plugin system for detection $this->log("Using ProjectTypeDetector for platform detection", 'INFO'); $detectionResult = $this->typeDetector->detectProjectType($repoPath); if (!empty($detectionResult['type'])) { $this->detectedPlatform = $detectionResult['type']; $this->log("Detected platform via plugin system: {$this->detectedPlatform}", 'INFO'); // Try to get the plugin for this type $this->detectedPlugin = $this->pluginFactory->createForProject($repoPath); if ($this->detectedPlugin) { $this->log("Loaded plugin: {$this->detectedPlugin->getPluginName()}", 'INFO'); // Update detection results with plugin info $this->detectionResults[$this->detectedPlatform] = [ 'score' => $detectionResult['confidence'] ?? 1.0, 'indicators' => $detectionResult['indicators'] ?? [], ]; } } else { // Fallback to legacy detection if plugin system doesn't detect anything $this->log("Plugin system did not detect type, using legacy detection", 'WARNING'); // Run platform detection using legacy methods // Client must run BEFORE Joomla — client repos contain Joomla dirs // but are NOT Joomla extensions $this->detectClient($repoPath); $this->detectJoomla($repoPath); $this->detectDolibarr($repoPath); $this->detectNodeJS($repoPath); $this->detectPython($repoPath); $this->detectTerraform($repoPath); $this->detectWordPress($repoPath); $this->detectMobile($repoPath); $this->detectAPI($repoPath); $this->detectMcpServer($repoPath); // Determine platform $this->determinePlatform(); } // Map to schema file $this->schemaFile = $this->mapPlatformToSchema($schemaDir); if (!file_exists($this->schemaFile)) { $this->log("Schema file not found: {$this->schemaFile}", 'ERROR'); return 3; } // Output results if ($this->jsonOutput) { $this->outputJson(); } else { $this->displayResults(); } // Generate reports $this->generateReports($outputDir, $repoPath); $this->log("Platform detection completed: {$this->detectedPlatform}", 'INFO'); $this->log("Schema file: {$this->schemaFile}", 'INFO'); if ($this->detectedPlugin) { $this->log("Plugin available for validation and health checks", 'INFO'); } return 0; } /** * Detect client site repository. * Client repos have either: * (a) src/ with Joomla site structure + deployment configs (legacy) * (b) src/templateDetails.xml with type="file" (theme package) * They are NOT Joomla extensions (component/module/plugin/template). */ private function detectClient(string $repoPath): void { $score = 0; $indicators = []; // Strong indicator: type="file" manifest (client theme package) $manifests = glob($repoPath . '/src/*.xml') ?: []; $isFilePackage = false; foreach ($manifests as $xml) { $content = @file_get_contents($xml); if ($content && preg_match('/]*type="file"/', $content)) { $score += 60; $indicators[] = 'Found Joomla type="file" manifest (theme package)'; $isFilePackage = true; break; } } // Theme package files $themeMarkers = [ 'src/media/templates/site/mokoonyx/css/theme/light.custom.css' => 15, 'src/media/templates/site/mokoonyx/css/theme/dark.custom.css' => 15, 'src/script.php' => 10, 'updates.xml' => 10, ]; foreach ($themeMarkers as $path => $weight) { $full = $repoPath . '/' . $path; if (is_file($full)) { $score += $weight; $indicators[] = "Found: {$path} (+{$weight})"; } } // Legacy indicators: deployment/monitoring configs $clientMarkers = [ 'scripts/sftp-config' => 30, 'scripts/sftp-config/sftp-config.dev.json' => 10, 'scripts/sftp-config/sftp-config.rs.json' => 10, 'monitoring/grafana' => 20, 'scripts/sync-dev-to-live.sh' => 15, 'scripts/joomla-monitor.sh' => 10, 'scripts/joomla-monitor.sites.conf' => 10, ]; foreach ($clientMarkers as $path => $weight) { $full = $repoPath . '/' . $path; if (is_dir($full) || is_file($full)) { $score += $weight; $indicators[] = "Found: {$path} (+{$weight})"; } } // Legacy: site structure inside src/ $siteDirs = ['src/administrator', 'src/components', 'src/plugins', 'src/templates', 'src/media']; $siteDirCount = 0; foreach ($siteDirs as $dir) { if (is_dir($repoPath . '/' . $dir)) { $siteDirCount++; } } if ($siteDirCount >= 3) { $score += 20; $indicators[] = "Joomla site structure in src/ ({$siteDirCount}/5 dirs)"; } // Negative: if there's a Joomla extension manifest (not type="file"), it's an extension if (!$isFilePackage) { foreach ($manifests as $xml) { $content = @file_get_contents($xml); if ($content && preg_match('/]*type="(component|module|plugin|template|package)"/', $content)) { $score -= 50; $indicators[] = "Has Joomla extension manifest — likely extension, not client"; break; } } } $this->detectionResults['client'] = [ 'score' => max(0, $score), 'indicators' => $indicators, ]; } private function detectJoomla(string $repoPath): void { $score = 0; $indicators = []; // Look for Joomla manifest files $manifests = $this->findFiles($repoPath, '*.xml', 3); foreach ($manifests as $manifest) { $content = @file_get_contents($manifest); if ( $content && ( strpos($content, 'findFiles($repoPath, 'index.html', 2)); if ($indexCount > 2) { $score += 0.2; $indicators[] = "Found {$indexCount} index.html files (Joomla pattern)"; } $this->detectionResults['joomla'] = [ 'score' => min(1.0, $score), 'indicators' => $indicators, ]; } private function detectDolibarr(string $repoPath): void { $score = 0; $indicators = []; // Look for Dolibarr module descriptor $descriptors = $this->findFiles($repoPath, 'mod*.class.php', 3); foreach ($descriptors as $descriptor) { $content = @file_get_contents($descriptor); if ($content && strpos($content, 'DolibarrModules') !== false) { $score += 0.4; $indicators[] = "Found Dolibarr module descriptor: " . basename($descriptor); } } // Check for Dolibarr-specific code patterns $phpFiles = $this->findFiles($repoPath, '*.php', 3); $dolibarrPatterns = ['dol_include_once', '$this->numero', 'DoliDB', 'Translate']; foreach ($phpFiles as $file) { $content = @file_get_contents($file); if (!$content) { continue; } foreach ($dolibarrPatterns as $pattern) { if (strpos($content, $pattern) !== false) { $score += 0.05; $indicators[] = "Found Dolibarr pattern '{$pattern}' in " . basename($file); break; // Only count once per file } } if ($score >= 0.8) { break; // Stop early if confident } } // Check for Dolibarr directory structure $dolibarrDirs = ['core/modules', 'sql', 'class', 'lib', 'langs']; foreach ($dolibarrDirs as $dir) { if (is_dir("{$repoPath}/{$dir}")) { $score += 0.1; $indicators[] = "Found Dolibarr directory: {$dir}/"; } } // Check for SQL files in sql/ directory if (is_dir("{$repoPath}/sql")) { $sqlFiles = $this->findFiles("{$repoPath}/sql", '*.sql', 1); if (count($sqlFiles) > 0) { $score += 0.1; $indicators[] = "Found " . count($sqlFiles) . " SQL files in sql/"; } } $this->detectionResults['dolibarr'] = [ 'score' => min(1.0, $score), 'indicators' => $indicators, ]; } private function detectNodeJS(string $repoPath): void { $score = 0; $indicators = []; // Check for package.json if (file_exists("{$repoPath}/package.json")) { $score += 0.5; $indicators[] = "Found package.json"; $content = @file_get_contents("{$repoPath}/package.json"); if ($content) { if (strpos($content, '"typescript"') !== false || strpos($content, '"@types/') !== false) { $score += 0.1; $indicators[] = "TypeScript dependencies detected"; } if ( strpos($content, '"react"') !== false || strpos($content, '"vue"') !== false || strpos($content, '"angular"') !== false || strpos($content, '"express"') !== false ) { $score += 0.1; $indicators[] = "Node.js framework detected"; } } } // Check for node_modules and lock files if (is_dir("{$repoPath}/node_modules")) { $score += 0.1; $indicators[] = "Found node_modules directory"; } if ( file_exists("{$repoPath}/package-lock.json") || file_exists("{$repoPath}/yarn.lock") || file_exists("{$repoPath}/pnpm-lock.yaml") || file_exists("{$repoPath}/bun.lockb") ) { $score += 0.1; $indicators[] = "Found package lock file"; } // Check for TypeScript config if (file_exists("{$repoPath}/tsconfig.json")) { $score += 0.2; $indicators[] = "Found tsconfig.json"; } $this->detectionResults['nodejs'] = [ 'score' => min(1.0, $score), 'indicators' => $indicators, ]; } private function detectPython(string $repoPath): void { $score = 0; $indicators = []; // Check for Python package files if (file_exists("{$repoPath}/setup.py") || file_exists("{$repoPath}/pyproject.toml")) { $score += 0.5; $indicators[] = "Found Python package configuration"; } if (file_exists("{$repoPath}/requirements.txt")) { $score += 0.2; $indicators[] = "Found requirements.txt"; } if (file_exists("{$repoPath}/Pipfile") || file_exists("{$repoPath}/poetry.lock")) { $score += 0.2; $indicators[] = "Found Python dependency manager config"; } // Check for Python files $pyFiles = $this->findFiles($repoPath, '*.py', 2); if (count($pyFiles) > 0) { $score += 0.2; $indicators[] = "Found " . count($pyFiles) . " Python files"; } // Check for virtual environment directories $venvDirs = ['venv', '.venv', 'env', '.env']; foreach ($venvDirs as $dir) { if (is_dir("{$repoPath}/{$dir}")) { $score += 0.05; $indicators[] = "Found virtual environment: {$dir}/"; break; } } $this->detectionResults['python'] = [ 'score' => min(1.0, $score), 'indicators' => $indicators, ]; } private function detectTerraform(string $repoPath): void { $score = 0; $indicators = []; // Check for Terraform files $tfFiles = $this->findFiles($repoPath, '*.tf', 3); if (count($tfFiles) > 0) { $score += 0.5; $indicators[] = "Found " . count($tfFiles) . " Terraform files"; } // Check for terraform.tfvars or *.tfvars $tfvarsFiles = $this->findFiles($repoPath, '*.tfvars', 2); if (count($tfvarsFiles) > 0) { $score += 0.2; $indicators[] = "Found Terraform variables files"; } // Check for .terraform directory if (is_dir("{$repoPath}/.terraform")) { $score += 0.1; $indicators[] = "Found .terraform directory"; } // Check for terraform.lock.hcl if (file_exists("{$repoPath}/.terraform.lock.hcl")) { $score += 0.1; $indicators[] = "Found Terraform lock file"; } // Check for main.tf, variables.tf, outputs.tf (common pattern) $commonFiles = ['main.tf', 'variables.tf', 'outputs.tf']; $foundCommon = 0; foreach ($commonFiles as $file) { if (file_exists("{$repoPath}/{$file}")) { $foundCommon++; } } if ($foundCommon >= 2) { $score += 0.2; $indicators[] = "Found standard Terraform structure"; } $this->detectionResults['terraform'] = [ 'score' => min(1.0, $score), 'indicators' => $indicators, ]; } private function detectWordPress(string $repoPath): void { $score = 0; $indicators = []; // Check for plugin header $phpFiles = $this->findFiles($repoPath, '*.php', 2); foreach ($phpFiles as $file) { $content = @file_get_contents($file); if ( $content && (strpos($content, 'Plugin Name:') !== false || strpos($content, 'Theme Name:') !== false) ) { $score += 0.5; $indicators[] = "Found WordPress plugin/theme header in " . basename($file); break; } } // Check for WordPress functions $wpFunctions = ['add_action', 'add_filter', 'wp_enqueue_script', 'register_activation_hook']; foreach ($phpFiles as $file) { $content = @file_get_contents($file); if (!$content) { continue; } foreach ($wpFunctions as $func) { if (strpos($content, $func) !== false) { $score += 0.1; $indicators[] = "Found WordPress function '{$func}'"; break 2; } } } // Check for WordPress directory structure $wpDirs = ['includes', 'templates', 'assets']; foreach ($wpDirs as $dir) { if (is_dir("{$repoPath}/{$dir}")) { $score += 0.05; $indicators[] = "Found WordPress directory: {$dir}/"; } } $this->detectionResults['wordpress'] = [ 'score' => min(1.0, $score), 'indicators' => $indicators, ]; } private function detectMobile(string $repoPath): void { $score = 0; $indicators = []; // Check for React Native if (file_exists("{$repoPath}/package.json")) { $content = @file_get_contents("{$repoPath}/package.json"); if ($content && strpos($content, '"react-native"') !== false) { $score += 0.5; $indicators[] = "Found React Native in package.json"; } } // Check for Flutter if (file_exists("{$repoPath}/pubspec.yaml")) { $content = @file_get_contents("{$repoPath}/pubspec.yaml"); if ($content && strpos($content, 'flutter:') !== false) { $score += 0.5; $indicators[] = "Found Flutter in pubspec.yaml"; } } // Check for iOS project $xcodeFiles = $this->findFiles($repoPath, '*.xcodeproj', 2); if (count($xcodeFiles) > 0) { $score += 0.3; $indicators[] = "Found Xcode project"; } // Check for Android project if (file_exists("{$repoPath}/build.gradle") || file_exists("{$repoPath}/app/build.gradle")) { $content = @file_get_contents("{$repoPath}/build.gradle") ?: @file_get_contents("{$repoPath}/app/build.gradle"); if ($content && strpos($content, 'com.android.application') !== false) { $score += 0.3; $indicators[] = "Found Android application gradle"; } } // Check for mobile directories $mobileDirs = ['ios', 'android', 'lib']; $foundCount = 0; foreach ($mobileDirs as $dir) { if (is_dir("{$repoPath}/{$dir}")) { $foundCount++; } } if ($foundCount >= 2) { $score += 0.2; $indicators[] = "Found mobile platform directories"; } $this->detectionResults['mobile'] = [ 'score' => min(1.0, $score), 'indicators' => $indicators, ]; } private function detectAPI(string $repoPath): void { $score = 0; $indicators = []; // Check for API documentation files $apiDocs = ['openapi.yaml', 'openapi.json', 'swagger.yaml', 'swagger.json', 'api.yaml']; foreach ($apiDocs as $doc) { if (file_exists("{$repoPath}/{$doc}")) { $score += 0.3; $indicators[] = "Found API documentation: {$doc}"; break; } } // Check for GraphQL schema $graphqlFiles = $this->findFiles($repoPath, '*.graphql', 2); if (count($graphqlFiles) > 0 || file_exists("{$repoPath}/schema.graphql")) { $score += 0.3; $indicators[] = "Found GraphQL schema"; } // Check for gRPC proto files $protoFiles = $this->findFiles($repoPath, '*.proto', 2); if (count($protoFiles) > 0) { $score += 0.3; $indicators[] = "Found Protocol Buffer definitions"; } // Check for Dockerfile (common in microservices) if (file_exists("{$repoPath}/Dockerfile")) { $score += 0.1; $indicators[] = "Found Dockerfile"; } // Check for docker-compose.yml if (file_exists("{$repoPath}/docker-compose.yml") || file_exists("{$repoPath}/docker-compose.yaml")) { $score += 0.1; $indicators[] = "Found docker-compose configuration"; } // Check for API patterns in code $apiFiles = array_merge( $this->findFiles($repoPath, '*.js', 2), $this->findFiles($repoPath, '*.ts', 2), $this->findFiles($repoPath, '*.py', 2) ); $apiPatterns = [ '@app.route' => 'Flask route', '@api_view' => 'Django REST framework', 'express()' => 'Express.js', 'fastapi' => 'FastAPI', '@Controller' => 'NestJS controller', ]; foreach ($apiFiles as $file) { $content = @file_get_contents($file); if (!$content) { continue; } foreach ($apiPatterns as $pattern => $name) { if (stripos($content, $pattern) !== false) { $score += 0.2; $indicators[] = "Found {$name} pattern"; break 2; } } } $this->detectionResults['api'] = [ 'score' => min(1.0, $score), 'indicators' => $indicators, ]; } private function detectMcpServer(string $repoPath): void { $score = 0; $indicators = []; // Check for MCP SDK in package.json if (file_exists("{$repoPath}/package.json")) { $content = @file_get_contents("{$repoPath}/package.json"); if ($content && strpos($content, '@modelcontextprotocol/sdk') !== false) { $score += 0.5; $indicators[] = "Found @modelcontextprotocol/sdk in package.json"; } } // Check for MCP server entry point with McpServer usage if (file_exists("{$repoPath}/src/index.ts")) { $content = @file_get_contents("{$repoPath}/src/index.ts"); if ($content) { if (strpos($content, 'McpServer') !== false) { $score += 0.3; $indicators[] = "Found McpServer import in src/index.ts"; } if (strpos($content, 'server.tool(') !== false) { $score += 0.1; $toolCount = substr_count($content, 'server.tool('); $indicators[] = "Found {$toolCount} tool registrations in src/index.ts"; } if (strpos($content, 'StdioServerTransport') !== false) { $score += 0.1; $indicators[] = "Found StdioServerTransport (stdio MCP transport)"; } } } // Check for the standard 4-file MCP structure $mcpFiles = ['src/index.ts', 'src/client.ts', 'src/config.ts', 'src/types.ts']; $foundCount = 0; foreach ($mcpFiles as $file) { if (file_exists("{$repoPath}/{$file}")) { $foundCount++; } } if ($foundCount === 4) { $score += 0.1; $indicators[] = "Found standard MCP 4-file src/ structure"; } // Check for setup wizard if (file_exists("{$repoPath}/scripts/setup.mjs")) { $score += 0.05; $indicators[] = "Found interactive setup wizard"; } // Check for .moko-platform platform declaration $mokoFiles = ["{$repoPath}/.gitea/.moko-platform", "{$repoPath}/.github/.moko-platform"]; foreach ($mokoFiles as $mokoFile) { if (file_exists($mokoFile)) { $content = @file_get_contents($mokoFile); if ($content && stripos($content, 'mcp-server') !== false) { $score += 0.2; $indicators[] = "Found explicit mcp-server platform declaration"; break; } } } $this->detectionResults['mcp-server'] = [ 'score' => min(1.0, $score), 'indicators' => $indicators, ]; } private function determinePlatform(): void { // Find platform with highest score above threshold $maxScore = 0; $selectedPlatform = 'generic'; foreach ($this->detectionResults as $platform => $data) { if ($data['score'] >= self::DETECTION_THRESHOLD && $data['score'] > $maxScore) { $maxScore = $data['score']; $selectedPlatform = $platform; } } $this->detectedPlatform = $selectedPlatform; } private function mapPlatformToSchema(string $schemaDir): string { $mapping = [ 'joomla' => 'waas-component.tf', 'dolibarr' => 'crm-module.tf', 'nodejs' => 'nodejs-repository.tf', 'python' => 'python-repository.tf', 'terraform' => 'terraform-repository.tf', 'wordpress' => 'wordpress-repository.tf', 'mobile' => 'mobile-app-repository.tf', 'api' => 'api-repository.tf', 'mcp-server' => 'mcp-server.tf', 'documentation' => 'documentation-repository.tf', 'standards' => 'standards-repository.tf', 'generic' => 'default-repository.tf', ]; return $schemaDir . '/' . $mapping[$this->detectedPlatform]; } private function displayResults(): void { echo "\n=== Platform Detection Results ===\n\n"; echo "Platform: {$this->detectedPlatform}\n"; echo "Schema: {$this->schemaFile}\n\n"; echo "Detection Scores:\n"; foreach ($this->detectionResults as $platform => $data) { $percentage = round($data['score'] * 100, 1); $status = ($data['score'] >= self::DETECTION_THRESHOLD) ? '✅' : '❌'; echo sprintf(" %s %s: %.1f%%\n", $status, ucfirst($platform), $percentage); } echo "\nDetection Indicators:\n"; $indicators = $this->detectionResults[$this->detectedPlatform]['indicators']; if (empty($indicators)) { echo " No specific indicators found (generic repository)\n"; } else { foreach ($indicators as $indicator) { echo " • {$indicator}\n"; } } echo "\n"; } private function outputJson(): void { $output = [ 'platform' => $this->detectedPlatform, 'schema' => $this->schemaFile, 'detection_results' => $this->detectionResults, 'threshold' => self::DETECTION_THRESHOLD, 'timestamp' => date('c'), 'plugin_available' => $this->detectedPlugin !== null, ]; if ($this->detectedPlugin) { $output['plugin_info'] = [ 'name' => $this->detectedPlugin->getPluginName(), 'version' => $this->detectedPlugin->getPluginVersion(), 'type' => $this->detectedPlugin->getProjectType(), ]; } echo json_encode($output, JSON_PRETTY_PRINT) . PHP_EOL; } private function generateReports(string $outputDir, string $repoPath): void { // Ensure output directory exists if (!is_dir($outputDir)) { @mkdir($outputDir, 0755, true); } $timestamp = date('Ymd_His'); // Generate detection report $detectionReport = $outputDir . "/detection_report_{$timestamp}.md"; $this->writeDetectionReport($detectionReport, $repoPath); // Generate summary report $summaryReport = $outputDir . "/SUMMARY_{$timestamp}.md"; $this->writeSummaryReport($summaryReport, $repoPath); $this->log("Reports generated in: {$outputDir}", 'INFO'); } private function writeDetectionReport(string $file, string $repoPath): void { $content = "# Platform Detection Report\n\n"; $content .= "**Generated**: " . date('Y-m-d H:i:s') . "\n"; $content .= "**Repository**: {$repoPath}\n\n"; $content .= "## Detected Platform\n\n"; $content .= "**Type**: " . strtoupper($this->detectedPlatform) . "\n"; $content .= "**Confidence**: " . round($this->detectionResults[$this->detectedPlatform]['score'] * 100, 1) . "%\n"; $content .= "**Schema**: {$this->schemaFile}\n\n"; $content .= "## Detection Indicators\n\n"; foreach ($this->detectionResults[$this->detectedPlatform]['indicators'] as $indicator) { $content .= "- {$indicator}\n"; } $content .= "\n## All Platform Scores\n\n"; foreach ($this->detectionResults as $platform => $data) { $percentage = round($data['score'] * 100, 1); $content .= "- **" . ucfirst($platform) . "**: {$percentage}%\n"; } @file_put_contents($file, $content); } private function writeSummaryReport(string $file, string $repoPath): void { $content = "# Platform Detection Summary\n\n"; $content .= "| Property | Value |\n"; $content .= "|----------|-------|\n"; $content .= "| Repository | {$repoPath} |\n"; $content .= "| Platform | " . strtoupper($this->detectedPlatform) . " |\n"; $content .= "| Confidence | " . round($this->detectionResults[$this->detectedPlatform]['score'] * 100, 1) . "% |\n"; $content .= "| Schema | " . basename($this->schemaFile) . " |\n"; $content .= "| Timestamp | " . date('Y-m-d H:i:s') . " |\n\n"; $content .= "## Next Steps\n\n"; $content .= "1. Review detection indicators\n"; $content .= "2. Validate repository against schema: {$this->schemaFile}\n"; $content .= "3. Address any validation errors or warnings\n"; @file_put_contents($file, $content); } private function findFiles(string $dir, string $pattern, int $maxDepth = 1): array { $files = []; $pattern = str_replace('*', '.*', $pattern); $pattern = str_replace('.', '\.', $pattern); try { $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); $iterator->setMaxDepth($maxDepth); foreach ($iterator as $file) { if ($file->isFile() && preg_match("/{$pattern}$/", $file->getFilename())) { $files[] = $file->getPathname(); } } } catch (Exception $e) { // Directory not accessible } return $files; } private function getAbsolutePath(string $path): string { if (strlen($path) > 0 && $path[0] === '/') { return $path; } return getcwd() . '/' . $path; } } // Run the application $app = new AutoDetectPlatform('auto_detect_platform', 'Automatically detect platform type and validate repository'); exit($app->execute());