Files
MokoCLI/validate/auto_detect_platform.php
T
Jonathan Miller b491241a58
Universal: Sync Feature Branch Versions / Sync feature branches with dev (push) Has been skipped
Merge branch 'main' into dev
# Conflicts:
#	.mokogitea/CLAUDE.md
#	.mokogitea/ISSUE_TEMPLATE/config.yml
#	.mokogitea/ISSUE_TEMPLATE/documentation.md
#	.mokogitea/ISSUE_TEMPLATE/feature_request.md
#	.mokogitea/ISSUE_TEMPLATE/security.md
#	.mokogitea/branch-protection.yml
#	.mokogitea/bulk-repo-sync.yml
#	.mokogitea/pr-branch-check.yml
#	.mokogitea/renovate.yml
#	.mokogitea/sync-wikis.yml
#	.mokogitea/workflows/auto-bump.yml
#	.mokogitea/workflows/auto-release.yml
#	.mokogitea/workflows/ci-platform.yml
#	.mokogitea/workflows/cleanup.yml
#	.mokogitea/workflows/gitleaks.yml
#	.mokogitea/workflows/issue-branch.yml
#	.mokogitea/workflows/notify.yml
#	.mokogitea/workflows/pre-release.yml
#	.mokogitea/workflows/repo-health.yml
#	.mokogitea/workflows/security-audit.yml
#	.script-registry.json
#	CHANGELOG.md
#	PLUGIN_SCRIPTS.md
#	README.md
#	analysis/index.md
#	automation/bulk_joomla_template.php
#	automation/bulk_sync.php
#	automation/enrich_manifest_xml.php
#	automation/enrich_mokostandards_xml.php
#	automation/index.md
#	automation/migrate_to_gitea.php
#	automation/push_files.php
#	automation/push_manifest_xml.php
#	automation/push_mokostandards_xml.php
#	automation/repo_cleanup.php
#	bin/moko
#	cli/archive_repo.php
#	cli/audit_query.php
#	cli/badge_update.php
#	cli/branch_rename.php
#	cli/bulk_workflow_push.php
#	cli/bulk_workflow_trigger.php
#	cli/changelog_promote.php
#	cli/changelog_prune.php
#	cli/client_dashboard.php
#	cli/client_health_check.php
#	cli/client_inventory.php
#	cli/client_provision.php
#	cli/completion.php
#	cli/create_project.php
#	cli/create_repo.php
#	cli/deploy_joomla.php
#	cli/dev_branch_reset.php
#	cli/grafana_dashboard.php
#	cli/joomla_build.php
#	cli/joomla_compat_check.php
#	cli/joomla_metadata_validate.php
#	cli/joomla_release.php
#	cli/license_manage.php
#	cli/manifest_element.php
#	cli/manifest_licensing.php
#	cli/manifest_read.php
#	cli/package_build.php
#	cli/platform_detect.php
#	cli/release.php
#	cli/release_body_update.php
#	cli/release_cascade.php
#	cli/release_create.php
#	cli/release_manage.php
#	cli/release_mirror.php
#	cli/release_notes.php
#	cli/release_package.php
#	cli/release_promote.php
#	cli/release_publish.php
#	cli/release_validate.php
#	cli/release_verify.php
#	cli/scaffold_client.php
#	cli/sync_rulesets.php
#	cli/theme_lint.php
#	cli/updates_xml_build.php
#	cli/updates_xml_sync.php
#	cli/version_auto_bump.php
#	cli/version_bump.php
#	cli/version_bump_remote.php
#	cli/version_check.php
#	cli/version_read.php
#	cli/version_reset_dev.php
#	cli/version_set_platform.php
#	cli/wiki_sync.php
#	cli/workflow_sync.php
#	composer.json
#	deploy/backup-before-deploy.php
#	deploy/deploy-dolibarr.php
#	deploy/deploy-joomla.php
#	deploy/deploy-sftp.php
#	deploy/health-check.php
#	deploy/rollback-joomla.php
#	deploy/sync-joomla.php
#	fix/fix_line_endings.php
#	fix/fix_permissions.php
#	fix/fix_tabs.php
#	fix/fix_trailing_spaces.php
#	fix/index.md
#	index.md
#	lib/CliBase.php
#	lib/Common.php
#	lib/Enterprise/AbstractProjectPlugin.php
#	lib/Enterprise/ApiClient.php
#	lib/Enterprise/AuditLogger.php
#	lib/Enterprise/CheckpointManager.php
#	lib/Enterprise/CliFramework.php
#	lib/Enterprise/Config.php
#	lib/Enterprise/ConfigValidator.php
#	lib/Enterprise/EnterpriseReadinessValidator.php
#	lib/Enterprise/ErrorRecovery.php
#	lib/Enterprise/FileFixUtility.php
#	lib/Enterprise/GitHubAdapter.php
#	lib/Enterprise/GitPlatformAdapter.php
#	lib/Enterprise/InputValidator.php
#	lib/Enterprise/ManifestParser.php
#	lib/Enterprise/ManifestReader.php
#	lib/Enterprise/MetricsCollector.php
#	lib/Enterprise/MokoGiteaAdapter.php
#	lib/Enterprise/PackageBuilder.php
#	lib/Enterprise/PlatformAdapterFactory.php
#	lib/Enterprise/PluginFactory.php
#	lib/Enterprise/PluginRegistry.php
#	lib/Enterprise/Plugins/ApiPlugin.php
#	lib/Enterprise/Plugins/DocumentationPlugin.php
#	lib/Enterprise/Plugins/DolibarrPlugin.php
#	lib/Enterprise/Plugins/GenericPlugin.php
#	lib/Enterprise/Plugins/JoomlaPlugin.php
#	lib/Enterprise/Plugins/McpServerPlugin.php
#	lib/Enterprise/Plugins/MobilePlugin.php
#	lib/Enterprise/Plugins/NodeJsPlugin.php
#	lib/Enterprise/Plugins/PythonPlugin.php
#	lib/Enterprise/Plugins/TerraformPlugin.php
#	lib/Enterprise/Plugins/WordPressPlugin.php
#	lib/Enterprise/ProjectConfigValidator.php
#	lib/Enterprise/ProjectMetricsCollector.php
#	lib/Enterprise/ProjectPluginInterface.php
#	lib/Enterprise/ProjectTypeDetector.php
#	lib/Enterprise/RecoveryError.php
#	lib/Enterprise/RecoveryManager.php
#	lib/Enterprise/RepositoryHealthChecker.php
#	lib/Enterprise/RepositorySynchronizer.php
#	lib/Enterprise/RetryHelper.php
#	lib/Enterprise/SecurityValidator.php
#	lib/Enterprise/SourceResolver.php
#	lib/Enterprise/SynchronizationException.php
#	lib/Enterprise/TransactionManager.php
#	lib/Enterprise/UnifiedValidation.php
#	lib/index.md
#	lib/plugins/Joomla/UpdateXmlGenerator.php
#	maintenance/index.md
#	maintenance/pin_action_shas.php
#	maintenance/repo_inventory.php
#	maintenance/rotate_secrets.php
#	maintenance/setup_labels.php
#	maintenance/sync_dolibarr_readmes.php
#	maintenance/update_repo_inventory.php
#	maintenance/update_sha_hashes.php
#	maintenance/update_version_from_readme.php
#	mcp/config.example.json
#	mcp/package.json
#	mcp/src/config.ts
#	mcp/src/index.ts
#	mcp/src/runner.ts
#	mcp/src/types.ts
#	phpcs.xml
#	plugin_health_check.php
#	plugin_list.php
#	plugin_metrics.php
#	plugin_readiness.php
#	plugin_validate.php
#	release/generate_dolibarr_version_txt.php
#	release/generate_joomla_update_xml.php
#	src/functions.php
#	templates/configs/README.md
#	templates/configs/index.md
#	templates/configs/manifest.xml.template
#	templates/configs/manifest.yml.template
#	templates/configs/mokostandards.xml.template
#	templates/configs/mokostandards.yml.template
#	templates/configs/phpcs.xml
#	templates/docs/README.md
#	templates/docs/extra/README.md
#	templates/docs/extra/index.md
#	templates/docs/index.md
#	templates/docs/required/GOVERNANCE.md
#	templates/docs/required/README.md
#	templates/docs/required/index.md
#	templates/docs/required/template-CONTRIBUTING.md
#	templates/docs/required/template-README.md
#	templates/docs/required/template-SECURITY.md
#	templates/index.md
#	templates/licenses/README.md
#	templates/licenses/index.md
#	templates/makefiles/README.md
#	templates/mokogitea/CLAUDE.dolibarr.md.template
#	templates/mokogitea/CLAUDE.joomla.md.template
#	templates/mokogitea/CLAUDE.md.template
#	templates/mokogitea/ISSUE_TEMPLATE/config.yml
#	templates/mokogitea/ISSUE_TEMPLATE/documentation.md
#	templates/mokogitea/ISSUE_TEMPLATE/dolibarr_module_id_request.md
#	templates/mokogitea/ISSUE_TEMPLATE/feature_request.md
#	templates/mokogitea/ISSUE_TEMPLATE/security.md
#	templates/mokogitea/README.md
#	templates/mokogitea/copilot-instructions.dolibarr.md.template
#	templates/mokogitea/copilot-instructions.joomla.md.template
#	templates/mokogitea/copilot-instructions.md.template
#	templates/mokogitea/dependabot.yml.template
#	templates/mokogitea/override.tf.template
#	templates/required/README.md
#	templates/schemas/README.md
#	templates/schemas/manifest-schema.xsd
#	templates/schemas/moko-platform-schema.xsd
#	templates/schemas/mokostandards-schema.xsd
#	templates/schemas/schemas/README.md
#	templates/schemas/template-repository-structure.xml
#	templates/scripts/README.md
#	templates/scripts/common/CliBase.template.php
#	templates/scripts/fix/index.md
#	templates/scripts/index.md
#	templates/scripts/release/index.md
#	templates/scripts/release/package_dolibarr.php
#	templates/scripts/release/package_joomla.php
#	templates/scripts/sftp-config/README.md
#	templates/scripts/validate/dolibarr_module.php
#	templates/scripts/validate/index.md
#	templates/scripts/validate/validate_manifest.php
#	templates/scripts/validate/validate_structure.php
#	templates/security/README.md
#	templates/security/index.php
#	templates/stubs/dolibarr.php
#	templates/stubs/joomla.php
#	templates/web/index.php
#	tests/Enterprise/GitPlatformAdapterTest.php
#	tests/Unit/VersionBumpTest.php
#	tests/Unit/VersionReadTest.php
#	tests/index.md
#	tests/test_circuit_breaker_handling.php
#	tests/test_enterprise_libraries.php
#	validate/SECURITY_SCANNING.md
#	validate/auto_detect_platform.php
#	validate/check_changelog.php
#	validate/check_client_theme.php
#	validate/check_composer_deps.php
#	validate/check_dolibarr_module.php
#	validate/check_enterprise_readiness.php
#	validate/check_file_integrity.php
#	validate/check_joomla_manifest.php
#	validate/check_language_structure.php
#	validate/check_license_headers.php
#	validate/check_no_secrets.php
#	validate/check_paths.php
#	validate/check_php_syntax.php
#	validate/check_repo_health.php
#	validate/check_structure.php
#	validate/check_tabs.php
#	validate/check_version_consistency.php
#	validate/check_wiki_health.php
#	validate/check_xml_wellformed.php
#	validate/index.md
#	validate/scan_drift.php
#	wrappers/auto_detect_platform.php
#	wrappers/bulk_sync.php
#	wrappers/check_changelog.php
#	wrappers/check_dolibarr_module.php
#	wrappers/check_enterprise_readiness.php
#	wrappers/check_joomla_manifest.php
#	wrappers/check_language_structure.php
#	wrappers/check_license_headers.php
#	wrappers/check_no_secrets.php
#	wrappers/check_paths.php
#	wrappers/check_php_syntax.php
#	wrappers/check_repo_health.php
#	wrappers/check_structure.php
#	wrappers/check_tabs.php
#	wrappers/check_version_consistency.php
#	wrappers/check_xml_wellformed.php
#	wrappers/deploy_sftp.php
#	wrappers/fix_line_endings.php
#	wrappers/fix_permissions.php
#	wrappers/fix_tabs.php
#	wrappers/fix_trailing_spaces.php
#	wrappers/gen_wrappers.php
#	wrappers/index.md
#	wrappers/pin_action_shas.php
#	wrappers/plugin_health_check.php
#	wrappers/plugin_list.php
#	wrappers/plugin_metrics.php
#	wrappers/plugin_readiness.php
#	wrappers/plugin_validate.php
#	wrappers/scan_drift.php
#	wrappers/setup_labels.php
#	wrappers/sync_dolibarr_readmes.php
#	wrappers/update_sha_hashes.php
#	wrappers/update_version_from_readme.php
2026-06-20 21:43:38 -05:00

