refactor: Divi-Reiter und divi-check in die Divi-Integration verschoben; CoreIndependenceTest prüft auch Cli, ChangeLog, Pods und die Wurzeldateien, erkennt Alias-Importe und kennt nur noch Plugin.php als Ausnahme

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-18 22:55:02 +02:00
co-authored by Claude Opus 5.5
parent d0d3488df8
commit 7f9a087b8b
10 changed files with 84 additions and 28 deletions
@@ -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 - **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 (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. 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` - **Kern ohne Divi:** `CoreIndependenceTest` erzwingt, dass `Abilities`, `Admin`, `ChangeLog`, `Cli`, `Guides`,
(außer `DiviTab`), `Guides` und `Server` nichts aus `Integrations\Divi` importieren. `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 ## Ergänzungen bei der Umsetzung
@@ -2,9 +2,12 @@
declare(strict_types=1); declare(strict_types=1);
namespace LIT\MCP\Admin; namespace LIT\MCP\Integrations\Divi\Admin;
use Closure; 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\ChangeLog\ChangeLog;
use LIT\MCP\Integrations\Divi\Release\CheckReport; use LIT\MCP\Integrations\Divi\Release\CheckReport;
use LIT\MCP\Integrations\Divi\Release\CheckReportStore; use LIT\MCP\Integrations\Divi\Release\CheckReportStore;
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace LIT\MCP\Cli; namespace LIT\MCP\Integrations\Divi\Cli;
use Closure; use Closure;
use LIT\MCP\Integrations\Divi\Release\CheckReport; use LIT\MCP\Integrations\Divi\Release\CheckReport;
+2 -2
View File
@@ -7,11 +7,11 @@ namespace LIT\MCP\Integrations\Divi;
use Closure; use Closure;
use LIT\MCP\Abilities\Ability; use LIT\MCP\Abilities\Ability;
use LIT\MCP\Abilities\ToolAvailability; use LIT\MCP\Abilities\ToolAvailability;
use LIT\MCP\Admin\DiviTab;
use LIT\MCP\Admin\SettingsPage; use LIT\MCP\Admin\SettingsPage;
use LIT\MCP\ChangeLog\ChangeLog; use LIT\MCP\ChangeLog\ChangeLog;
use LIT\MCP\Cli\DiviCheckCommand;
use LIT\MCP\Integrations\Bootable; 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\BlockTree\Parser;
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader; use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter; use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
@@ -5,8 +5,8 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Release; namespace LIT\MCP\Integrations\Divi\Release;
use Closure; use Closure;
use LIT\MCP\Admin\DiviTab;
use LIT\MCP\Admin\SettingsPage; use LIT\MCP\Admin\SettingsPage;
use LIT\MCP\Integrations\Divi\Admin\DiviTab;
use LIT\MCP\Log; use LIT\MCP\Log;
use Throwable; use Throwable;
use WP_REST_Response; use WP_REST_Response;
+3 -3
View File
@@ -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). # 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; NOTICE=$(wp --user="$ADMIN_LOGIN" eval '$tab = null;
foreach (apply_filters(LIT\MCP\Admin\SettingsPage::TABS_FILTER, []) as $candidate) { 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; $tab = $candidate;
} }
} }
@@ -177,7 +177,7 @@ TAB=$(wp --user="$ADMIN_LOGIN" eval '
require_once ABSPATH . "wp-admin/includes/template.php"; require_once ABSPATH . "wp-admin/includes/template.php";
$tab = null; $tab = null;
foreach (apply_filters(LIT\MCP\Admin\SettingsPage::TABS_FILTER, []) as $candidate) { 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; $tab = $candidate;
} }
} }
@@ -198,7 +198,7 @@ echo "$STATUS" | jq -e '.components[] | select(.id == "divi") | .features | all(
|| fail "every feature must be released again: $STATUS" || fail "every feature must be released again: $STATUS"
[ -z "$(wp --user="$ADMIN_LOGIN" eval '$tab = null; [ -z "$(wp --user="$ADMIN_LOGIN" eval '$tab = null;
foreach (apply_filters(LIT\MCP\Admin\SettingsPage::TABS_FILTER, []) as $candidate) { 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; $tab = $candidate;
} }
} }
@@ -10,27 +10,37 @@ use RecursiveDirectoryIterator;
use RecursiveIteratorIterator; use RecursiveIteratorIterator;
/** /**
* The generic core knows no Divi class: nothing below the CORE_PATHS imports or names a class of * The generic core knows no Divi class: nothing below the CORE_PATHS and none of the CORE_ROOTS files imports or names
* LIT\MCP\Integrations\Divi. Divi plugs in through ports (AbilityGuard, ToolRefusal, BuilderDetector, * 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 * 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 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 = [ private const CORE_PATHS = [
'Abilities', 'Abilities',
'Integrations/Core',
'Integrations/WordPress',
'Integrations/Media',
'Admin', 'Admin',
'ChangeLog',
'Cli',
'Guides', 'Guides',
'Integrations/Core',
'Integrations/Media',
'Integrations/Pods',
'Integrations/WordPress',
'Server', '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 = [ 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 public function testTheCoreUsesNoDiviClass(): void
@@ -63,7 +73,7 @@ final class CoreIndependenceTest extends TestCase
} }
} }
public function testTheScanFindsImportsQualifiedNamesGroupUsesAndClassStrings(): void public function testTheScanFindsImportsQualifiedNamesGroupUsesAliasesAndClassStrings(): void
{ {
$source = <<<'PHP' $source = <<<'PHP'
<?php <?php
@@ -72,11 +82,13 @@ final class CoreIndependenceTest extends TestCase
$a = \LIT\MCP\Integrations\Divi\Schema\DiviModules::isAvailable(); $a = \LIT\MCP\Integrations\Divi\Schema\DiviModules::isAvailable();
$b = 'LIT\\MCP\\Integrations\\Divi\\Design\\DesignSystemReader'; $b = 'LIT\\MCP\\Integrations\\Divi\\Design\\DesignSystemReader';
$c = "LIT\\MCP\\Integrations\\Divi\\Library\\LibraryAccess"; $c = "LIT\\MCP\\Integrations\\Divi\\Library\\LibraryAccess";
use LIT\MCP\Integrations\Divi as D;
PHP; PHP;
self::assertSame( self::assertSame(
[ [
'Divi\Pages\DiviPages', 'Divi\Pages\DiviPages',
'LIT\MCP\Integrations\Divi',
'LIT\MCP\Integrations\Divi\Design\DesignSystemReader', 'LIT\MCP\Integrations\Divi\Design\DesignSystemReader',
'LIT\MCP\Integrations\Divi\Library\LibraryAccess', 'LIT\MCP\Integrations\Divi\Library\LibraryAccess',
'LIT\MCP\Integrations\Divi\Release\ReleaseGate', 'LIT\MCP\Integrations\Divi\Release\ReleaseGate',
@@ -114,7 +126,8 @@ final class CoreIndependenceTest extends TestCase
T_CONSTANT_ENCAPSED_STRING => ltrim(str_replace('\\\\', '\\', substr($token[1], 1, -1)), '\\'), T_CONSTANT_ENCAPSED_STRING => ltrim(str_replace('\\\\', '\\', substr($token[1], 1, -1)), '\\'),
default => '', 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; $names[] = $text;
} }
} }
@@ -124,12 +137,44 @@ final class CoreIndependenceTest extends TestCase
return $names; return $names;
} }
/** @return array<string, string> 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<string, string> Path below src/ => absolute path of every PHP file in CORE_PATHS (recursively) and
* directly in CORE_ROOTS.
*/
private static function coreFiles(): array private static function coreFiles(): array
{ {
$root = dirname(__DIR__, 3) . '/src/'; $root = self::root();
$files = []; $files = [];
foreach (self::CORE_PATHS as $directory) { foreach (self::CORE_PATHS as $directory) {
if (! is_dir($root . $directory)) {
continue; // Only an OPTIONAL_PATHS entry may be missing (testEveryScannedPathExists).
}
$iterator = new RecursiveIteratorIterator( $iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root . $directory, FilesystemIterator::SKIP_DOTS) 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); ksort($files);
return $files; return $files;
@@ -2,12 +2,12 @@
declare(strict_types=1); 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\Actions;
use Brain\Monkey\Functions; use Brain\Monkey\Functions;
use LIT\MCP\Admin\AdminPostAction; 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\CheckReport;
use LIT\MCP\Integrations\Divi\Release\CheckReportStore; use LIT\MCP\Integrations\Divi\Release\CheckReportStore;
use LIT\MCP\Integrations\Divi\Release\CheckStep; use LIT\MCP\Integrations\Divi\Release\CheckStep;
@@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Cli; namespace LIT\MCP\Tests\Unit\Integrations\Divi\Cli;
use Brain\Monkey\Functions; 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\CheckReport;
use LIT\MCP\Integrations\Divi\Release\CheckReportStore; use LIT\MCP\Integrations\Divi\Release\CheckReportStore;
use LIT\MCP\Integrations\Divi\Release\CheckStep; use LIT\MCP\Integrations\Divi\Release\CheckStep;
@@ -11,7 +11,7 @@ use LIT\MCP\Abilities\Ability;
use LIT\MCP\Abilities\ExposurePolicy; use LIT\MCP\Abilities\ExposurePolicy;
use LIT\MCP\Abilities\ToolAvailability; use LIT\MCP\Abilities\ToolAvailability;
use LIT\MCP\Admin\AdminTab; 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\Admin\SettingsPage;
use LIT\MCP\Integrations\Core\EnvironmentComponent; use LIT\MCP\Integrations\Core\EnvironmentComponent;
use LIT\MCP\Integrations\Divi\DiviBuilderDetector; use LIT\MCP\Integrations\Divi\DiviBuilderDetector;