refactor: rename lib/ to core/ for clearer core-module separation

Rename the top-level lib/ directory to core/ so the project root
immediately communicates which code is core platform and which lives
in modules/. PHP namespaces (MintyPHP\*) are unchanged — only the
Composer PSR-4 path mapping moves from lib/ to core/.

Module-internal lib/ directories (modules/*/lib/) are untouched.

Updated across the full stack:
- composer.json PSR-4 mapping
- bootstrap entry points (web/index.php, bin/*, tests/bootstrap.php)
- tooling configs (phpstan.neon, phpunit.xml, php-cs-fixer)
- 26 architecture contract tests
- enforcement-policy, guard-catalog, quality-gates
- all documentation (CLAUDE.md, README, 25 docs/, .agents/skills/)
- bin/qa-extended.sh search paths
- .claude/settings.local.json permission paths

Workflow: .agents/runs/CORE-LIB-RENAME-001/ (Analyst → Planner →
Executor → Code Review (4 findings fixed) → Security Review →
Acceptance Test → Finalizer)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 23:20:05 +02:00
parent 7c10fadcb9
commit 0e86925464
371 changed files with 191 additions and 192 deletions

View File

@@ -10,7 +10,7 @@ final class AddressBookModuleIsolationContractTest extends TestCase
public function testSearchInfrastructureHasNoAddressBookHardcoding(): void
{
$searchDir = dirname(__DIR__, 2) . '/lib/Support/Search';
$searchDir = dirname(__DIR__, 2) . '/core/Support/Search';
$hits = $this->grepDir($searchDir, '/address.book/i', ['*.php']);
self::assertSame([], $hits, sprintf(
@@ -21,7 +21,7 @@ final class AddressBookModuleIsolationContractTest extends TestCase
public function testSearchConfigHasNoAddressBookHardcoding(): void
{
$file = dirname(__DIR__, 2) . '/lib/Support/SearchConfig.php';
$file = dirname(__DIR__, 2) . '/core/Support/SearchConfig.php';
$content = file_get_contents($file);
self::assertIsString($content);
@@ -32,7 +32,7 @@ final class AddressBookModuleIsolationContractTest extends TestCase
public function testLayoutContextHasNoHardcodedAddressBookBlock(): void
{
$file = dirname(__DIR__, 2) . '/lib/Support/helpers/app.php';
$file = dirname(__DIR__, 2) . '/core/Support/helpers/app.php';
$content = file_get_contents($file);
self::assertIsString($content);
@@ -67,7 +67,7 @@ final class AddressBookModuleIsolationContractTest extends TestCase
public function testCoreNoLongerContainsAddressBookServiceDirectory(): void
{
$coreServiceDir = dirname(__DIR__, 2) . '/lib/Service/AddressBook';
$coreServiceDir = dirname(__DIR__, 2) . '/core/Service/AddressBook';
self::assertDirectoryDoesNotExist(
$coreServiceDir,
'Addressbook service classes should live in modules/addressbook/lib, not in Core lib/Service'

View File

@@ -29,7 +29,7 @@ class ApiResourceContractTest extends TestCase
$this->assertStringContainsString('UserApiWriteInputMapper::class', $usersShow);
$this->assertStringContainsString('->normalize($input)', $usersShow);
$mapper = $this->readProjectFile('lib/Service/User/UserApiWriteInputMapper.php');
$mapper = $this->readProjectFile('core/Service/User/UserApiWriteInputMapper.php');
$this->assertStringContainsString("'tenant_ids' => 'use_tenant_uuids'", $mapper);
$this->assertStringContainsString("'role_ids' => 'use_role_uuids'", $mapper);
$this->assertStringContainsString("'department_ids' => 'use_department_uuids'", $mapper);

View File

@@ -50,7 +50,7 @@ class ApiSchemaContractTest extends TestCase
public function testApiErrorEnvelopeIsCentralizedAndRequestIdAware(): void
{
$apiResponse = $this->readProjectFile('lib/Http/ApiResponse.php');
$apiResponse = $this->readProjectFile('core/Http/ApiResponse.php');
$this->assertStringContainsString("'ok' => false", $apiResponse);
$this->assertStringContainsString("'error_code' => \$normalizedErrorCode", $apiResponse);
$this->assertStringContainsString("'details' => \$normalizedDetails", $apiResponse);

View File

@@ -45,7 +45,7 @@ final class ApiSessionIsolationContractTest extends TestCase
public function testApiBootstrapDoesNotStartSessionOrRememberMeFlow(): void
{
$content = $this->readProjectFile('lib/Http/ApiBootstrap.php');
$content = $this->readProjectFile('core/Http/ApiBootstrap.php');
self::assertStringNotContainsString('Session::start(', $content);
self::assertStringNotContainsString('RememberMe', $content);

View File

@@ -8,7 +8,7 @@ final class AuditModuleIsolationContractTest extends TestCase
{
public function testCoreServiceAuditDirectoryContainsOnlyInterfaces(): void
{
$dir = dirname(__DIR__, 2) . '/lib/Service/Audit';
$dir = dirname(__DIR__, 2) . '/core/Service/Audit';
self::assertDirectoryExists($dir);
$files = array_values(array_filter(
@@ -37,7 +37,7 @@ final class AuditModuleIsolationContractTest extends TestCase
self::assertSame(
$allowed,
$files,
'lib/Service/Audit/ should contain only interfaces and null implementations, found: ' . implode(', ', $files)
'core/Service/Audit/ should contain only interfaces and null implementations, found: ' . implode(', ', $files)
);
}
@@ -45,14 +45,14 @@ final class AuditModuleIsolationContractTest extends TestCase
{
$root = dirname(__DIR__, 2);
self::assertDirectoryDoesNotExist(
$root . '/lib/Repository/Audit',
'Audit repository classes should live in modules/audit/lib, not in Core lib/Repository/Audit'
$root . '/core/Repository/Audit',
'Audit repository classes should live in modules/audit/lib, not in Core core/Repository/Audit'
);
}
public function testCoreDomainTaxonomyHasNoAuditEnums(): void
{
$dir = dirname(__DIR__, 2) . '/lib/Domain/Taxonomy';
$dir = dirname(__DIR__, 2) . '/core/Domain/Taxonomy';
if (!is_dir($dir)) {
self::addToAssertionCount(1);
return;
@@ -90,7 +90,7 @@ final class AuditModuleIsolationContractTest extends TestCase
{
$root = dirname(__DIR__, 2);
self::assertFileDoesNotExist(
$root . '/lib/Http/ApiSystemAuditReporter.php',
$root . '/core/Http/ApiSystemAuditReporter.php',
'ApiSystemAuditReporter should live in modules/audit/lib, not in Core lib/Http'
);
}
@@ -114,7 +114,7 @@ final class AuditModuleIsolationContractTest extends TestCase
{
$root = dirname(__DIR__, 2);
self::assertFileDoesNotExist(
$root . '/lib/Service/Scheduler/Handler/SystemAuditPurgeJobHandler.php',
$root . '/core/Service/Scheduler/Handler/SystemAuditPurgeJobHandler.php',
'SystemAuditPurgeJobHandler should live in modules/audit, not in Core'
);
}

View File

@@ -16,7 +16,7 @@ class AuthzApiBootstrapContractTest extends TestCase
public function testApiBootstrapSupportsOptionalAuthRequirement(): void
{
$content = $this->readProjectFile('lib/Http/ApiBootstrap.php');
$content = $this->readProjectFile('core/Http/ApiBootstrap.php');
$this->assertStringContainsString('public static function init(bool $requireAuth = true): void', $content);
$this->assertStringContainsString('if ($requireAuth) {', $content);
$this->assertStringContainsString('$authenticated = ApiAuth::authenticate();', $content);

View File

@@ -117,7 +117,7 @@ trait AuthzUiContractSupport
$constantName = trim((string) end($parts));
$this->assertNotSame('', $constantName, 'Invalid UiCapabilityMap constant: ' . $mapConstant);
$mapContent = $this->readProjectFile('lib/Service/Access/UiCapabilityMap.php');
$mapContent = $this->readProjectFile('core/Service/Access/UiCapabilityMap.php');
$constantPattern = '/public const\\s+' . preg_quote($constantName, '/') . '\\s*=\\s*\\[(.*?)\\];/s';
$matched = preg_match($constantPattern, $mapContent, $constantMatch);
$this->assertSame(

View File

@@ -36,13 +36,13 @@ class AuthzUiLayoutContractTest extends TestCase
public function testUiAccessServiceUsesCentralLayoutMapDefinition(): void
{
$content = $this->readProjectFile('lib/Service/Access/UiAccessService.php');
$content = $this->readProjectFile('core/Service/Access/UiAccessService.php');
$this->assertStringContainsString('UiCapabilityMap::LAYOUT', $content);
}
public function testCoreCapabilityMapDoesNotHardcodeAuditCapabilities(): void
{
$content = $this->readProjectFile('lib/Service/Access/UiCapabilityMap.php');
$content = $this->readProjectFile('core/Service/Access/UiCapabilityMap.php');
$this->assertStringNotContainsString('can_view_system_audit', $content);
$this->assertStringNotContainsString('can_view_api_audit', $content);
$this->assertStringNotContainsString('can_view_user_lifecycle_audit', $content);

View File

@@ -42,7 +42,7 @@ final class BookmarksModuleIsolationContractTest extends TestCase
{
$root = dirname(__DIR__, 2);
self::assertDirectoryDoesNotExist(
$root . '/lib/Service/Bookmark',
$root . '/core/Service/Bookmark',
'Bookmark service classes should live in modules/bookmarks/lib, not in Core lib/Service'
);
}
@@ -50,7 +50,7 @@ final class BookmarksModuleIsolationContractTest extends TestCase
public function testCoreNoLongerContainsBookmarkRepositoryFiles(): void
{
$root = dirname(__DIR__, 2);
$repoDir = $root . '/lib/Repository/User';
$repoDir = $root . '/core/Repository/User';
if (!is_dir($repoDir)) {
self::addToAssertionCount(1);
return;
@@ -61,7 +61,7 @@ final class BookmarksModuleIsolationContractTest extends TestCase
self::assertSame(
[],
array_values($bookmarkFiles),
'Core lib/Repository/User/ still contains Bookmark files: ' . implode(', ', $bookmarkFiles)
'Core core/Repository/User/ still contains Bookmark files: ' . implode(', ', $bookmarkFiles)
);
}
@@ -69,14 +69,14 @@ final class BookmarksModuleIsolationContractTest extends TestCase
{
$root = dirname(__DIR__, 2);
self::assertFileDoesNotExist(
$root . '/lib/Support/BookmarkUrlNormalizer.php',
$root . '/core/Support/BookmarkUrlNormalizer.php',
'BookmarkUrlNormalizer should live in modules/bookmarks/lib, not in Core lib/Support'
);
}
public function testCoreAuthLifecycleHasNoLegacyBookmarkSessionKeyWrites(): void
{
$file = dirname(__DIR__, 2) . '/lib/Service/Auth/AuthSessionTenantContextService.php';
$file = dirname(__DIR__, 2) . '/core/Service/Auth/AuthSessionTenantContextService.php';
$content = file_get_contents($file);
self::assertIsString($content);
self::assertStringNotContainsString('user_bookmarks', $content);

View File

@@ -72,7 +72,7 @@ final class ConfigContractsTest extends TestCase
private function runtimeSourceFiles(): array
{
$root = $this->projectRootPath();
$scanTargets = ['lib', 'pages', 'templates', 'web'];
$scanTargets = ['core', 'pages', 'templates', 'web'];
$extensions = ['php', 'phtml', 'js'];
$files = [];

View File

@@ -63,7 +63,7 @@ class CoreStarterkitContractTest extends TestCase
];
foreach ($patterns as $pattern => $label) {
$violations = $this->findPatternMatchesInPhpFiles('lib/Service', $pattern);
$violations = $this->findPatternMatchesInPhpFiles('core/Service', $pattern);
$this->assertSame([], $violations, "Superglobal {$label} usage found in lib/Service/:\n" . implode("\n", $violations));
}
}
@@ -139,7 +139,7 @@ class CoreStarterkitContractTest extends TestCase
foreach ($patterns as $pattern => $label) {
$violations = array_merge(
$this->findPatternMatchesInPhpFiles('lib', $pattern),
$this->findPatternMatchesInPhpFiles('core', $pattern),
$this->findPatternMatchesInPhpFiles('pages', $pattern),
$this->findPatternMatchesInPhpFiles('tests', $pattern),
);

View File

@@ -17,14 +17,14 @@ class FileStorageContractTest extends TestCase
/** Services that legitimately write to web/ for favicon delivery. */
private const WEB_WRITE_EXEMPT_FILES = [
'lib/Service/Branding/BrandingFaviconService.php',
'lib/Service/Tenant/TenantFaviconService.php',
'core/Service/Branding/BrandingFaviconService.php',
'core/Service/Tenant/TenantFaviconService.php',
];
public function testNoServiceWritesFilesToWebDirectory(): void
{
$root = $this->projectRootPath();
$serviceDir = $root . '/lib/Service';
$serviceDir = $root . '/core/Service';
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($serviceDir));
$violations = [];

View File

@@ -12,7 +12,7 @@ class FrontendTelemetryContractTest extends TestCase
{
$js = $this->readProjectFile('web/js/core/app-telemetry.js');
$ingest = $this->readProjectFile('modules/audit/lib/Module/Audit/Service/FrontendTelemetryIngestService.php');
$settings = $this->readProjectFile('lib/Service/Settings/SettingsFrontendTelemetryGateway.php');
$settings = $this->readProjectFile('core/Service/Settings/SettingsFrontendTelemetryGateway.php');
$jsMap = $this->parseJsEventKeyToTypeMap($js);
$phpMap = $this->parsePhpEventTypeToKeyMap($ingest);

View File

@@ -40,7 +40,7 @@ class RepositoryInterfaceContractTest extends TestCase
private function collectRepositoryPairs(): array
{
$root = $this->projectRootPath();
$repoDir = $root . '/lib/Repository';
$repoDir = $root . '/core/Repository';
$pairs = [];
@@ -64,9 +64,9 @@ class RepositoryInterfaceContractTest extends TestCase
continue;
}
// Derive PSR-4 class name: lib/Repository/Foo/Bar.php → MintyPHP\Repository\Foo\Bar
// Derive PSR-4 class name: core/Repository/Foo/Bar.php → MintyPHP\Repository\Foo\Bar
$relativePath = ltrim(str_replace($root, '', $file->getPathname()), '/');
$className = 'MintyPHP\\' . str_replace(['/', '.php'], ['\\', ''], substr($relativePath, strlen('lib/')));
$className = 'MintyPHP\\' . str_replace(['/', '.php'], ['\\', ''], substr($relativePath, strlen('core/')));
if (in_array($className, self::EXCLUDED_CLASSES, true)) {
continue;

View File

@@ -27,7 +27,7 @@ class RepositoryLayerIsolationContractTest extends TestCase
public function testCoreRepositoriesDoNotAccessHttpSuperglobals(): void
{
$this->assertRepositoryIsolation('lib/Repository');
$this->assertRepositoryIsolation('core/Repository');
}
public function testModuleRepositoriesDoNotAccessHttpSuperglobals(): void

View File

@@ -12,12 +12,12 @@ class SecurityCryptoContractTest extends TestCase
{
use ProjectFileAssertionSupport;
private const CRYPTO_FILE = 'lib/Support/Crypto.php';
private const CRYPTO_FILE = 'core/Support/Crypto.php';
public function testNoRawOpensslEncryptOutsideCrypto(): void
{
$violations = array_merge(
$this->findPatternMatchesInPhpFiles('lib', '/\bopenssl_encrypt\s*\(/'),
$this->findPatternMatchesInPhpFiles('core', '/\bopenssl_encrypt\s*\(/'),
$this->findPatternMatchesInPhpFiles('pages', '/\bopenssl_encrypt\s*\(/')
);
@@ -36,7 +36,7 @@ class SecurityCryptoContractTest extends TestCase
public function testNoRawOpensslDecryptOutsideCrypto(): void
{
$violations = array_merge(
$this->findPatternMatchesInPhpFiles('lib', '/\bopenssl_decrypt\s*\(/'),
$this->findPatternMatchesInPhpFiles('core', '/\bopenssl_decrypt\s*\(/'),
$this->findPatternMatchesInPhpFiles('pages', '/\bopenssl_decrypt\s*\(/')
);

View File

@@ -11,7 +11,7 @@ final class SecurityLoggingRuntimeContractTest extends TestCase
public function testFrontendTelemetryIngestUsesOnlyRequestScopedErrorLogFallback(): void
{
$violations = array_merge(
$this->findPatternMatchesInPhpFiles('lib', '/\berror_log\s*\(/'),
$this->findPatternMatchesInPhpFiles('core', '/\berror_log\s*\(/'),
$this->findPatternMatchesInPhpFiles('pages', '/\berror_log\s*\(/'),
$this->findPatternMatchesInPhpFiles('modules', '/\berror_log\s*\(/')
);

View File

@@ -16,7 +16,7 @@ class ServiceLayerNoHtmlContractTest extends TestCase
/** @var list<string> */
private const EXCLUDED_FILES = [
'lib/Service/User/UserAccessPdfService.php',
'core/Service/User/UserAccessPdfService.php',
];
/**
@@ -27,8 +27,8 @@ class ServiceLayerNoHtmlContractTest extends TestCase
public function testServicesContainNoHtmlTags(): void
{
$violations = $this->findHtmlViolations('lib/Service');
$violations = array_merge($violations, $this->findHtmlViolations('lib/Http'));
$violations = $this->findHtmlViolations('core/Service');
$violations = array_merge($violations, $this->findHtmlViolations('core/Http'));
self::assertSame(
[],

View File

@@ -16,12 +16,12 @@ final class StatusTaxonomyContractFiles
'modules/audit/lib/Module/Audit/Domain/UserLifecycleTriggerType.php',
'modules/audit/lib/Module/Audit/Domain/UserLifecycleStatus.php',
'modules/audit/lib/Module/Audit/Domain/ImportAuditStatus.php',
'lib/Domain/Taxonomy/ScheduledJobStatus.php',
'lib/Domain/Taxonomy/ScheduledJobRunStatus.php',
'lib/Domain/Taxonomy/ScheduledJobTriggerType.php',
'lib/Domain/Taxonomy/MailLogStatus.php',
'lib/Domain/Taxonomy/TenantStatus.php',
'lib/Domain/Taxonomy/SchedulerRuntimeResult.php',
'core/Domain/Taxonomy/ScheduledJobStatus.php',
'core/Domain/Taxonomy/ScheduledJobRunStatus.php',
'core/Domain/Taxonomy/ScheduledJobTriggerType.php',
'core/Domain/Taxonomy/MailLogStatus.php',
'core/Domain/Taxonomy/TenantStatus.php',
'core/Domain/Taxonomy/SchedulerRuntimeResult.php',
];
}
@@ -85,15 +85,15 @@ final class StatusTaxonomyContractFiles
'modules/audit/lib/Module/Audit/Repository/UserLifecycleAuditRepository.php',
'modules/audit/lib/Module/Audit/Service/ImportAuditService.php',
'modules/audit/lib/Module/Audit/Repository/ImportAuditRunRepository.php',
'lib/Service/Scheduler/SchedulerRunService.php',
'lib/Repository/Scheduler/ScheduledJobRepository.php',
'lib/Repository/Scheduler/ScheduledJobRunRepository.php',
'lib/Repository/Scheduler/SchedulerRuntimeRepository.php',
'lib/Service/Mail/MailService.php',
'lib/Repository/Mail/MailLogRepository.php',
'lib/Service/Tenant/TenantService.php',
'lib/Repository/Tenant/TenantRepository.php',
'lib/Repository/Stats/AdminStatsRepository.php',
'core/Service/Scheduler/SchedulerRunService.php',
'core/Repository/Scheduler/ScheduledJobRepository.php',
'core/Repository/Scheduler/ScheduledJobRunRepository.php',
'core/Repository/Scheduler/SchedulerRuntimeRepository.php',
'core/Service/Mail/MailService.php',
'core/Repository/Mail/MailLogRepository.php',
'core/Service/Tenant/TenantService.php',
'core/Repository/Tenant/TenantRepository.php',
'core/Repository/Stats/AdminStatsRepository.php',
'modules/audit/pages/audit/system-audit/data().php',
'modules/audit/pages/audit/user-lifecycle-audit/data().php',
'modules/audit/pages/audit/import-audit/data().php',

View File

@@ -109,7 +109,7 @@ final class ConsoleApplicationTest extends TestCase
public function testAutoDiscoverFindsCommandsInDirectory(): void
{
$app = new ConsoleApplication();
$commandsDir = dirname(__DIR__, 2) . '/lib/Console/Commands';
$commandsDir = dirname(__DIR__, 2) . '/core/Console/Commands';
$app->discoverCommands($commandsDir);
// Should find at least the 8 commands we have

View File

@@ -20,7 +20,7 @@ final class SearchProviderCollectionTest extends TestCase
if (!($GLOBALS['minty_app_container'] ?? null) instanceof \MintyPHP\App\AppContainer
|| !($GLOBALS['minty_app_container'])->has(ModuleRegistry::class)) {
// Restore the bootstrap container that has the module registry
$container = require dirname(__DIR__, 3) . '/lib/App/registerContainer.php';
$container = require dirname(__DIR__, 3) . '/core/App/registerContainer.php';
$this->pushAppContainer($container);
setAppContainer($container);
}

View File

@@ -3,7 +3,6 @@
namespace MintyPHP\Tests\Unit\Support;
use MintyPHP\Support\Search\SearchQueryNormalizer;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
final class SearchQueryNormalizerTest extends TestCase

View File

@@ -1,6 +1,6 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../lib/Support/helpers.php';
$container = require __DIR__ . '/../lib/App/registerContainer.php';
require __DIR__ . '/../core/Support/helpers.php';
$container = require __DIR__ . '/../core/App/registerContainer.php';
setAppContainer($container);