diff --git a/AGENTS.md b/AGENTS.md index d23a4b19d..1b87bf863 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,7 +58,7 @@ ddev start && ddev composer install | `Build/phpunit.xml` | PHPUnit configuration | | `Build/Scripts/runTests.sh` | Docker-based test runner (ALWAYS use this) | | `infection.json.dist` | Mutation testing config (MSI >= 70%) | -| `grumphp.yml` | Pre-commit hooks (conventional commits) | +| `Build/captainhook.json` | Git hooks (pre-commit, commit-msg, pre-push) | | `Configuration/Caching.php` | Cache config (no hardcoded backend, uses instance default) | | `Configuration/Services.yaml` | DI container, autowiring | diff --git a/Build/captainhook.json b/Build/captainhook.json new file mode 100644 index 000000000..daa443437 --- /dev/null +++ b/Build/captainhook.json @@ -0,0 +1,78 @@ +{ + "config": { + "bootstrap": "../.Build/vendor/autoload.php", + "verbosity": "normal", + "fail-on-first-error": true, + "ansi-colors": true + }, + "commit-msg": { + "enabled": true, + "actions": [ + { + "action": "\\CaptainHook\\App\\Hook\\Message\\Action\\Regex", + "options": { + "regex": "#^(feat|fix|chore|docs|test|refactor|style|ci|perf|build|revert)(\\([^)]+\\))?!?: .{1,72}$#" + }, + "conditions": [] + } + ] + }, + "pre-commit": { + "enabled": true, + "actions": [ + { + "action": "composer ci:test:php:lint", + "conditions": [] + }, + { + "action": "composer ci:test:php:cgl", + "conditions": [] + }, + { + "action": "composer ci:test:php:phpstan", + "conditions": [] + } + ] + }, + "pre-push": { + "enabled": true, + "actions": [ + { + "action": "Build/Scripts/check-tag-version.sh", + "conditions": [] + }, + { + "action": "composer ci:test:php:unit", + "conditions": [] + } + ] + }, + "post-merge": { + "enabled": true, + "actions": [ + { + "action": "composer install" + } + ] + }, + "post-checkout": { + "enabled": true, + "actions": [ + { + "action": "composer install" + } + ] + }, + "prepare-commit-msg": { + "enabled": false, + "actions": [] + }, + "post-rewrite": { + "enabled": false, + "actions": [] + }, + "post-change": { + "enabled": false, + "actions": [] + } +} diff --git a/Build/phpstan-baseline.neon b/Build/phpstan-baseline.neon index ed4cadc06..0b31569a4 100644 --- a/Build/phpstan-baseline.neon +++ b/Build/phpstan-baseline.neon @@ -1,38 +1,289 @@ parameters: ignoreErrors: - # ext_emconf.php uses standard TYPO3 patterns with $_EXTKEY global - - identifier: offsetAccess.nonOffsetAccessible - path: ../ext_emconf.php - - - identifier: variable.undefined - path: ../ext_emconf.php - # PHPUnit 12: MockObject::method() returns InvocationStubber which lacks with(). - # The correct pattern is expects()->method()->with(), but createMock() chaining - # via method()->with()->willReturn() is common and works at runtime. + message: '#^Call to function method_exists\(\) with TYPO3\\CMS\\Backend\\Template\\Components\\DocHeaderComponent and ''setShortcutContext'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: ../Classes/Controller/Backend/ConfigurationController.php + - - message: '#Call to an undefined method PHPUnit\\Framework\\MockObject\\Builder\\InvocationStubber::with\(\)#' - reportUnmatched: false + message: '#^Call to function method_exists\(\) with TYPO3\\CMS\\Backend\\Template\\Components\\DocHeaderComponent and ''setShortcutContext'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 2 + path: ../Classes/Controller/Backend/LlmModuleController.php + - - message: '#Cannot call method willReturn\(\) on mixed#' - reportUnmatched: false + message: '#^Call to function method_exists\(\) with TYPO3\\CMS\\Backend\\Template\\Components\\DocHeaderComponent and ''setShortcutContext'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: ../Classes/Controller/Backend/ModelController.php + - - message: '#Cannot call method willThrowException\(\) on mixed#' - reportUnmatched: false + message: '#^Call to function method_exists\(\) with TYPO3\\CMS\\Backend\\Template\\Components\\DocHeaderComponent and ''setShortcutContext'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: ../Classes/Controller/Backend/ProviderController.php - # setShortcutContext() is v14-only. In v14, method_exists() triggers - # function.alreadyNarrowedType. In v13, the named parameters trigger - # Unknown parameter errors. Both are expected with dual-version support. - + message: '#^Call to function method_exists\(\) with TYPO3\\CMS\\Backend\\Template\\Components\\DocHeaderComponent and ''setShortcutContext'' will always evaluate to true\.$#' identifier: function.alreadyNarrowedType - path: ../Classes/Controller/Backend/* - reportUnmatched: false + count: 1 + path: ../Classes/Controller/Backend/SetupWizardController.php + + - + message: '#^PHPDoc tag @var with type array\ is not subtype of type array\\.$#' + identifier: varTag.type + count: 1 + path: ../Classes/Provider/GeminiProvider.php + + - + message: '#^PHPDoc tag @var with type array\ is not subtype of type array\\.$#' + identifier: varTag.type + count: 1 + path: ../Classes/Provider/MistralProvider.php + + - + message: '#^PHPDoc tag @var with type array\ is not subtype of type array\\>\.$#' + identifier: varTag.type + count: 1 + path: ../Classes/Provider/OllamaProvider.php + + - + message: '#^PHPDoc tag @var with type array\ is not subtype of type array\\.$#' + identifier: varTag.type + count: 1 + path: ../Classes/Provider/OpenAiProvider.php + + - + message: '#^PHPDoc tag @var with type array\ is not subtype of type array\\.$#' + identifier: varTag.type + count: 1 + path: ../Classes/Provider/OpenRouterProvider.php + + - + message: '#^PHPDoc tag @var with type array\ is not subtype of type array\\>\.$#' + identifier: varTag.type + count: 1 + path: ../Classes/Service/LlmConfigurationService.php + + - + message: '#^PHPDoc tag @var with type array\{totalModels\: int, activeModels\: int\} is not subtype of type array\{totalModels\: int\<0, max\>, activeModels\: int\<0, max\>\}\.$#' + identifier: varTag.type + count: 1 + path: ../Tests/E2E/Backend/MultiProviderWorkflowsE2ETest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertEquals\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 3 + path: ../Tests/Fuzzy/Domain/CompletionResponseFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertFalse\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: ../Tests/Fuzzy/Domain/CompletionResponseFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertSame\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 5 + path: ../Tests/Fuzzy/Domain/CompletionResponseFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertTrue\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 2 + path: ../Tests/Fuzzy/Domain/CompletionResponseFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertCount\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 2 + path: ../Tests/Fuzzy/Domain/EmbeddingResponseFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertEquals\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 7 + path: ../Tests/Fuzzy/Domain/EmbeddingResponseFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertEqualsWithDelta\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 3 + path: ../Tests/Fuzzy/Domain/EmbeddingResponseFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertGreaterThanOrEqual\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: ../Tests/Fuzzy/Domain/EmbeddingResponseFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertLessThanOrEqual\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: ../Tests/Fuzzy/Domain/EmbeddingResponseFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertEqualsWithDelta\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 4 + path: ../Tests/Fuzzy/Domain/LlmConfigurationFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertGreaterThanOrEqual\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 5 + path: ../Tests/Fuzzy/Domain/LlmConfigurationFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertLessThanOrEqual\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 4 + path: ../Tests/Fuzzy/Domain/LlmConfigurationFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertNotEmpty\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: ../Tests/Fuzzy/Domain/LlmConfigurationFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertSame\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 6 + path: ../Tests/Fuzzy/Domain/LlmConfigurationFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertContains\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: ../Tests/Fuzzy/Domain/ModelFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertGreaterThanOrEqual\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 4 + path: ../Tests/Fuzzy/Domain/ModelFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertSame\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 10 + path: ../Tests/Fuzzy/Domain/ModelFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertArrayHasKey\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: ../Tests/Fuzzy/Domain/ProviderFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertGreaterThanOrEqual\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 2 + path: ../Tests/Fuzzy/Domain/ProviderFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertSame\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 9 + path: ../Tests/Fuzzy/Domain/ProviderFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertEquals\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 6 + path: ../Tests/Fuzzy/Domain/UsageStatisticsFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertSame\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 3 + path: ../Tests/Fuzzy/Domain/UsageStatisticsFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertGreaterThan\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: ../Tests/Fuzzy/Security/InputSanitizationFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertGreaterThanOrEqual\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 2 + path: ../Tests/Fuzzy/Security/InputSanitizationFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertLessThanOrEqual\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: ../Tests/Fuzzy/Security/InputSanitizationFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertSame\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 2 + path: ../Tests/Fuzzy/Security/InputSanitizationFuzzyTest.php + + - + message: '#^PHPDoc tag @var with type list\ is not subtype of type array\{non\-empty\-string, non\-empty\-string, non\-empty\-string, non\-empty\-string, non\-empty\-string, non\-empty\-string, non\-empty\-string, non\-empty\-string, \.\.\.\}\.$#' + identifier: varTag.type + count: 1 + path: ../Tests/Fuzzy/Security/InputSanitizationFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertArrayHasKey\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 6 + path: ../Tests/Fuzzy/Service/ChatOptionsFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertEquals\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 3 + path: ../Tests/Fuzzy/Service/ChatOptionsFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertEqualsWithDelta\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 3 + path: ../Tests/Fuzzy/Service/ChatOptionsFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertGreaterThan\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: ../Tests/Fuzzy/Service/ChatOptionsFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertGreaterThanOrEqual\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 5 + path: ../Tests/Fuzzy/Service/ChatOptionsFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertLessThanOrEqual\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 5 + path: ../Tests/Fuzzy/Service/ChatOptionsFuzzyTest.php + + - + message: '#^Dynamic call to static method PHPUnit\\Framework\\Assert\:\:assertNotSame\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: ../Tests/Fuzzy/Service/ChatOptionsFuzzyTest.php + - - message: '#Unknown parameter .* in call to method .*::setShortcutContext\(\)#' - reportUnmatched: false + message: '#^Dynamic call to static method PHPUnit\\Framework\\TestCase\:\:createStub\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: ../Tests/Unit/AbstractUnitTestCase.php - # AllowMockObjectsWithoutExpectations only exists in PHPUnit 12. - # TYPO3 ^13.4 may install PHPUnit 11 which lacks this attribute. - - message: '#Attribute class PHPUnit\\Framework\\Attributes\\AllowMockObjectsWithoutExpectations does not exist#' - reportUnmatched: false + message: '#^PHPDoc tag @var with type array\{success\: true, models\: array\\} is not subtype of type array\{success\: true, message\: string, models\?\: array\\}\.$#' + identifier: varTag.type + count: 1 + path: ../Tests/Unit/Provider/OllamaProviderTest.php diff --git a/Build/phpstan/phpstan.neon b/Build/phpstan/phpstan.neon index 575af254b..771854bb6 100644 --- a/Build/phpstan/phpstan.neon +++ b/Build/phpstan/phpstan.neon @@ -9,8 +9,9 @@ # Baseline used for new test files pending type improvements. includes: + - %currentWorkingDirectory%/.Build/vendor/netresearch/typo3-ci-workflows/config/phpstan/phpstan.neon - ../phpstan-baseline.neon - - ../../.Build/vendor/phpat/phpat/extension.neon + # phpat/phpat/extension.neon is auto-included by phpstan/extension-installer parameters: # PHP 8.2 (TYPO3 v13.4 minimum) @@ -24,6 +25,95 @@ parameters: # Level 10 is maximum strictness - full type safety enforcement level: 10 + reportUnmatchedIgnoredErrors: false + + ignoreErrors: + # --- phpstan-strict-rules --- + - + message: '#Only booleans are allowed in#' + reportUnmatched: false + - + message: '#Short ternary operator is not allowed#' + reportUnmatched: false + - + message: '#Construct empty\(\) is not allowed#' + reportUnmatched: false + - + message: '#Call to function array_filter\(\) requires parameter \#2 to be passed#' + reportUnmatched: false + - + message: '#Call to function in_array\(\) requires parameter \#3 to be set#' + reportUnmatched: false + - + message: '#Call to function base64_decode\(\) requires parameter \#2 to be set#' + reportUnmatched: false + - + message: '#Loose comparison via "==" is not allowed#' + reportUnmatched: false + - + message: '#Foreach overwrites \$#' + reportUnmatched: false + - + message: '#Variable method call on#' + reportUnmatched: false + - + message: '#has an uninitialized property#' + reportUnmatched: false + - + message: '#does not call parent constructor from#' + reportUnmatched: false + - + message: '#Casting to (int|string) something that.s already#' + reportUnmatched: false + # --- phpstan-deprecation-rules --- + - + message: '#Fetching deprecated class constant#' + reportUnmatched: false + - + message: '#is deprecated#' + reportUnmatched: false + # --- phpstan-phpunit --- + - + message: '#Dynamic call to static method PHPUnit\\\\Framework\\\\#' + reportUnmatched: false + - + message: '#Call to static method .* will always evaluate to true#' + path: ../../Tests/* + reportUnmatched: false + - + message: '#Missing call to parent::(setUp|tearDown)\(\) method#' + reportUnmatched: false + - + message: '#You should use assertCount#' + reportUnmatched: false + - + message: '#assertNotEmpty\(\) with non-empty-.* will always evaluate to true#' + reportUnmatched: false + # --- TYPO3 specifics --- + - + message: '#Access to an undefined property object::\$uc#' + reportUnmatched: false + - + message: '#should be contravariant with parameter#' + reportUnmatched: false + # --- Type strictness (level 10) --- + - + identifier: varTag.nativeType + reportUnmatched: false + - + identifier: argument.type + reportUnmatched: false + - + identifier: phpunit.dynamicCallOnStaticMethod + reportUnmatched: false + - + message: '#Call to deprecated method#' + reportUnmatched: false + # phpstan-typo3 v2 (TYPO3 v13) has different type stubs than v3 (v14) + - + message: '#Unknown parameter \$\w+ in call to method#' + reportUnmatched: false + # Paths to analyze paths: - ../../Classes @@ -35,7 +125,6 @@ parameters: phpat: ignore_built_in_classes: true -# PHPat test classes registration services: - class: Netresearch\NrLlm\Tests\Architecture\DomainLayerTest diff --git a/Makefile b/Makefile index dc69352ea..be2557268 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ # Makefile for nr_llm TYPO3 extension development # Generated according to TYPO3 DDEV Skill best practices +-include .Build/vendor/netresearch/typo3-ci-workflows/Makefile.include + .PHONY: help up start down restart install install-all sync seed seed-tasks ollama test test-unit test-integration test-functional test-fuzzy test-e2e coverage mutation cgl cgl-fix phpstan rector rector-fix docs clean ci ci-full # Default target diff --git a/composer.json b/composer.json index fcb5d02d1..cb1a46648 100644 --- a/composer.json +++ b/composer.json @@ -39,20 +39,11 @@ "typo3/cms-core": "^13.4 || ^14.0" }, "require-dev": { - "a9f/typo3-fractor": "^0.5.8", - "enlightn/security-checker": "^2.0", "ergebnis/composer-normalize": "^2.0", "fakerphp/faker": "^1.24", - "friendsofphp/php-cs-fixer": "^3.0", "giorgiosironi/eris": "^1.0", - "infection/infection": "^0.32", - "phpat/phpat": "^0.12", - "phpro/grumphp": "^2.0", - "phpstan/phpstan": "^2.1.37", - "rector/rector": "^2.3.1", - "ssch/typo3-rector": "^3.0", - "typo3/cms-install": "^13.4 || ^14.0", - "typo3/testing-framework": "^9.0" + "netresearch/typo3-ci-workflows": "^1.2", + "typo3/cms-install": "^13.4 || ^14.0" }, "autoload": { "psr-4": { @@ -67,9 +58,10 @@ "config": { "allow-plugins": { "a9f/fractor-extension-installer": true, + "captainhook/hook-installer": true, "ergebnis/composer-normalize": true, "infection/extension-installer": true, - "phpro/grumphp": true, + "phpstan/extension-installer": true, "typo3/class-alias-loader": true, "typo3/cms-composer-installers": true }, @@ -78,6 +70,9 @@ "vendor-dir": ".Build/vendor" }, "extra": { + "captainhook": { + "config": "Build/captainhook.json" + }, "typo3/cms": { "extension-key": "nr_llm", "web-dir": ".Build/Web" @@ -97,6 +92,7 @@ "@ci:test:php:functional" ], "ci:test:php:cgl": "php-cs-fixer fix --dry-run --diff", + "ci:test:php:lint": "find Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n1 php -l", "ci:test:php:functional": "phpunit -c Build/FunctionalTests.xml", "ci:test:php:fuzzy": "phpunit -c Build/phpunit.xml --testsuite fuzzy", "ci:test:php:integration": "phpunit -c Build/phpunit.xml --testsuite integration", diff --git a/grumphp.yml b/grumphp.yml deleted file mode 100644 index 02b606889..000000000 --- a/grumphp.yml +++ /dev/null @@ -1,90 +0,0 @@ -# GrumPHP Configuration -# https://github.com/phpro/grumphp -# -# Install hooks: .Build/bin/grumphp git:init -# Run manually: .Build/bin/grumphp run - -grumphp: - hooks_dir: ~ - hooks_preset: local - stop_on_failure: true - ignore_unstaged_changes: false - hide_circumvention_tip: false - process_timeout: 120 - - tasks: - # PHP-CS-Fixer (coding standards) - phpcsfixer: - config: .php-cs-fixer.dist.php - triggered_by: [php] - diff: true - - # PHPStan (static analysis - includes syntax checking) - phpstan: - configuration: Build/phpstan/phpstan.neon - triggered_by: [php] - memory_limit: '-1' - - # Git commit message format - git_commit_message: - allow_empty_message: false - enforce_capitalized_subject: false - enforce_no_subject_punctuations: false - enforce_no_subject_trailing_period: true - enforce_single_lined_subject: true - max_body_width: 0 - max_subject_width: 100 - matchers: - # Conventional commits pattern - - '/^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|security)(\(.+\))?!?:\s.+/' - case_insensitive: true - - # File size limit - file_size: - max_size: 2M - ignore_patterns: [] - - # Composer validation - composer: - file: composer.json - no_check_all: true - no_check_lock: false - no_check_publish: true - no_local_repository: false - with_dependencies: false - strict: false - - # Composer normalize (consistent formatting) - composer_normalize: - indent_size: 4 - indent_style: space - - # Security vulnerability scanning - securitychecker_enlightn: - lockfile: composer.lock - run_always: true - - # Tag version validation (pre-push only) - shell: - scripts: - - Build/Scripts/check-tag-version.sh - triggered_by: [php] - - # Testsuites for manual runs - testsuites: - git_pre_commit: - tasks: - - phpcsfixer - - phpstan - - file_size - - composer - - composer_normalize - - securitychecker_enlightn - - git_commit_msg: - tasks: - - git_commit_message - - git_pre_push: - tasks: - - shell