From 7f9a087b8bb615970b81c0537e3d83d4060713b4 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Sun, 18 Oct 2026 22:55:02 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20Divi-Reiter=20und=20divi-check=20in?= =?UTF-8?q?=20die=20Divi-Integration=20verschoben;=20CoreIndependenceTest?= =?UTF-8?q?=20pr=C3=BCft=20auch=20Cli,=20ChangeLog,=20Pods=20und=20die=20W?= =?UTF-8?q?urzeldateien,=20erkennt=20Alias-Importe=20und=20kennt=20nur=20n?= =?UTF-8?q?och=20Plugin.php=20als=20Ausnahme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../notes/2026-10-18-divi-faehigkeiten.md | 7 +- src/{ => Integrations/Divi}/Admin/DiviTab.php | 5 +- .../Divi}/Cli/DiviCheckCommand.php | 2 +- src/Integrations/Divi/DiviIntegration.php | 4 +- .../Divi/Release/DiviCheckRoute.php | 2 +- tests/Integration/divi-release-smoke.sh | 6 +- .../Architecture/CoreIndependenceTest.php | 76 +++++++++++++++---- .../Divi}/Admin/DiviTabTest.php | 4 +- .../Divi}/Cli/DiviCheckCommandTest.php | 4 +- .../Integrations/Divi/DiviIntegrationTest.php | 2 +- 10 files changed, 84 insertions(+), 28 deletions(-) rename src/{ => Integrations/Divi}/Admin/DiviTab.php (99%) rename src/{ => Integrations/Divi}/Cli/DiviCheckCommand.php (99%) rename tests/Unit/{ => Integrations/Divi}/Admin/DiviTabTest.php (99%) rename tests/Unit/{ => Integrations/Divi}/Cli/DiviCheckCommandTest.php (99%) diff --git a/docs/superpowers/notes/2026-10-18-divi-faehigkeiten.md b/docs/superpowers/notes/2026-10-18-divi-faehigkeiten.md index b979b85..ef34c5e 100644 --- a/docs/superpowers/notes/2026-10-18-divi-faehigkeiten.md +++ b/docs/superpowers/notes/2026-10-18-divi-faehigkeiten.md @@ -38,8 +38,11 @@ pauschale Sperre der Divi-Absicherung (2026-09-29) durch eine Freigabe je Fähig - **Zustand:** Code-, Signatur- und Konstanten-Prüfungen laufen je Request live. Gecacht wird nur die Mirror-Prüfung (Transient `lit_mcp_divi_state`, Format 2: Mirror-Probleme mit Art und Fähigkeiten, ein Tag); ein Eintrag im alten Format zählt als Miss. Ein Divi-Hotfix ohne Versionssprung wird so sofort erkannt. -- **Kern ohne Divi:** `CoreIndependenceTest` erzwingt, dass `Abilities`, `Integrations/{Core,WordPress,Media}`, `Admin` - (außer `DiviTab`), `Guides` und `Server` nichts aus `Integrations\Divi` importieren. +- **Kern ohne Divi:** `CoreIndependenceTest` erzwingt, dass `Abilities`, `Admin`, `ChangeLog`, `Cli`, `Guides`, + `Integrations/{Core,Media,Pods,WordPress}`, `Server` sowie die Dateien direkt unter `src/` und `src/Integrations/` + nichts aus `Integrations\Divi` importieren (auch kein Alias-Import). Einzige Ausnahme ist `Plugin.php` als + Composition Root. Die Divi-Oberfläche liegt in der Divi-Integration: `Integrations/Divi/Admin/DiviTab.php` und + `Integrations/Divi/Cli/DiviCheckCommand.php`. ## Ergänzungen bei der Umsetzung diff --git a/src/Admin/DiviTab.php b/src/Integrations/Divi/Admin/DiviTab.php similarity index 99% rename from src/Admin/DiviTab.php rename to src/Integrations/Divi/Admin/DiviTab.php index 3392dee..c785718 100644 --- a/src/Admin/DiviTab.php +++ b/src/Integrations/Divi/Admin/DiviTab.php @@ -2,9 +2,12 @@ declare(strict_types=1); -namespace LIT\MCP\Admin; +namespace LIT\MCP\Integrations\Divi\Admin; use Closure; +use LIT\MCP\Admin\AdminPostAction; +use LIT\MCP\Admin\AdminTab; +use LIT\MCP\Admin\SettingsPage; use LIT\MCP\ChangeLog\ChangeLog; use LIT\MCP\Integrations\Divi\Release\CheckReport; use LIT\MCP\Integrations\Divi\Release\CheckReportStore; diff --git a/src/Cli/DiviCheckCommand.php b/src/Integrations/Divi/Cli/DiviCheckCommand.php similarity index 99% rename from src/Cli/DiviCheckCommand.php rename to src/Integrations/Divi/Cli/DiviCheckCommand.php index f0fba57..6f0774a 100644 --- a/src/Cli/DiviCheckCommand.php +++ b/src/Integrations/Divi/Cli/DiviCheckCommand.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace LIT\MCP\Cli; +namespace LIT\MCP\Integrations\Divi\Cli; use Closure; use LIT\MCP\Integrations\Divi\Release\CheckReport; diff --git a/src/Integrations/Divi/DiviIntegration.php b/src/Integrations/Divi/DiviIntegration.php index 2e998f0..e80e35b 100644 --- a/src/Integrations/Divi/DiviIntegration.php +++ b/src/Integrations/Divi/DiviIntegration.php @@ -7,11 +7,11 @@ namespace LIT\MCP\Integrations\Divi; use Closure; use LIT\MCP\Abilities\Ability; use LIT\MCP\Abilities\ToolAvailability; -use LIT\MCP\Admin\DiviTab; use LIT\MCP\Admin\SettingsPage; use LIT\MCP\ChangeLog\ChangeLog; -use LIT\MCP\Cli\DiviCheckCommand; use LIT\MCP\Integrations\Bootable; +use LIT\MCP\Integrations\Divi\Admin\DiviTab; +use LIT\MCP\Integrations\Divi\Cli\DiviCheckCommand; use LIT\MCP\Integrations\Divi\BlockTree\Parser; use LIT\MCP\Integrations\Divi\Design\DesignSystemReader; use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter; diff --git a/src/Integrations/Divi/Release/DiviCheckRoute.php b/src/Integrations/Divi/Release/DiviCheckRoute.php index 2078f3b..71433f7 100644 --- a/src/Integrations/Divi/Release/DiviCheckRoute.php +++ b/src/Integrations/Divi/Release/DiviCheckRoute.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace LIT\MCP\Integrations\Divi\Release; use Closure; -use LIT\MCP\Admin\DiviTab; use LIT\MCP\Admin\SettingsPage; +use LIT\MCP\Integrations\Divi\Admin\DiviTab; use LIT\MCP\Log; use Throwable; use WP_REST_Response; diff --git a/tests/Integration/divi-release-smoke.sh b/tests/Integration/divi-release-smoke.sh index 57a6cb7..aa0bdea 100755 --- a/tests/Integration/divi-release-smoke.sh +++ b/tests/Integration/divi-release-smoke.sh @@ -165,7 +165,7 @@ pass "site status: only page-write incompatible (problem and tools named), refus # The tab is taken from the settings page filter, as the plugin wires it (with the tools of every integration). NOTICE=$(wp --user="$ADMIN_LOGIN" eval '$tab = null; foreach (apply_filters(LIT\MCP\Admin\SettingsPage::TABS_FILTER, []) as $candidate) { - if ($candidate instanceof LIT\MCP\Admin\DiviTab) { + if ($candidate instanceof LIT\MCP\Integrations\Divi\Admin\DiviTab) { $tab = $candidate; } } @@ -177,7 +177,7 @@ TAB=$(wp --user="$ADMIN_LOGIN" eval ' require_once ABSPATH . "wp-admin/includes/template.php"; $tab = null; foreach (apply_filters(LIT\MCP\Admin\SettingsPage::TABS_FILTER, []) as $candidate) { - if ($candidate instanceof LIT\MCP\Admin\DiviTab) { + if ($candidate instanceof LIT\MCP\Integrations\Divi\Admin\DiviTab) { $tab = $candidate; } } @@ -198,7 +198,7 @@ echo "$STATUS" | jq -e '.components[] | select(.id == "divi") | .features | all( || fail "every feature must be released again: $STATUS" [ -z "$(wp --user="$ADMIN_LOGIN" eval '$tab = null; foreach (apply_filters(LIT\MCP\Admin\SettingsPage::TABS_FILTER, []) as $candidate) { - if ($candidate instanceof LIT\MCP\Admin\DiviTab) { + if ($candidate instanceof LIT\MCP\Integrations\Divi\Admin\DiviTab) { $tab = $candidate; } } diff --git a/tests/Unit/Architecture/CoreIndependenceTest.php b/tests/Unit/Architecture/CoreIndependenceTest.php index 4f189e0..ff1d906 100644 --- a/tests/Unit/Architecture/CoreIndependenceTest.php +++ b/tests/Unit/Architecture/CoreIndependenceTest.php @@ -10,27 +10,37 @@ use RecursiveDirectoryIterator; use RecursiveIteratorIterator; /** - * The generic core knows no Divi class: nothing below the CORE_PATHS imports or names a class of - * LIT\MCP\Integrations\Divi. Divi plugs in through ports (AbilityGuard, ToolRefusal, BuilderDetector, + * The generic core knows no Divi class: nothing below the CORE_PATHS and none of the CORE_ROOTS files imports or names + * a class of LIT\MCP\Integrations\Divi. Divi plugs in through ports (AbilityGuard, ToolRefusal, BuilderDetector, * MediaUsageSource, EnvironmentComponent, ContentSchema's excluded post types) that Plugin.php – the composition - * root, outside these paths – wires up. Divi's own names (et_* …) are DiviBoundaryTest's business, not this test's. + * root, the one listed exception – wires up. Divi's own UI (settings tab, WP-CLI command) lives in + * Integrations/Divi. Divi's own names (et_* …) are DiviBoundaryTest's business, not this test's. */ final class CoreIndependenceTest extends TestCase { - /** Directories below src/ that must not use Divi classes. */ + /** Directories below src/ (scanned recursively) that must not use Divi classes. */ private const CORE_PATHS = [ 'Abilities', - 'Integrations/Core', - 'Integrations/WordPress', - 'Integrations/Media', 'Admin', + 'ChangeLog', + 'Cli', 'Guides', + 'Integrations/Core', + 'Integrations/Media', + 'Integrations/Pods', + 'Integrations/WordPress', 'Server', ]; - /** Files inside CORE_PATHS that may use Divi classes, each with its reason. */ + /** CORE_PATHS that may be absent: no generic WP-CLI command exists yet (Divi's lives in Integrations/Divi/Cli). */ + private const OPTIONAL_PATHS = ['Cli']; + + /** Directories below src/ whose PHP files directly inside (not below) must not use Divi classes either. */ + private const CORE_ROOTS = ['', 'Integrations']; + + /** Files of the scan that may use Divi classes, each with its reason. */ private const EXCEPTIONS = [ - 'Admin/DiviTab.php' => 'The settings tab "Divi" is Divi UI: release state, admin check and release/withdraw.', + 'Plugin.php' => 'Composition root: builds DiviIntegration and wires its ports (DiviFeatureGate) into the core.', ]; public function testTheCoreUsesNoDiviClass(): void @@ -63,7 +73,7 @@ final class CoreIndependenceTest extends TestCase } } - public function testTheScanFindsImportsQualifiedNamesGroupUsesAndClassStrings(): void + public function testTheScanFindsImportsQualifiedNamesGroupUsesAliasesAndClassStrings(): void { $source = <<<'PHP' ltrim(str_replace('\\\\', '\\', substr($token[1], 1, -1)), '\\'), default => '', }; - if (preg_match('/^(?:LIT\\\\MCP\\\\Integrations\\\\)?Divi\\\\[A-Za-z0-9_\\\\]+$/', $text) === 1) { + $diviClass = '/^(?:LIT\\\\MCP\\\\Integrations\\\\)?Divi\\\\[A-Za-z0-9_\\\\]+$/'; + if (preg_match($diviClass, $text) === 1 || $text === 'LIT\\MCP\\Integrations\\Divi') { $names[] = $text; } } @@ -124,12 +137,44 @@ final class CoreIndependenceTest extends TestCase return $names; } - /** @return array Path below src/ => absolute path of every PHP file in CORE_PATHS. */ + public function testEveryScannedPathExists(): void + { + $root = self::root(); + foreach ([...self::CORE_PATHS, ...self::CORE_ROOTS] as $directory) { + if (! in_array($directory, self::OPTIONAL_PATHS, true)) { + self::assertDirectoryExists($root . $directory, $directory . ' fehlt – Scan-Liste anpassen'); + } + } + } + + public function testTheDiviUiLivesInTheDiviIntegration(): void + { + $files = self::coreFiles(); + + self::assertArrayHasKey('Plugin.php', $files, 'root files are scanned'); + self::assertArrayHasKey('Integrations/IntegrationRegistry.php', $files, 'Integrations root files are scanned'); + self::assertArrayNotHasKey('Admin/DiviTab.php', $files, 'the Divi tab belongs to Integrations/Divi/Admin'); + self::assertFileExists(self::root() . 'Integrations/Divi/Admin/DiviTab.php'); + self::assertFileExists(self::root() . 'Integrations/Divi/Cli/DiviCheckCommand.php'); + } + + private static function root(): string + { + return dirname(__DIR__, 3) . '/src/'; + } + + /** + * @return array Path below src/ => absolute path of every PHP file in CORE_PATHS (recursively) and + * directly in CORE_ROOTS. + */ private static function coreFiles(): array { - $root = dirname(__DIR__, 3) . '/src/'; + $root = self::root(); $files = []; foreach (self::CORE_PATHS as $directory) { + if (! is_dir($root . $directory)) { + continue; // Only an OPTIONAL_PATHS entry may be missing (testEveryScannedPathExists). + } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($root . $directory, FilesystemIterator::SKIP_DOTS) ); @@ -139,6 +184,11 @@ final class CoreIndependenceTest extends TestCase } } } + foreach (self::CORE_ROOTS as $directory) { + foreach (glob($root . ($directory === '' ? '' : $directory . '/') . '*.php') ?: [] as $path) { + $files[substr($path, strlen($root))] = $path; + } + } ksort($files); return $files; diff --git a/tests/Unit/Admin/DiviTabTest.php b/tests/Unit/Integrations/Divi/Admin/DiviTabTest.php similarity index 99% rename from tests/Unit/Admin/DiviTabTest.php rename to tests/Unit/Integrations/Divi/Admin/DiviTabTest.php index 3a77424..62270d8 100644 --- a/tests/Unit/Admin/DiviTabTest.php +++ b/tests/Unit/Integrations/Divi/Admin/DiviTabTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace LIT\MCP\Tests\Unit\Admin; +namespace LIT\MCP\Tests\Unit\Integrations\Divi\Admin; use Brain\Monkey\Actions; use Brain\Monkey\Functions; use LIT\MCP\Admin\AdminPostAction; -use LIT\MCP\Admin\DiviTab; +use LIT\MCP\Integrations\Divi\Admin\DiviTab; use LIT\MCP\Integrations\Divi\Release\CheckReport; use LIT\MCP\Integrations\Divi\Release\CheckReportStore; use LIT\MCP\Integrations\Divi\Release\CheckStep; diff --git a/tests/Unit/Cli/DiviCheckCommandTest.php b/tests/Unit/Integrations/Divi/Cli/DiviCheckCommandTest.php similarity index 99% rename from tests/Unit/Cli/DiviCheckCommandTest.php rename to tests/Unit/Integrations/Divi/Cli/DiviCheckCommandTest.php index 27fb17c..3098480 100644 --- a/tests/Unit/Cli/DiviCheckCommandTest.php +++ b/tests/Unit/Integrations/Divi/Cli/DiviCheckCommandTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace LIT\MCP\Tests\Unit\Cli; +namespace LIT\MCP\Tests\Unit\Integrations\Divi\Cli; use Brain\Monkey\Functions; -use LIT\MCP\Cli\DiviCheckCommand; +use LIT\MCP\Integrations\Divi\Cli\DiviCheckCommand; use LIT\MCP\Integrations\Divi\Release\CheckReport; use LIT\MCP\Integrations\Divi\Release\CheckReportStore; use LIT\MCP\Integrations\Divi\Release\CheckStep; diff --git a/tests/Unit/Integrations/Divi/DiviIntegrationTest.php b/tests/Unit/Integrations/Divi/DiviIntegrationTest.php index 85d4f8d..be828af 100644 --- a/tests/Unit/Integrations/Divi/DiviIntegrationTest.php +++ b/tests/Unit/Integrations/Divi/DiviIntegrationTest.php @@ -11,7 +11,7 @@ use LIT\MCP\Abilities\Ability; use LIT\MCP\Abilities\ExposurePolicy; use LIT\MCP\Abilities\ToolAvailability; use LIT\MCP\Admin\AdminTab; -use LIT\MCP\Admin\DiviTab; +use LIT\MCP\Integrations\Divi\Admin\DiviTab; use LIT\MCP\Admin\SettingsPage; use LIT\MCP\Integrations\Core\EnvironmentComponent; use LIT\MCP\Integrations\Divi\DiviBuilderDetector;