testIsOptionalFiles($path); } }; } public static function classificationProvider(): iterable { // 可选(true) 12 例:既有规则覆盖 8 例 + .agents 项目自维护新规则 4 例 yield 'optional: root AGENTS.md' => ['AGENTS.md', true]; yield 'optional: root composer.json' => ['composer.json', true]; yield 'optional: root .php-cs-fixer.php' => ['.php-cs-fixer.php', true]; yield 'optional: app prefix' => ['app/common/service/X.php', true]; yield 'optional: config prefix' => ['config/app.php', true]; yield 'optional: route prefix' => ['route/app.php', true]; yield 'optional: source/docker prefix' => ['source/docker/Dockerfile', true]; yield 'optional: extend/think prefix' => ['extend/think/store/X.php', true]; yield 'optional: .agents/PROJECT.md exact' => ['.agents/PROJECT.md', true]; yield 'optional: project rule flat' => ['.agents/rules/project-dev-runtime-deploy.md', true]; yield 'optional: project rule flat 2' => ['.agents/rules/project-account-system.md', true]; yield 'optional: project rule nested' => ['.agents/rules/project-deploy-detail/sub.md', true]; // 强制(false) 10 例:核心入口、框架内核、静态资源、迁移、.agents 其余内容 yield 'force: think entry' => ['think', false]; yield 'force: extend/base kernel' => ['extend/base/admin/service/AdminUpdateServiceBase.php', false]; yield 'force: public static' => ['public/static/common/css/a.css', false]; yield 'force: database migrations' => ['database/migrations/2024.php', false]; yield 'force: ulthon rule' => ['.agents/rules/ulthon-naming-convention.md', false]; yield 'force: ulthon skill' => ['.agents/skills/ulthon-timer/SKILL.md', false]; yield 'force: project skill (skills 始终强制)' => ['.agents/skills/project-x/SKILL.md', false]; yield 'force: project.md 无连字符不匹配前缀' => ['.agents/rules/project.md', false]; yield 'force: .agents/PROJECT 非精确不匹配' => ['.agents/PROJECT/about.md', false]; yield 'force: .agents/rules/.README' => ['.agents/rules/.README', false]; } #[\PHPUnit\Framework\Attributes\DataProvider('classificationProvider')] public function test_classification(string $path, bool $expectedOptional): void { $exposer = $this->makeExposer(); $this->assertSame( $expectedOptional, $exposer->expose($path), sprintf( '路径 %s 分类不符:预期%s,实际%s', $path, $expectedOptional ? '可选(true)' : '强制(false)', $expectedOptional ? '强制(false)' : '可选(true)' ) ); } }