973 lines
34 KiB
PHP
Executable File

#!/usr/bin/env php
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
<<<<<<< HEAD
* DEFGROUP: MokoCLI.Scripts.Validate
* INGROUP: MokoCLI
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
=======
* DEFGROUP: MokoPlatform.Scripts.Validate
* INGROUP: MokoPlatform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
>>>>>>> main
* 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 MokoCli\{
CliFramework,
ProjectTypeDetector,
PluginFactory,
PluginRegistry,
AuditLogger,
MetricsCollector,
SourceResolver
};
/**
* 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 CliFramework
{
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 configure(): void
{
$this->setDescription('Automatically detect platform type and validate repository');
$this->addArgument('--repo-path', 'Path to repository to analyze', '.');
$this->addArgument('--schema-dir', 'Path to schema definitions directory', 'definitions/default');
$this->addArgument('--output-dir', 'Directory for output reports', 'var/logs/validation');
}
protected function run(): int
{
$repoPath = $this->getArgument('--repo-path', '.');
$schemaDir = $this->getArgument('--schema-dir', 'definitions/default');
$outputDir = $this->getArgument('--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} (schema validation skipped)", 'WARN');
$schemaDir = '';
}
$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->detect($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->getArgument("--json", false)) {
$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('/<extension\s+[^>]*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 source/ or src/
$srcName = SourceResolver::resolve($repoPath);
$siteDirs = ["{$srcName}/administrator", "{$srcName}/components", "{$srcName}/plugins", "{$srcName}/templates", "{$srcName}/media"];
$siteDirCount = 0;
foreach ($siteDirs as $dir) {
if (is_dir($repoPath . '/' . $dir)) {
$siteDirCount++;
}
}
if ($siteDirCount >= 3) {
$score += 20;
$indicators[] = "Joomla site structure in {$srcName}/ ({$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('/<extension\s+[^>]*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, '<extension') !== false ||
strpos($content, '<install') !== false
)
) {
$score += 0.3;
$indicators[] = "Found Joomla manifest: " . basename($manifest);
}
}
// Check for Joomla directory structure
$joomlaDirs = ['site', 'admin', 'administrator', 'language', 'media'];
foreach ($joomlaDirs as $dir) {
if (is_dir("{$repoPath}/{$dir}")) {
$score += 0.1;
$indicators[] = "Found Joomla directory: {$dir}/";
}
}
// Check for index.html files (Joomla security pattern)
$indexCount = count($this->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
$mcpEntry = SourceResolver::findUnderSource($repoPath, 'index.ts');
if ($mcpEntry !== null) {
$content = @file_get_contents($mcpEntry);
$mcpSrcName = SourceResolver::resolve($repoPath);
if ($content) {
if (strpos($content, 'McpServer') !== false) {
$score += 0.3;
$indicators[] = "Found McpServer import in {$mcpSrcName}/index.ts";
}
if (strpos($content, 'server.tool(') !== false) {
$score += 0.1;
$toolCount = substr_count($content, 'server.tool(');
$indicators[] = "Found {$toolCount} tool registrations in {$mcpSrcName}/index.ts";
}
if (strpos($content, 'StdioServerTransport') !== false) {
$score += 0.1;
$indicators[] = "Found StdioServerTransport (stdio MCP transport)";
}
}
}
// Check for the standard 4-file MCP structure
$mcpRequired = ['index.ts', 'client.ts', 'config.ts', 'types.ts'];
$foundCount = 0;
foreach ($mcpRequired as $file) {
if (SourceResolver::findUnderSource($repoPath, $file) !== null) {
$foundCount++;
}
}
if ($foundCount === 4) {
$score += 0.1;
$mcpSrcName = $mcpSrcName ?? SourceResolver::resolve($repoPath);
$indicators[] = "Found standard MCP 4-file {$mcpSrcName}/ structure";
}
// Check for setup wizard
if (file_exists("{$repoPath}/scripts/setup.mjs")) {
$score += 0.05;
$indicators[] = "Found interactive setup wizard";
}
<<<<<<< HEAD
// Check for .MokoCLI platform declaration
$mokoFiles = ["{$repoPath}/.gitea/.MokoCLI", "{$repoPath}/.github/.MokoCLI"];
=======
// Check for .mokoplatform platform declaration
$mokoFiles = ["{$repoPath}/.gitea/.mokoplatform", "{$repoPath}/.github/.mokoplatform"];
>>>>>>> main
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();
exit($app->execute());