Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:43:13 +08:00
commit f6d4a68978
178 changed files with 51030 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
{
"name": "netresearch-skills-bundle",
"description": "Temporary container plugin for skill visibility workaround (Claude Code issue #10568)",
"version": "0.0.0-2025.11.28",
"author": {
"name": "Netresearch DTT GmbH",
"email": "info@netresearch.de"
},
"skills": [
"./skills/typo3-docs",
"./skills/typo3-testing",
"./skills/typo3-ddev",
"./skills/typo3-core-contributions",
"./skills/typo3-conformance",
"./skills/netresearch-branding",
"./skills/agents"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# netresearch-skills-bundle
Temporary container plugin for skill visibility workaround (Claude Code issue #10568)

744
plugin.lock.json Normal file
View File

@@ -0,0 +1,744 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:netresearch/claude-code-marketplace:netresearch-skills-bundle",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "1e84fc21f2f1f1032570653f85f86f4214bbffb1",
"treeHash": "2dac5be64a7d70361a08099a0ed7fd471693e4f6b4405d6c25731205d9e9b3ba",
"generatedAt": "2025-11-28T10:27:20.800627Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "netresearch-skills-bundle",
"description": "Temporary container plugin for skill visibility workaround (Claude Code issue #10568)"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "02b0131ab35e7f80600c283a7b93f2d134f59068fed4b227ba367059a4af65d7"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "588cdc173df1df42e067d518fed06e6fc6424c7c6bf60cd093e7bc7314713024"
},
{
"path": "skills/typo3-testing/LICENSE",
"sha256": "62e5db1ab91125b874e6408a65301f10802d8204c06b3047ec07f84d7db3f471"
},
{
"path": "skills/typo3-testing/README.md",
"sha256": "0ffd450890029269e5dae36dbabb6c6fcd2c2ef469dfc326058945af4bba6a26"
},
{
"path": "skills/typo3-testing/.gitignore",
"sha256": "db6cc676b541ea3d6987d9e5d662e998f36a146eba3abd14b5e65cab025fc389"
},
{
"path": "skills/typo3-testing/SKILL.md",
"sha256": "8d3779f65441ab80e7c3c5f3b6f4cdee483e9d1eced56a7820330cccb79ad488"
},
{
"path": "skills/typo3-testing/references/quality-tools.md",
"sha256": "d186766c200a72755ea9456677a8a26632b6fa5093aad847f9c4463ea56aac15"
},
{
"path": "skills/typo3-testing/references/test-runners.md",
"sha256": "ea26c2ddcc6abc23a5206665aa39c053250d09cc21c3b172f9c08f78727f4017"
},
{
"path": "skills/typo3-testing/references/acceptance-testing.md",
"sha256": "2eb3373a49f4bd584d6af026406fe89b77bf53eaf6250a5e739036fe681152b1"
},
{
"path": "skills/typo3-testing/references/functional-testing.md",
"sha256": "7a5f94e262f76b533328e6522cfce2730e03c5d4718b20dc82bf9427e59bca4f"
},
{
"path": "skills/typo3-testing/references/unit-testing.md",
"sha256": "d284a11aea0f9f74c65de21028ad1e0312a23d8f2b4a6f02d71383908a8f7a24"
},
{
"path": "skills/typo3-testing/references/javascript-testing.md",
"sha256": "d912bf3a55c3fde7d5b35dd6b8d217a2c92dc56cbce2aa833b97cbf4f930c090"
},
{
"path": "skills/typo3-testing/references/ci-cd.md",
"sha256": "cc63f64bb0b3cc59e08c145a27e5f4a22a74ffb23c7d07d11b43276e60872fb5"
},
{
"path": "skills/typo3-testing/claudedocs/refactoring-summary.md",
"sha256": "23e8c66839a7c3dc5f277a82eccb2d74dcea57ebf992ca3e01a519270613e7f5"
},
{
"path": "skills/typo3-testing/scripts/validate-setup.sh",
"sha256": "c44fa4a6b8b0e88e6d2bbbdd4415279635a6478dd9f1e599cb27f890fef8ef92"
},
{
"path": "skills/typo3-testing/scripts/setup-testing.sh",
"sha256": "e19c01d4c34abd3baa6afd6674e15095d1666909bac602273ac666f34a50e1db"
},
{
"path": "skills/typo3-testing/scripts/generate-test.sh",
"sha256": "e72909650ac0cc12e9483e0ec7a84da863dce99882fa12410035460fc1c93cd3"
},
{
"path": "skills/typo3-testing/templates/FunctionalTestsBootstrap.php",
"sha256": "60caec69cc3792c3f22184c627eb6f597e57a387c1646e2277148e877f66aa0c"
},
{
"path": "skills/typo3-testing/templates/UnitTests.xml",
"sha256": "61cd16d938ba8fbb7d49c21a444fb86766ce65449f66e3d9a12135451a174786"
},
{
"path": "skills/typo3-testing/templates/github-actions-tests.yml",
"sha256": "8da189186487e10e5d5ddf0c51302138c169220a2c11306c4eab345230853d41"
},
{
"path": "skills/typo3-testing/templates/AGENTS.md",
"sha256": "9a0c9055ce3404dca8f3913515de25698030855e5583367450aae2eb22c56e17"
},
{
"path": "skills/typo3-testing/templates/FunctionalTests.xml",
"sha256": "8b73dedafa787328d263b46661bb4df91850e60ed12d2a33c5f493b828e79f9a"
},
{
"path": "skills/typo3-testing/templates/docker/codeception.yml",
"sha256": "85c24f53e5a1f7e959312d141fd62261f0440be3ca70a45896082e0b5353dd2b"
},
{
"path": "skills/typo3-testing/templates/docker/docker-compose.yml",
"sha256": "2693efec58eb5ddebd218d2a452581f21c02397fa214fea3ae46e677516881dd"
},
{
"path": "skills/typo3-testing/templates/example-tests/ExampleAcceptanceCest.php",
"sha256": "30e158149508c15ecb75de850d8b6f9b09baad0014d6ab0183aba249dbd03d10"
},
{
"path": "skills/typo3-testing/templates/example-tests/ExampleFunctionalTest.php",
"sha256": "81de444c741d8f05ef9abba8b005d32474da9b77a0a0aefe559350acd5f2b428"
},
{
"path": "skills/typo3-testing/templates/example-tests/ExampleUnitTest.php",
"sha256": "2c68c617a1c70f8a896c252c4aa0ed5578af5bcc7c0e7cc49257866da05fb11c"
},
{
"path": "skills/netresearch-branding/README.md",
"sha256": "5dfbc0502098d2c9d44de543a6b62a82e4c34bc34c5d6d2cfede71c21622150c"
},
{
"path": "skills/netresearch-branding/SKILL.md",
"sha256": "4dbc3494d69303bf5be910168bd991bfffa75c0d4f0606274e333f4f1349a032"
},
{
"path": "skills/netresearch-branding/references/colors.md",
"sha256": "42fbf46dc515b079a250727b8881dde7541400d1777eccfd07b910c6a43328f8"
},
{
"path": "skills/netresearch-branding/references/web-design.md",
"sha256": "2016875af8aaca78eff4121e107871339d79441d0e23d4ff3f148dff3fc12dd9"
},
{
"path": "skills/netresearch-branding/references/typography.md",
"sha256": "a2484d7416e8f35e1db48dc694fe56945819951e2e8b9ad9b6d1815627b29fff"
},
{
"path": "skills/netresearch-branding/claudedocs/refactoring-summary.md",
"sha256": "c1ace25fce5c5c8afa76d0900c8fec9c8255618c845a32b5eb5adce6803b260f"
},
{
"path": "skills/netresearch-branding/examples/components.html",
"sha256": "548341dfdbaacf0b0d8befc95b264f4fdde1793dc6d3f73e1edde038624591f5"
},
{
"path": "skills/netresearch-branding/templates/styles.css",
"sha256": "f4c1ecdb68b4132130f4f6261e56c1b020fdcbe38cf4ad4e4bff85dded88cc79"
},
{
"path": "skills/netresearch-branding/templates/landing-page.html",
"sha256": "d5ad833e350e48a1e8cac4c8aa461e293e9723fab60469a8fc3b912046708fed"
},
{
"path": "skills/netresearch-branding/assets/logos/netresearch-symbol-only.svg",
"sha256": "b63c3f838232beff165668516c1a2a81f9c05327b3712c07453523e4055337c1"
},
{
"path": "skills/agents/LICENSE",
"sha256": "b4aa20b52533f8ab363e465f07d9e26ad92dac6e250b650c8947c99d6ee466e0"
},
{
"path": "skills/agents/STATUS.md",
"sha256": "a656b07890efb13577aa36e08becaded05e86e72e4fe09cc147d2edff921b71d"
},
{
"path": "skills/agents/README.md",
"sha256": "b8521f379edf306b0fd5edb2567859bf52185585464a334cfe2d9a74a9bda56f"
},
{
"path": "skills/agents/.gitignore",
"sha256": "03d5d046fa175e5d6ea564d562ffbd2a555e964b0a91d3ce04af8bc54f3a54ab"
},
{
"path": "skills/agents/SKILL.md",
"sha256": "ee7db7e9bdc267a2ae2e45fae3996af707eee3be49b9a9387e1286c449d642a2"
},
{
"path": "skills/agents/composer.json",
"sha256": "0c5d90b317084fbadf5b68d7c82f384ea38fb7077ad2f53a3eccd1e923c0b85d"
},
{
"path": "skills/agents/references/analysis.md",
"sha256": "2a24ab56d49577acfd6818a7243fd3ef1dc518d55196a379deacace140a9012c"
},
{
"path": "skills/agents/references/examples/coding-agent-cli/scripts-AGENTS.md",
"sha256": "fb68e9b8fdd0a38887edb23adf8a80270635b6d66c6063b5e5c9049217193aca"
},
{
"path": "skills/agents/references/examples/coding-agent-cli/AGENTS.md",
"sha256": "0012cbc25474d641e1f43714e4728082a2f8106323880fe91959f4b785eeb7ef"
},
{
"path": "skills/agents/references/examples/ldap-selfservice/internal-AGENTS.md",
"sha256": "587e67c42b0b273c3a1a7291c1cd481380aeac33693e0314c48908344efa3c5f"
},
{
"path": "skills/agents/references/examples/ldap-selfservice/internal-web-AGENTS.md",
"sha256": "fe8aaf9130a6f35e4a93dd91fd477088fe0e6fc8d9b046473b02c7491ef7774e"
},
{
"path": "skills/agents/references/examples/ldap-selfservice/AGENTS.md",
"sha256": "12e0d8102ff9a8d9afe58a6c62f5f80dfbab6a852a812d9d14ffbc2cf6167f7e"
},
{
"path": "skills/agents/references/examples/t3x-rte-ckeditor-image/Classes-AGENTS.md",
"sha256": "18c70510d702bdb19f0635cf56b7fd1d43f33b261195c08a076bda7cdb6e47ef"
},
{
"path": "skills/agents/references/examples/t3x-rte-ckeditor-image/AGENTS.md",
"sha256": "07b74ea78fa432057a660207cf0359a9fccab3823329a47a506c77f2b2e71d7a"
},
{
"path": "skills/agents/references/examples/simple-ldap-go/examples-AGENTS.md",
"sha256": "883911058ad0c8c48da8486897c90f029a19658986f017bf1bde5ddc54eeffc7"
},
{
"path": "skills/agents/references/examples/simple-ldap-go/AGENTS.md",
"sha256": "b7f17e8beca30c42f40a7285d6c0043a67807181de5703aff2bce9e9c73ab072"
},
{
"path": "skills/agents/claudedocs/refactoring-summary.md",
"sha256": "eddf1114e3997c29e57d31133cf5a98e5cac0eabdefb06897328851f7b67cedd"
},
{
"path": "skills/agents/scripts/validate-structure.sh",
"sha256": "60789bd917958d125638f94479e6501e51d2d0f5d5373a4e33970f5f7710c848"
},
{
"path": "skills/agents/scripts/detect-scopes.sh",
"sha256": "a02b0147487009e754b658f84d48c47b4a45e46a0345616686a2aaf72bbe7967"
},
{
"path": "skills/agents/scripts/extract-commands.sh",
"sha256": "0ee80d781d8b031a0c626da4206321c747084b675c70fb8cc3e1303b2c1b4fc5"
},
{
"path": "skills/agents/scripts/generate-agents.sh",
"sha256": "3d50161327537782bde5d322e9e5fb6c83cd059e2d487212bcee0c72dd2dfe7a"
},
{
"path": "skills/agents/scripts/detect-project.sh",
"sha256": "403b1324a978ec99c0c99c70314de1b97136cc3dd23eb131e2e72389d7a5980c"
},
{
"path": "skills/agents/scripts/lib/template.sh",
"sha256": "4b30b4d85a30eea9ff5f9a36085b34a757396fde3058c734c869c640b18d9e17"
},
{
"path": "skills/agents/templates/root-verbose.md",
"sha256": "de9d263062471fc8f1fc154729a9a521acef48fda771ffee15f8a1803fd86e2c"
},
{
"path": "skills/agents/templates/root-thin.md",
"sha256": "686a97e5d5dd69ecbf567059167b35dde87a0691d6c5f69eac0759039b0bf71d"
},
{
"path": "skills/agents/templates/scoped/backend-go.md",
"sha256": "f1aad3498ac26177fdbbe69d07196b17b129fde9538e3d600754df686f26ee9d"
},
{
"path": "skills/agents/templates/scoped/cli.md",
"sha256": "c54098b6a5547639c7abc2403994495b96e61072b4e94d3bcb1bec749a611201"
},
{
"path": "skills/agents/templates/scoped/frontend-typescript.md",
"sha256": "c8024e783a228ad367679b2f961760fd3bf79bfb3b290c9c9d70c197626867a1"
},
{
"path": "skills/agents/templates/scoped/backend-php.md",
"sha256": "8956f7006f8a8fc13da61e9be3abde9711c7fd94fe533dd8fdfa8b3a966a6c80"
},
{
"path": "skills/typo3-ddev/LICENSE",
"sha256": "ab373f9ef890455f235a4ccfab7ae7f1157d5edd45e659abae687b3b6255a8d6"
},
{
"path": "skills/typo3-ddev/README.md",
"sha256": "fc05bd45dbda4ad94bfce6cbb2ddc745810328c95a6c1ab011e85b288b58fe51"
},
{
"path": "skills/typo3-ddev/.gitignore",
"sha256": "593d792f248e0cbea5d41215b10771ad52198bc565f0f20b640e9be3eecacf53"
},
{
"path": "skills/typo3-ddev/SKILL.md",
"sha256": "bfff69ac938e2e64d5d51056f8937eef72607a784c54f1925b711bbf04262f10"
},
{
"path": "skills/typo3-ddev/references/windows-optimizations.md",
"sha256": "7eae23d263c5b026b183c2e42542b2efaa449ca9ad9c996768ca0c0e5cd08e5f"
},
{
"path": "skills/typo3-ddev/references/0001-valkey-default-with-redis-alternative.md",
"sha256": "ba2b2598645132dec697fd19c7d838d503bfc22480daf5c249e105dd55be54fa"
},
{
"path": "skills/typo3-ddev/references/windows-fixes.md",
"sha256": "e6ad59d54c2198f266f169afdf23f717d4f0865fecb95f927a93651db8904a74"
},
{
"path": "skills/typo3-ddev/references/troubleshooting.md",
"sha256": "f242014dc3ee7088e43484aeaa79899d4afc09bd11936e28a72a6854d601a21a"
},
{
"path": "skills/typo3-ddev/references/advanced-options.md",
"sha256": "91a5afc978a25e97208b781781f0eaeb08b11b8ee92db64f9d4e37caaa46200b"
},
{
"path": "skills/typo3-ddev/references/prerequisites-validation.md",
"sha256": "cf809654a94cb872202843b9f4a95d9f3b1ae6dfeb1efa9d0b6569f11d0b1d42"
},
{
"path": "skills/typo3-ddev/references/quickstart.md",
"sha256": "cc7ebde884323fcae12df2699dcdcf1e140b0b62117a8f7658dcc42fa3c68b34"
},
{
"path": "skills/typo3-ddev/references/index-page-generation.md",
"sha256": "a667a23a99423bcdb1b6ee3c6e5782c4717dd679292b85af9dcbe321ca98cac7"
},
{
"path": "skills/typo3-ddev/references/0002-mariadb-default-with-database-alternatives.md",
"sha256": "aec6ece3c744066b63023e40df82369e0719a99e988a2014414602f21cc16b21"
},
{
"path": "skills/typo3-ddev/claudedocs/skill-refactoring-summary.md",
"sha256": "382d6d27e41b5395ddf31134f6d2ae787f761bb4870ac2e22a770a3846eb6a6c"
},
{
"path": "skills/typo3-ddev/scripts/validate-prerequisites.sh",
"sha256": "5c1401510f9986e343749a56a8fa64f8046946637362848284748e4fb8aa01b3"
},
{
"path": "skills/typo3-ddev/assets/templates/docker-compose.git-info.yaml",
"sha256": "c88eec7b034e5ff2c59e2bc4f1f99da7b768a8c917f8f53437786031c0891825"
},
{
"path": "skills/typo3-ddev/assets/templates/config.redis.php.example",
"sha256": "d30b42cedfa7e6b1939518d91ff9a85959d137bb56ecae0616982917f7b07fbc"
},
{
"path": "skills/typo3-ddev/assets/templates/.envrc",
"sha256": "35911511432d6c5fbbd00dc9b1f25d17658e30ddf1a44cd8da17b3c0d4e75cb6"
},
{
"path": "skills/typo3-ddev/assets/templates/docker-compose.web.yaml",
"sha256": "703079576c5a3c970d3dedec4968de4391654f34834f7d1c36b3c4b147b6f23f"
},
{
"path": "skills/typo3-ddev/assets/templates/Makefile.template",
"sha256": "d39015650acc88a43f89983de4cb141766f92bbf921269f439da53615b372986"
},
{
"path": "skills/typo3-ddev/assets/templates/docker-compose.ofelia.yaml.optional",
"sha256": "b664d9d7b69c3ce7855603e1fa2c77f930a447a73fcfdfaceb845c24242f0b4b"
},
{
"path": "skills/typo3-ddev/assets/templates/index.html.template",
"sha256": "b3c905e7b64dc1284ec8f5013c04e9c12180f75031d69232694538c601828bcf"
},
{
"path": "skills/typo3-ddev/assets/templates/config.yaml",
"sha256": "3a83be64a83f6e50aedca7320c31e6a80612d12b3e65b1569324906ec1cc31f6"
},
{
"path": "skills/typo3-ddev/assets/templates/README-SERVICES.md.optional",
"sha256": "20f1d7379ff9115b53187154b3afcab6dab2bc81811b5392a81c02a9cf6bbafa"
},
{
"path": "skills/typo3-ddev/assets/templates/docker-compose.services.yaml.optional",
"sha256": "fd2a75d4b5c2861367dd0e63cf09e9be31a52f5c4990d38c21d6471bb3a44fe1"
},
{
"path": "skills/typo3-ddev/assets/templates/docker-compose.services-redis.yaml.optional",
"sha256": "564fde7771013bc18808dde014e29621c68c826ef07f8957482ab6b1d31c0ccc"
},
{
"path": "skills/typo3-ddev/assets/templates/web-build/install-cron.sh.optional",
"sha256": "ef1cba4123b9df86aedb15509c393e198300cf2ae92cb9aef4b5e3284af8ca00"
},
{
"path": "skills/typo3-ddev/assets/templates/web-build/Dockerfile",
"sha256": "518c5065f73bdf796d412efd84db4192fe6c486c08741d576a144cc82755a436"
},
{
"path": "skills/typo3-ddev/assets/templates/homeadditions/.bashrc_additions.optional",
"sha256": "5b8046dbf3974f89227b3f8d87bf4089183333a3c83f454925c5fdb340e8bbf9"
},
{
"path": "skills/typo3-ddev/assets/templates/commands/install-v11",
"sha256": "6e80f13afd7b64883db9539c9d79d8443b0d134cf8bd7f78520a8f6cbf337689"
},
{
"path": "skills/typo3-ddev/assets/templates/commands/install-v13",
"sha256": "f87b775f3822318876d0db63c78cbc8dad48e4e37f61d7958ae76ac90a81d9ce"
},
{
"path": "skills/typo3-ddev/assets/templates/commands/install-v12",
"sha256": "f50abab5b2e0446d5a5da6dce3f4b6d801e9823773b435df19f17a349f8f3a7a"
},
{
"path": "skills/typo3-ddev/assets/templates/commands/install-all",
"sha256": "2ba13bf400536925fc9e9faba5127031423e8ba32feed76a643c8343bb79e922"
},
{
"path": "skills/typo3-ddev/assets/templates/commands/host/setup",
"sha256": "a4dfdd5540a4662e58f125e4ca72b685d783a50275971d23bbf5c95b1240038f"
},
{
"path": "skills/typo3-ddev/assets/templates/commands/host/docs",
"sha256": "13545bb03d87dd11dba24131753b66db758862c96986df40f48555f7d4786908"
},
{
"path": "skills/typo3-ddev/assets/templates/commands/host/pre-start-git-info",
"sha256": "1579befd98de8d00ad8d62b7d96b4ec36a0d7fe97084dc55761e100d56f876c2"
},
{
"path": "skills/typo3-ddev/assets/templates/commands/web/generate-index",
"sha256": "3ad42122b0c027a1a9b028af3fda855518f15763c7aa5adfc6bc4d996f3b7bd6"
},
{
"path": "skills/typo3-ddev/assets/templates/commands/web/configure-extension.optional",
"sha256": "400cdb85bf7ef285556e6d337e25e4636a8bb4dbb222fcb1672f3374f19e404e"
},
{
"path": "skills/typo3-ddev/assets/templates/commands/web/generate-makefile",
"sha256": "8fee0d248e9b63e86a4a8fa29f0c5b121cd22ead08f12287e827a114d45fc664"
},
{
"path": "skills/typo3-ddev/assets/templates/commands/web/install-introduction.optional",
"sha256": "3f2a8f3605795cdd69b52760a8c2c9952c38fcfcf267815490deb1b7c4bb58ed"
},
{
"path": "skills/typo3-ddev/assets/templates/apache/apache-site.conf",
"sha256": "f40f09c69e02e8855fc30416a55bd3059ddedfbb2a727c853d7a89f00d5cbd60"
},
{
"path": "skills/typo3-core-contributions/LICENSE",
"sha256": "ab373f9ef890455f235a4ccfab7ae7f1157d5edd45e659abae687b3b6255a8d6"
},
{
"path": "skills/typo3-core-contributions/README.md",
"sha256": "0433a370f77599dacfd39053cec3cb17118ae46c4f72a0114fafda1526bc6dcb"
},
{
"path": "skills/typo3-core-contributions/.gitignore",
"sha256": "3f62ab1e721fdb95478da9f8f9da4cecdc319027d5bd50875be155096894577b"
},
{
"path": "skills/typo3-core-contributions/SKILL.md",
"sha256": "be04cc864305cd81f7c453e438b7561b9d6f55b2d6a08f97dc16de977c01525c"
},
{
"path": "skills/typo3-core-contributions/references/commit-message-format.md",
"sha256": "9b2f6e02622990792e3fb3dd9b3517bdf589fdf617177db3e62e8cdb8435ade4"
},
{
"path": "skills/typo3-core-contributions/references/ddev-setup-workflow.md",
"sha256": "d4d7ce7295626390c0a7e5d0de001905d8f1eba58c412f6f2fcf2d9756b549b8"
},
{
"path": "skills/typo3-core-contributions/references/troubleshooting.md",
"sha256": "5f2fa7356654dc27b7f49c6472ab5548b86927aad8c2452889cc04897fb69ef9"
},
{
"path": "skills/typo3-core-contributions/references/forge-api.md",
"sha256": "e7a0fc7dbe169392e851441f9f780de4015040db4bc004a418357ae157359d26"
},
{
"path": "skills/typo3-core-contributions/references/gerrit-workflow.md",
"sha256": "4495e847cf2c2865216b54670446060d721d879609a824e6a6b2ce8ec4b93c81"
},
{
"path": "skills/typo3-core-contributions/references/commit-msg-hook.md",
"sha256": "1db8b73f5ffcb3c09a23035c7b772b3db5471c07cdd4a9e3a65c2f6420581896"
},
{
"path": "skills/typo3-core-contributions/references/account-setup.md",
"sha256": "9dc89d22b729d21cf09efb6aa848237c641faa84a2c9ab234cb5297d07231756"
},
{
"path": "skills/typo3-core-contributions/references/gerrit-review-patterns.md",
"sha256": "585195c4184806acdf01e3437dcc9b97de0c7104c86886242067bbf08836de14"
},
{
"path": "skills/typo3-core-contributions/claudedocs/refactoring-summary.md",
"sha256": "0feaaf54fc2738956bf5c34829a7093a562ddb7d9f1ab468f0198a40992a347c"
},
{
"path": "skills/typo3-core-contributions/scripts/setup-typo3-coredev.sh",
"sha256": "d47c65f4cd00aab4a1c0b46323a8e96ba1c2e1d14992f94d6dce798f8113c7b0"
},
{
"path": "skills/typo3-core-contributions/scripts/create-commit-message.py",
"sha256": "c0ea29d591cbeefe21ec7a8375829bd870b6c271ff50d3f7e12717004974c28a"
},
{
"path": "skills/typo3-core-contributions/scripts/query-forge-metadata.sh",
"sha256": "aa747ec03f3fc0a8842bd1bff236ae7253ded95f24f70e6c10e7567328a40aec"
},
{
"path": "skills/typo3-core-contributions/scripts/verify-prerequisites.sh",
"sha256": "533f0555ec0f2a23088c5d11dce6193175439a68405faf2b6f5eef4251766f19"
},
{
"path": "skills/typo3-core-contributions/scripts/create-forge-issue.sh",
"sha256": "60da8c26d4fde62279923281e402ada6a81a61865e9f55b4081188b74d3a97fc"
},
{
"path": "skills/typo3-core-contributions/scripts/validate-commit-message.py",
"sha256": "3b75909562f243565927169dccaaf6c9fd5241e6bfd4b571a81aa5bca3b42db9"
},
{
"path": "skills/typo3-core-contributions/assets/commit-template.txt",
"sha256": "9ff82d4769378bd677c2cccf167452040474c219991680a0b674b103fea9d681"
},
{
"path": "skills/typo3-docs/LICENSE",
"sha256": "83edab32209ba0c16c0c48a2fe26fff50845567cb9f6b4da15cdfb833f000465"
},
{
"path": "skills/typo3-docs/README.md",
"sha256": "58f342349d0d552d247c206c03815388eeb039887c24139716a8322fa7689b7a"
},
{
"path": "skills/typo3-docs/.gitignore",
"sha256": "78e70ee14ad1b25efd9dc634bbc6789105c9b58590a0ee723e61a16acd1905f2"
},
{
"path": "skills/typo3-docs/SKILL.md",
"sha256": "9602724068e345b8b5992887a0bf53ec23758b69ab6f2e7cccdff8d2ecc65aef"
},
{
"path": "skills/typo3-docs/references/documentation-coverage-analysis.md",
"sha256": "7af86f9dbc003566e17e8474abf6ba2de42d6a30c8ec8a4c55d1df6ab0ab0924"
},
{
"path": "skills/typo3-docs/references/intercept-deployment.md",
"sha256": "2ed547dc4b75bdceaf14a9191ad8fa4705485ddec3d8b03e11a68d7f5dd8b095"
},
{
"path": "skills/typo3-docs/references/extraction-patterns.md",
"sha256": "4923178cb9513c7f1bfc891bb426d086084c4137cb6b76606bd44957d9c309ea"
},
{
"path": "skills/typo3-docs/references/typo3-extension-architecture.md",
"sha256": "301e42f88e60c124c3c66e0ca800a379d80ba9664187d690b7989e9b15a20a5c"
},
{
"path": "skills/typo3-docs/references/rst-syntax.md",
"sha256": "c1ad74539a0bb59d270dc4f6628ab481b3d72aa86c55a7717a3b60a70166df1e"
},
{
"path": "skills/typo3-docs/references/typo3-directives.md",
"sha256": "3986e558ff5c7c260051897613d73e3debd8a49d791c186c928e6761b29fa816"
},
{
"path": "skills/typo3-docs/scripts/extract-composer.sh",
"sha256": "1085c1bf7cba1d98a7ee0b3ad0a3326f8490a209e069dc7c3ee709fa4a32ea0b"
},
{
"path": "skills/typo3-docs/scripts/add-agents-md.sh",
"sha256": "6dd85ef682d34d36266c50cd04debe7663aa3b70f5d23a56dfc16d946ffbec52"
},
{
"path": "skills/typo3-docs/scripts/extract-php.sh",
"sha256": "e0f67003b1b4b6744f940a538c144394f9685bfd92f87281a2758564f379b023"
},
{
"path": "skills/typo3-docs/scripts/render_docs.sh",
"sha256": "4992904d50c3b67a0a5c0b122bfba185b80d10814653dbdb236c6b03ad9f5322"
},
{
"path": "skills/typo3-docs/scripts/extract-repo-metadata.sh",
"sha256": "0d41fe60e9945ddf749cef361b518a6cfdb68f31507fa0ab4ef3962f8829355d"
},
{
"path": "skills/typo3-docs/scripts/extract-extension-config.sh",
"sha256": "9670d9c8f23c8f5666eb5ca2aec23ef0a94f86c5c53c2ed8d2be9cec29d1fc61"
},
{
"path": "skills/typo3-docs/scripts/extract-all.sh",
"sha256": "391d6798db12db06c18088582d6a6264ce5d1d25ecf37e6ee3ab4a1e5de9027f"
},
{
"path": "skills/typo3-docs/scripts/extract-project-files.sh",
"sha256": "013930097424d8d00a7ece3ff9703e8ffcaa2439264627c252e2fb02dc7a9958"
},
{
"path": "skills/typo3-docs/scripts/analyze-docs.sh",
"sha256": "82efbe1190502dcf31ddc09e80ffd22e710706ffa359da85dd24f457f6634954"
},
{
"path": "skills/typo3-docs/scripts/validate_docs.sh",
"sha256": "92620e2cd268a896d3362964f6a47db4124fb291e88a0f3f33fe3274f2ceb8f1"
},
{
"path": "skills/typo3-docs/scripts/extract-build-configs.sh",
"sha256": "019921be95911ca275e6afb42bea36758c4f4ed9f893b2894f9ffb64f53a9b5c"
},
{
"path": "skills/typo3-docs/templates/AGENTS.md",
"sha256": "170e12674522df8099c2b46917c4dc21b8df0882c3bc21a0ac61e539c7910649"
},
{
"path": "skills/typo3-conformance/README.md",
"sha256": "5d13579ebff7c20fe0c59538aba85ce77763b3d7d59c5d3bffcc0ba20cf254ea"
},
{
"path": "skills/typo3-conformance/.gitignore",
"sha256": "d2dbcbb13e12a322c8987474608144d4852424e8db305fa8eacf5cddfc8d50da"
},
{
"path": "skills/typo3-conformance/SKILL.md",
"sha256": "68b223fd999a61f844e53158d7c0d2acaf3f2bb4188326ec9daf29c45ddad417"
},
{
"path": "skills/typo3-conformance/references/v13-deprecations.md",
"sha256": "a6374eaf550050c52292a7d6187d044a7990222ec107f0f076948e4c4498d224"
},
{
"path": "skills/typo3-conformance/references/backend-module-v13.md",
"sha256": "45c2a675563204470367611ab401768e52efd82539190e5d2037b09e71e1d16f"
},
{
"path": "skills/typo3-conformance/references/ext-files-validation.md",
"sha256": "14856d9c803ca69d8c9628c86297e88b19b48dc12d409d7aae8a59b5956c6fd6"
},
{
"path": "skills/typo3-conformance/references/best-practices.md",
"sha256": "305184bfe93aaa6ccada0fb278734a40c192301dfea7dad20163195021f5c1c6"
},
{
"path": "skills/typo3-conformance/references/directory-structure.md",
"sha256": "cae94c8f7b5895b2b7ac8c756caa13eb2849858e1e95b2d2d1d4044b255364e5"
},
{
"path": "skills/typo3-conformance/references/crowdin-integration.md",
"sha256": "92625735ff042ad0e99e0ec40732a430b2777653a53f15f381f5f725a54633ed"
},
{
"path": "skills/typo3-conformance/references/excellence-indicators.md",
"sha256": "9cffb3cb62597c0b3b00624ed580de4d90aec51a50ec513a5e9823981c6b5f32"
},
{
"path": "skills/typo3-conformance/references/testing-standards.md",
"sha256": "54ae9cfe6e1e682898f31a7756ca99e79a5d3f982c51646cc5b9dc030332ed1a"
},
{
"path": "skills/typo3-conformance/references/composer-validation.md",
"sha256": "6542761396e7bc706925a84745e043e46d310d10d9e2a3ebfb754cd69e369c3e"
},
{
"path": "skills/typo3-conformance/references/extension-architecture.md",
"sha256": "4023d9dc4294c464ab48b703d452568c5b42ad3c86665e97bbe0e1ef4851c00c"
},
{
"path": "skills/typo3-conformance/references/ext-emconf-validation.md",
"sha256": "991b455b6eb042828c143aa9c08b725106979d7590d7b9b0adf9af5305013824"
},
{
"path": "skills/typo3-conformance/references/coding-guidelines.md",
"sha256": "30388716f84210020d177abfcdf6a28b4b78b35e5b609d7d82c7eaa222348302"
},
{
"path": "skills/typo3-conformance/references/runtests-validation.md",
"sha256": "e6cb139eb19592c42d8b1c87950632245e67fb34476110d63d7d70358900858b"
},
{
"path": "skills/typo3-conformance/references/version-requirements.md",
"sha256": "daa17739122f2e1621f01fefc8b1389729b355aafba4258f182c65e127931589"
},
{
"path": "skills/typo3-conformance/references/hooks-and-events.md",
"sha256": "84b4b535d3ac6f8a7908a576cda9f1a05b3658804fcd3130ebd5f33f7d20b59e"
},
{
"path": "skills/typo3-conformance/references/php-architecture.md",
"sha256": "a39281ae23be135cff2791780cc4b8e8d6b2584d64776e66f901649c440644fe"
},
{
"path": "skills/typo3-conformance/references/development-environment.md",
"sha256": "6036bfb46a66e58fdb8f22f99461fa50a92582e1e788c960742dee1c1b60456d"
},
{
"path": "skills/typo3-conformance/scripts/check-file-structure.sh",
"sha256": "0da9d33a9d83adda445785fbe04a12345da4df802e700702867a8ac16d06102d"
},
{
"path": "skills/typo3-conformance/scripts/generate-report.sh",
"sha256": "5792c0770a616fa9b23073005626a46e81d85c14c05c46c77aeca2c7b84bd832"
},
{
"path": "skills/typo3-conformance/scripts/check-architecture.sh",
"sha256": "8e1fb2c2621c701ddd054dbb102fb43aa41c2541b45a14b4f18555c270b1d7a6"
},
{
"path": "skills/typo3-conformance/scripts/check-conformance.sh",
"sha256": "dcc37ae3a2d5eb50197e020d93be9e331348df82ae50fac3817400ca7b7cebb9"
},
{
"path": "skills/typo3-conformance/scripts/check-phpstan-baseline.sh",
"sha256": "e297e01caee0ecb47715e2c4631ba03b62e1fb9e24567c58d6c23f3539841735"
},
{
"path": "skills/typo3-conformance/scripts/check-testing.sh",
"sha256": "70c8aaaa31b0d708424991c8a5e66786ff472bda356efedf8a75a579e7592e97"
},
{
"path": "skills/typo3-conformance/scripts/check-coding-standards.sh",
"sha256": "22d162f56484650d1a801f7e418103408dfb643086c36d1684f289f4d5a81dd4"
},
{
"path": "skills/typo3-conformance/.github/workflows/publish-to-ter.yml",
"sha256": "62eee6d30da96da6b44d376939f46e823b019cd7e79fff19361eccff90795c4d"
},
{
"path": "skills/typo3-conformance/.github/ISSUE_TEMPLATE/config.yml",
"sha256": "8c74d3fce410f9dfcf021dee69ed6a840a64477ba3108baf276ad0d34a264ac7"
}
],
"dirSha256": "2dac5be64a7d70361a08099a0ed7fd471693e4f6b4405d6c25731205d9e9b3ba"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

19
skills/agents/.gitignore vendored Normal file
View File

@@ -0,0 +1,19 @@
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Temporary
*.tmp
*.bak
.cache/
# Test output
test-output/
*.test.md

18
skills/agents/LICENSE Normal file
View File

@@ -0,0 +1,18 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 2025 Netresearch DTT GmbH
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

440
skills/agents/README.md Normal file
View File

@@ -0,0 +1,440 @@
# AGENTS.md Generator Skill
Generate and maintain AGENTS.md files following the [public agents.md convention](https://github.com/anthropics/anthropic-sdk-python/blob/main/AGENTS.md).
## Quick Start
```bash
# Generate AGENTS.md files for current project (thin root + auto-detected scopes)
/tmp/agents-skill/scripts/generate-agents.sh .
# Preview what will be created
/tmp/agents-skill/scripts/generate-agents.sh . --dry-run
# Use verbose root template
/tmp/agents-skill/scripts/generate-agents.sh . --style=verbose
# Validate existing structure
/tmp/agents-skill/scripts/validate-structure.sh .
```
## What It Does
Creates hierarchical AGENTS.md documentation for software projects:
- **Thin root files** (~30 lines) with precedence rules and global defaults
- **Scoped files** for subsystems (backend/, frontend/, internal/, cmd/, etc.)
- **Auto-extracted commands** from Makefile, package.json, composer.json, go.mod
- **Managed headers** marking files as agent-maintained with timestamps
- **Language-specific templates** for Go, PHP, TypeScript, Python
- **Idempotent updates** that preserve existing structure
## Supported Project Types
### Languages & Frameworks
- **Go**: Libraries, web apps (Fiber/Echo/Gin), CLI tools (Cobra/urfave/cli)
- **PHP**: Composer packages, TYPO3 extensions, Laravel/Symfony apps
- **TypeScript/JavaScript**: React, Next.js, Vue, Node.js, Express
- **Python**: pip, poetry, pipenv, Django, Flask, FastAPI
- **Hybrid**: Multi-language projects (auto-creates scoped files per stack)
### Auto-Detection
The skill automatically detects:
- Project language and version
- Build tools (make, npm, composer, poetry)
- Quality tools (linters, formatters, type checkers)
- Test frameworks
- Framework type (React, TYPO3, Django, etc.)
- Directories needing scoped AGENTS.md files
## Usage
### Basic Generation
```bash
# Generate for current project
/tmp/agents-skill/scripts/generate-agents.sh .
# Generate for specific project
/tmp/agents-skill/scripts/generate-agents.sh /path/to/project
```
**Output**:
```
✅ Created: ./AGENTS.md
✅ Created: ./internal/AGENTS.md
✅ Created: ./cmd/AGENTS.md
✅ Generated: 1 root + 2 scoped files
```
### Template Styles
#### Thin Root (Default)
Perfect thin root following simple-ldap-go pattern (~30 lines):
```bash
/tmp/agents-skill/scripts/generate-agents.sh . --style=thin
```
Contains:
- Precedence statement
- Minimal global rules
- Pre-commit checks
- Scope index
- Conflict resolution
#### Verbose Root
Comprehensive root with detailed sections (~100-200 lines):
```bash
/tmp/agents-skill/scripts/generate-agents.sh . --style=verbose
```
Additional sections:
- Project overview
- Development workflow
- Code quality standards
- Security guidelines
- Testing requirements
- Documentation links
### Options
```bash
--style=thin|verbose Template style (default: thin)
--dry-run Preview what will be created without writing files
--update Update existing files only (preserve custom content)
--force Force regeneration of all files
--verbose, -v Verbose output with detection details
--help, -h Show help message
```
### Examples
```bash
# Preview changes before applying
/tmp/agents-skill/scripts/generate-agents.sh . --dry-run
# Generate verbose root with detailed guidelines
/tmp/agents-skill/scripts/generate-agents.sh . --style=verbose
# Update existing files with refreshed commands and timestamps
/tmp/agents-skill/scripts/generate-agents.sh . --update
# Force regeneration of all files
/tmp/agents-skill/scripts/generate-agents.sh . --force
# Verbose output to see detection process
/tmp/agents-skill/scripts/generate-agents.sh . --verbose
```
## Output Structure
### Thin Root Example
```markdown
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: 2025-10-18 -->
# AGENTS.md (root)
This file explains repo-wide conventions and where to find scoped rules.
**Precedence:** the **closest `AGENTS.md`** to the files you're changing wins. Root holds global defaults only.
## Global rules
- Keep diffs small; add tests for new code paths
- Ask first before: adding heavy deps, running full e2e suites, or repo-wide rewrites
- Never commit secrets or sensitive data to the repository
- Follow Go 1.24 conventions and idioms
## Minimal pre-commit checks
- Typecheck (all packages): `go build -v ./...`
- Lint/format (file scope): `gofmt -w <file.go>` and `golangci-lint run ./...`
- Unit tests (fast): `go test -v -race -short -timeout=10s ./...`
## Index of scoped AGENTS.md
- `./internal/AGENTS.md` — Backend services (Go)
- `./cmd/AGENTS.md` — Command-line interface tools
## When instructions conflict
- The nearest `AGENTS.md` wins. Explicit user prompts override files.
- For Go-specific patterns, defer to language idioms and standard library conventions
```
### Scoped File (9-Section Schema)
Each scoped file follows this structure:
1. **Overview**: Purpose of this subsystem
2. **Setup & environment**: Prerequisites, installation
3. **Build & tests**: File-scoped commands (preferred)
4. **Code style & conventions**: Language-specific standards
5. **Security & safety**: Security practices
6. **PR/commit checklist**: Pre-commit requirements
7. **Good vs. bad examples**: Concrete code samples
8. **When stuck**: Where to find help
9. **House Rules** (optional): Overrides of global rules
## Detection Scripts
### Project Detection
```bash
/tmp/agents-skill/scripts/detect-project.sh .
```
Returns JSON with detected information:
```json
{
"type": "go-web-app",
"language": "go",
"version": "1.24",
"build_tool": "make",
"framework": "fiber",
"has_docker": true,
"quality_tools": ["golangci-lint", "gofmt"],
"test_framework": "testing",
"ci": "github-actions"
}
```
### Scope Detection
```bash
/tmp/agents-skill/scripts/detect-scopes.sh .
```
Returns directories needing scoped AGENTS.md:
```json
{
"scopes": [
{"path": "internal", "type": "backend-go", "files": 15},
{"path": "cmd", "type": "cli", "files": 3}
]
}
```
### Command Extraction
```bash
/tmp/agents-skill/scripts/extract-commands.sh .
```
Returns auto-extracted build commands:
```json
{
"typecheck": "go build -v ./...",
"lint": "golangci-lint run ./...",
"format": "gofmt -w .",
"test": "go test -v -race -short ./...",
"build": "go build -v ./...",
"dev": ""
}
```
## Validation
```bash
/tmp/agents-skill/scripts/validate-structure.sh .
```
Validates:
- ✅ Root is thin (≤50 lines or has index)
- ✅ All scoped files have 9 sections
- ✅ Managed headers present
- ✅ Precedence statement in root
- ✅ Links from root to scoped files work
**Example output**:
```
Validating AGENTS.md structure in: .
=== Root AGENTS.md ===
✅ Managed header present: ./AGENTS.md
✅ Root is thin: 27 lines
✅ Precedence statement present
✅ All scope index links work
=== Scoped AGENTS.md Files ===
Checking: internal/AGENTS.md
✅ Managed header present: internal/AGENTS.md
✅ All required sections present: internal/AGENTS.md
=== Validation Summary ===
✅ All checks passed!
```
## Real-World Examples
The skill includes real examples from Netresearch projects in `references/examples/`:
### simple-ldap-go (Perfect Thin Root)
**26-line root** demonstrating minimal best practice:
- Clear precedence statement
- File-scoped commands
- Scope index with descriptions
- No duplication
See: `references/examples/simple-ldap-go/AGENTS.md`
### ldap-selfservice (Hybrid Go + TypeScript)
**Multi-stack project**:
- Thin root with navigation
- `internal/AGENTS.md` for Go backend
- `internal/web/AGENTS.md` for TypeScript + Tailwind frontend
### t3x-rte_ckeditor_image (TYPO3 Extension)
**PHP TYPO3 extension**:
- Composer-based with Make targets
- Scoped for Classes/, Documentation/, Tests/
- TYPO3-specific conventions (DI, CGL, PHPStan Level 10)
### coding_agent_cli (Python CLI)
**Script-heavy toolset**:
- Precedence-focused root
- Scoped for scripts/ directory
- Python-specific tooling (ruff, mypy, pytest)
## Managed Headers
All generated files include a managed header:
```html
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: 2025-10-18 -->
```
This indicates:
- File is agent-maintained
- Section structure should not be changed
- Content within sections can be edited
- Timestamp tracks last update
## Idempotent Updates
Safe to run multiple times:
1. Checks existing files
2. Preserves custom content in sections
3. Updates only auto-extracted parts (commands, versions)
4. Refreshes timestamps
5. Adds missing sections
6. No changes if nothing updated
```bash
# Update existing files with refreshed data
/tmp/agents-skill/scripts/generate-agents.sh . --update
```
## Best Practices
### Keep Root Thin
**Good** (simple-ldap-go, 26 lines):
- Precedence statement
- Minimal global rules
- Pre-commit checks
- Scope index
**Bloated** (some projects, 300+ lines):
- Detailed setup instructions (→ move to scoped files)
- Language-specific patterns (→ move to scoped files)
- Extensive examples (→ move to scoped files)
### Scope Appropriately
Create scoped files for:
- Different technology stacks (backend/, frontend/, api/)
- Public vs private packages (internal/, pkg/)
- CLI tools (cmd/, cli/)
- Utility scripts (scripts/)
- Documentation (docs/, examples/)
- Testing infrastructure (tests/, testutil/)
### Auto-Extract Commands
Don't manually write commands if they exist in:
- Makefile targets
- package.json scripts
- composer.json scripts
- CI workflows
Let the generator extract them automatically.
## Installation
### Claude Code Marketplace
Add to `.claude/marketplace.json`:
```json
{
"name": "agents",
"description": "Generate AGENTS.md files following public convention",
"version": "1.0.0",
"path": "/tmp/agents-skill"
}
```
### Direct Usage
```bash
# Clone skill
git clone https://github.com/netresearch/agents-skill.git /tmp/agents-skill
# Generate for current project
/tmp/agents-skill/scripts/generate-agents.sh .
```
## Troubleshooting
### No Commands Detected
```bash
# Check what was detected
/tmp/agents-skill/scripts/extract-commands.sh . --verbose
# Fallback: Check Makefile or package.json manually
cat Makefile
cat package.json
```
### Wrong Project Type
```bash
# Check detection
/tmp/agents-skill/scripts/detect-project.sh .
# Verify files that should be detected
ls -la go.mod package.json composer.json pyproject.toml
```
### Scoped File Not Created
```bash
# Check scope detection
/tmp/agents-skill/scripts/detect-scopes.sh .
# Minimum 5 source files needed for scopes (except cmd/, tests/)
find internal -name "*.go" | wc -l
```
## License
GPL-2.0-or-later (matching other Netresearch skills)
## References
- **Analysis**: `references/analysis.md` - Analysis of 21 real AGENTS.md files
- **Examples**: `references/examples/` - Real-world AGENTS.md files from Netresearch projects
- **Public Convention**: https://github.com/anthropics/anthropic-sdk-python/blob/main/AGENTS.md

390
skills/agents/SKILL.md Normal file
View File

@@ -0,0 +1,390 @@
---
name: agents
version: 1.1.0
description: Generate and maintain AGENTS.md files following the public agents.md convention. Use when creating documentation for AI agent workflows, onboarding guides, or when standardizing agent interaction patterns across projects.
license: Complete terms in LICENSE.txt
---
# AGENTS.md Generator Skill
Generate and maintain AGENTS.md files following the public agents.md convention.
## What This Skill Does
Creates hierarchical AGENTS.md documentation for software projects:
- **Thin root files** (~30 lines) with precedence rules and global defaults
- **Scoped files** for subsystems (backend/, frontend/, internal/, cmd/, etc.)
- **Auto-extracted commands** from Makefile, package.json, composer.json, go.mod
- **Managed headers** marking files as agent-maintained with timestamps
- **Language-specific templates** for Go, PHP, TypeScript, Python, and hybrid projects
- **Idempotent updates** that preserve existing structure
Based on analysis of 21 real AGENTS.md files across Netresearch projects.
## When to Use This Skill
- **New projects**: Establish baseline AGENTS.md structure
- **Existing projects**: Standardize agent documentation
- **Team onboarding**: Provide AI assistants with project context
- **Multi-repo consistency**: Apply same standards across repositories
- **Documentation updates**: Refresh after major changes
## Usage
### Generate for Current Project
```bash
# Basic generation (thin root + auto-detected scopes)
/tmp/agents-skill/scripts/generate-agents.sh .
# Dry-run to preview what will be created
/tmp/agents-skill/scripts/generate-agents.sh . --dry-run
# Verbose output with detection details
/tmp/agents-skill/scripts/generate-agents.sh . --verbose
```
### Template Styles
```bash
# Thin root (default, ~30 lines, simple-ldap-go style)
/tmp/agents-skill/scripts/generate-agents.sh . --style=thin
# Verbose root (~100-200 lines, ldap-selfservice style)
/tmp/agents-skill/scripts/generate-agents.sh . --style=verbose
```
### Update Existing Files
```bash
# Update timestamps and refresh auto-extracted content
/tmp/agents-skill/scripts/generate-agents.sh . --update
# Force regeneration (overwrites existing, keeps structure)
/tmp/agents-skill/scripts/generate-agents.sh . --force
```
### Validation
```bash
# Validate existing AGENTS.md structure
/tmp/agents-skill/scripts/validate-structure.sh .
# Check for missing scoped files
/tmp/agents-skill/scripts/detect-scopes.sh .
```
## Supported Project Types
### Languages & Frameworks
- **Go**: Libraries, web apps (Fiber/Echo/Gin), CLI tools (Cobra/urfave/cli)
- **PHP**: Composer packages, TYPO3 extensions, Laravel/Symfony apps
- **TypeScript/JavaScript**: React, Next.js, Vue, Node.js, Express
- **Python**: pip, poetry, pipenv, Django, Flask, FastAPI
- **Hybrid**: Multi-language projects (auto-creates scoped files per stack)
### Detection Signals
| Signal | Detection |
|--------|-----------|
| `go.mod` | Go project, extracts version |
| `composer.json` | PHP project, detects TYPO3/Laravel |
| `package.json` | Node.js project, detects framework |
| `pyproject.toml` | Python project, detects poetry/ruff |
| `Makefile` | Extracts targets with `##` comments |
| `.github/workflows/` | Extracts CI checks |
| `docker-compose.yml` | Docker-first setup |
## Output Structure
### Thin Root (Default)
**~30 lines** following `simple-ldap-go` pattern:
```markdown
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: YYYY-MM-DD -->
# AGENTS.md (root)
**Precedence:** The **closest AGENTS.md** to changed files wins. Root holds global defaults only.
## Global rules
- Keep PRs small (~≤300 net LOC)
- Conventional Commits: type(scope): subject
- Ask before: heavy deps, full e2e, repo rewrites
- Never commit secrets or PII
## Minimal pre-commit checks
- Typecheck: [auto-detected from build tools]
- Lint: [auto-detected from linters]
- Format: [auto-detected from formatters]
- Tests: [auto-detected from test runners]
## Index of scoped AGENTS.md
- `./backend/AGENTS.md` — Backend services
- `./frontend/AGENTS.md` — Frontend application
## When instructions conflict
Nearest AGENTS.md wins. User prompts override files.
```
### Scoped Files (9-Section Schema)
Each scoped file follows this structure:
1. **Overview**: Purpose of this subsystem
2. **Setup & environment**: Prerequisites, installation
3. **Build & tests**: File-scoped commands (preferred)
4. **Code style & conventions**: Language-specific standards
5. **Security & safety**: Security practices
6. **PR/commit checklist**: Pre-commit requirements
7. **Good vs. bad examples**: Concrete code samples
8. **When stuck**: Where to find help
9. **House Rules** (optional): Overrides of global rules
### Managed Header
All generated files include:
```html
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-18 -->
```
This marks files as agent-maintained and provides update tracking.
## Auto-Detection Features
### Project Type Detection
```bash
$ /tmp/agents-skill/scripts/detect-project.sh .
{
"type": "go-web-app",
"language": "go",
"version": "1.24",
"build_tool": "make",
"has_docker": true,
"quality_tools": ["golangci-lint", "gofmt"],
"test_framework": "testing",
"ci": "github-actions"
}
```
### Scope Detection
Automatically creates scoped AGENTS.md for directories with ≥5 source files:
```bash
$ /tmp/agents-skill/scripts/detect-scopes.sh .
{
"scopes": [
{"path": "internal", "type": "backend-go", "files": 15},
{"path": "cmd", "type": "cli", "files": 3},
{"path": "examples", "type": "examples", "files": 8}
]
}
```
### Command Extraction
Extracts actual commands from build tools:
```bash
$ /tmp/agents-skill/scripts/extract-commands.sh .
{
"typecheck": "go build -v ./...",
"lint": "golangci-lint run ./...",
"format": "gofmt -w .",
"test": "go test -v -race -short ./...",
"build": "go build -o bin/app ./cmd/app"
}
```
## Examples
Real-world examples from Netresearch projects in `references/examples/`:
### Go Library (simple-ldap-go)
**Perfect thin root** (26 lines):
- Minimal global rules
- File-scoped commands
- Clear scope index
- No duplication
### Hybrid App (ldap-selfservice-password-changer)
**Go backend + TypeScript frontend**:
- Root with quick navigation
- Scoped: `internal/AGENTS.md` (Go)
- Scoped: `internal/web/AGENTS.md` (TypeScript + Tailwind)
### PHP TYPO3 Extension (t3x-rte_ckeditor_image)
**Composer-based with Make targets**:
- Root with emoji headers
- Scoped: `Classes/AGENTS.md` (PHP backend)
- Scoped: `Documentation/AGENTS.md` (RST docs)
- Scoped: `Tests/AGENTS.md` (PHPUnit tests)
### Python CLI (coding_agent_cli_toolset)
**Script-heavy toolset**:
- Root with precedence focus
- Scoped: `scripts/AGENTS.md`
## Customization
### Override Templates
Copy templates to project and modify:
```bash
cp /tmp/agents-skill/templates/root-thin.md ./.agents-templates/root.md
# Edit ./.agents-templates/root.md
/tmp/agents-skill/scripts/generate-agents.sh . --template-dir=./.agents-templates
```
### Add Custom Sections
Templates support placeholders:
- `{{PROJECT_NAME}}` - From package.json/composer.json/go.mod
- `{{PROJECT_TYPE}}` - Auto-detected type
- `{{LANGUAGE}}` - Primary language
- `{{BUILD_COMMANDS}}` - Extracted commands
- `{{QUALITY_TOOLS}}` - Detected linters/formatters
- `{{TIMESTAMP}}` - Current date (YYYY-MM-DD)
## Idempotent Updates
Safe to run multiple times:
1. Checks existing files
2. Preserves custom content in sections
3. Updates only auto-extracted parts (commands, versions)
4. Refreshes timestamps
5. Adds missing sections
6. No changes if nothing updated
## Validation
```bash
# Check structure compliance
/tmp/agents-skill/scripts/validate-structure.sh .
# Validates:
# ✅ Root is thin (≤50 lines or has index)
# ✅ All scoped files have 9 sections
# ✅ Managed headers present
# ✅ Precedence statement in root
# ✅ Links from root to scoped files work
# ✅ No duplicate content between root and scoped
```
## Integration with Claude Code
### As Marketplace Skill
Add to `claude-code-marketplace`:
```json
{
"name": "agents",
"description": "Generate AGENTS.md files following public convention",
"version": "1.0.0",
"source": "./skills/agents"
}
```
### Direct Usage
```bash
# Clone skill
git clone https://github.com/netresearch/agents-skill.git /tmp/agents-skill
# Generate for current project
/tmp/agents-skill/scripts/generate-agents.sh .
```
## Structure Standards Application
**When creating root AGENTS.md files**, keep them thin (~30 lines):
- Include clear precedence statement at top
- Define minimal global rules only (PR size, commit format, safety)
- List pre-commit checks (typecheck, lint, format, test)
- Provide scope index linking to scoped files
- Move detailed setup to scoped files (not in root)
- Move language-specific patterns to scoped files (not in root)
- Move extensive examples to scoped files (not in root)
**When determining scope boundaries**, create scoped files for:
- Different technology stacks: `backend/`, `frontend/`, `api/`
- Package visibility: `internal/`, `pkg/` (Go projects)
- CLI tools: `cmd/`, `cli/`
- Utility scripts: `scripts/`
- Documentation and examples: `docs/`, `examples/`
- Testing infrastructure: `tests/`, `testutil/`
**When extracting commands**, automate extraction from:
- Makefile targets with `##` comments
- package.json scripts section
- composer.json scripts section
- CI workflow files (.github/workflows/, .gitlab-ci.yml)
- Never manually duplicate commands that exist in build tools
## Troubleshooting
### No Commands Detected
```bash
# Check what was detected
/tmp/agents-skill/scripts/extract-commands.sh . --verbose
# Fallback: Specify commands manually
/tmp/agents-skill/scripts/generate-agents.sh . --commands='{"lint":"make lint","test":"make test"}'
```
### Wrong Project Type
```bash
# Override auto-detection
/tmp/agents-skill/scripts/generate-agents.sh . --type=go-library
# Supported types:
# go-library, go-web-app, go-cli
# php-library, php-typo3, php-laravel
# typescript-react, typescript-node
# python-library, python-cli
# hybrid
```
### Scoped File Not Created
```bash
# Check scope detection
/tmp/agents-skill/scripts/detect-scopes.sh .
# Manually specify scopes
/tmp/agents-skill/scripts/generate-agents.sh . --scopes=internal,cmd,examples
```
## Contributing
Improvements welcome! Common additions:
- New language templates
- Better command extraction
- Additional validation rules
- More real-world examples
## License
GPL-2.0-or-later (matching other Netresearch skills)
## References
- **Analysis**: `references/analysis.md` - Analysis of 21 real AGENTS.md files
- **Prompt**: `references/prompt.md` - Original generation prompt/rule
- **Examples**: `references/examples/` - Real-world AGENTS.md files
- **Best Practices**: `references/best-practices.md` - Writing guide

104
skills/agents/STATUS.md Normal file
View File

@@ -0,0 +1,104 @@
# agents-skill Creation Status
**Created**: 2025-10-18
**Status**: ✅ COMPLETE - Fully functional skill ready for use
## ✅ Completed
1. **Directory Structure**: Created all necessary directories
2. **SKILL.md**: Complete skill metadata and documentation
3. **`.gitignore**`: Standard ignores
4. **Analysis**: Complete analysis of 21 real AGENTS.md files
5. **Templates**: root-thin.md, root-verbose.md, and scoped templates (Go, PHP, TypeScript, CLI)
6. **Scripts**: All generator and detection scripts implemented
7. **Examples**: Real-world AGENTS.md files from 4 projects copied
8. **README.md**: Comprehensive usage guide
9. **LICENSE**: GPL-2.0-or-later
10. **Git Repository**: Initialized with initial commit
## 📦 Implementation Summary
### Templates (✅ Complete)
-`templates/root-thin.md` - Thin root template (simple-ldap-go style, ~30 lines)
-`templates/root-verbose.md` - Verbose root template (~100-200 lines)
-`templates/scoped/backend-go.md` - Go backend 9-section template
-`templates/scoped/backend-php.md` - PHP backend 9-section template
-`templates/scoped/frontend-typescript.md` - TypeScript frontend 9-section template
-`templates/scoped/cli.md` - CLI tools 9-section template
### Scripts (✅ Complete)
-`scripts/generate-agents.sh` - Main orchestrator with --dry-run, --update, --force, --style
-`scripts/detect-project.sh` - Auto-detect language, version, framework, tools
-`scripts/detect-scopes.sh` - Find directories needing scoped AGENTS.md
-`scripts/extract-commands.sh` - Parse Makefile, package.json, composer.json
-`scripts/validate-structure.sh` - Validate structure compliance
-`scripts/lib/template.sh` - Template rendering helper functions
### Examples (✅ Complete)
-`references/examples/simple-ldap-go/` - Perfect thin root (26 lines)
-`references/examples/ldap-selfservice/` - Hybrid Go + TypeScript
-`references/examples/t3x-rte-ckeditor-image/` - PHP TYPO3 extension
-`references/examples/coding-agent-cli/` - Python CLI toolset
-`references/analysis.md` - Comprehensive analysis of 21 files
### Documentation (✅ Complete)
-`README.md` - Comprehensive usage guide with examples
-`SKILL.md` - Complete skill metadata and documentation
-`LICENSE` - GPL-2.0-or-later
-`.gitignore` - Standard ignores
### Git Repository (✅ Complete)
- ✅ Initialized with all files
- ✅ Initial commit with descriptive message
- ✅ Ready for push to GitHub
## 🚀 Next Steps
1.**Push to GitHub**: Create repository at `https://github.com/netresearch/agents-skill`
2.**Add to marketplace**: Update sync configuration and workflow
3.**Test on real projects**: Validate with simple-ldap-go, t3x-rte_ckeditor_image, etc.
## 📝 Final Directory Structure
```
/tmp/agents-skill/
├── .git/ ✅ Git repository initialized
├── .gitignore ✅ Standard ignores
├── LICENSE ✅ GPL-2.0-or-later
├── README.md ✅ Comprehensive usage guide
├── SKILL.md ✅ Complete skill metadata
├── STATUS.md ✅ This file
├── templates/
│ ├── root-thin.md ✅ Thin root template
│ ├── root-verbose.md ✅ Verbose root template
│ ├── scoped/
│ │ ├── backend-go.md ✅ Go backend template
│ │ ├── backend-php.md ✅ PHP backend template
│ │ ├── cli.md ✅ CLI tools template
│ │ └── frontend-typescript.md ✅ TypeScript frontend template
│ └── sections/ (future: modular sections)
├── scripts/
│ ├── detect-project.sh ✅ Project type detection
│ ├── detect-scopes.sh ✅ Scope detection
│ ├── extract-commands.sh ✅ Build command extraction
│ ├── generate-agents.sh ✅ Main generator
│ ├── validate-structure.sh ✅ Structure validation
│ └── lib/
│ └── template.sh ✅ Template rendering helpers
└── references/
├── analysis.md ✅ Comprehensive analysis
└── examples/
├── simple-ldap-go/ ✅ Perfect thin root example
├── ldap-selfservice/ ✅ Hybrid Go + TypeScript
├── t3x-rte-ckeditor-image/ ✅ PHP TYPO3 extension
└── coding-agent-cli/ ✅ Python CLI toolset
```
## ✅ Skill is Complete and Ready for Use
The agents-skill is fully implemented and ready to generate AGENTS.md files for any supported project type (Go, PHP, TypeScript, Python, hybrid).
**Usage**:
```bash
/tmp/agents-skill/scripts/generate-agents.sh /path/to/project
```

View File

@@ -0,0 +1,45 @@
# SKILL.md Refactoring Summary
**Date:** 2025-11-14
**Version Change:** 1.0.0 → 1.1.0
**Skill:** agents
## Changes Applied
### Pattern 2: Converted "## Best Practices" to Imperative Form
- **Before:** "## Best Practices" with subsections containing mixed Do's/Don'ts
- **After:** "## Structure Standards Application" with imperative "When X" format
#### Keep Root Thin
- **Before:** "Good" and "Bloated" examples with checkmarks/X marks
- **After:** "When creating root AGENTS.md files" with action-oriented instructions
- Converted positive examples to directives
- Converted negative examples to avoidance instructions
#### Scope Appropriately
- **Before:** "Create scoped files for:" with bullet list
- **After:** "When determining scope boundaries" with action-oriented guidance
- Same content, imperative presentation
#### Auto-Extract Commands
- **Before:** "Don't manually write commands if they exist in:"
- **After:** "When extracting commands, automate extraction from:"
- Positive framing with clear directive
## Impact Analysis
**Readability:** Improved - clearer action-oriented instructions
**Consistency:** Aligned with skill-creator best practices
**Usability:** Enhanced - readers know when and how to apply each pattern
**Structure:** Maintained all examples while improving presentation
## Files Modified
- `/SKILL.md` (lines 1-400)
## Verification
- Version number updated in YAML frontmatter: ✓
- Best Practices converted to imperative form: ✓
- All guidance remains intact: ✓
- No broken internal references: ✓

View File

@@ -0,0 +1,21 @@
{
"name": "netresearch/agents-skill",
"description": "Generate and maintain AGENTS.md files following the public agents.md convention",
"type": "ai-agent-skill",
"license": "GPL-2.0-or-later",
"authors": [
{
"name": "Netresearch DTT GmbH",
"email": "plugins@netresearch.de",
"homepage": "https://www.netresearch.de/",
"role": "Manufacturer"
}
],
"require": {
"netresearch/composer-agent-skill-plugin": "*"
},
"keywords": [
"ai-agent",
"skill"
]
}

View File

@@ -0,0 +1,320 @@
# AGENTS.md Analysis Across 6 Netresearch Projects
## Executive Summary
**Total AGENTS.md Files Found**: 21 files across 6 projects
**Patterns Observed**:
1. **Root files are thin** (26-348 lines) with precedence rules and global defaults
2. **Scoped files are focused** on specific subsystems (backend, frontend, CLI, etc.)
3. **Managed header** present in newer files: `<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: YYYY-MM-DD -->`
4. **Consistent structure** following the 9-section schema from your prompt
## File Distribution
| Project | Root | Scoped Files | Total Lines (root) |
|---------|------|--------------|-------------------|
| t3x-rte_ckeditor_image | ✅ | Classes/, Documentation/, Resources/, Tests/ | 348 |
| coding_agent_cli_toolset | ✅ | scripts/ | 308 |
| ldap-selfservice-password-changer | ✅ | internal/, internal/web/ | 282 |
| ldap-manager | ✅ | cmd/, internal/, internal/web/, scripts/ | 228 |
| raybeam | ✅ | cmd/, internal/ | 209 |
| simple-ldap-go | ✅ | docs/, examples/, testutil/ | 26 ⭐ **Perfect thin root** |
## Key Findings
### 1. **simple-ldap-go** is the Best Example ⭐
**Root file** (26 lines):
- Minimal global rules
- Clear precedence statement
- Index of scoped files
- No duplication with scoped content
```markdown
## Global rules
- Keep diffs small; add tests for new code paths
- Ask first before: adding heavy deps, running full e2e suites, or repo-wide rewrites
## Minimal pre-commit checks
- Typecheck (all packages): `go build -v ./...`
- Lint/format (file scope): `gofmt -w <file.go>`
- Unit tests (fast): `go test -v -race -short -timeout=10s ./...`
## Index of scoped AGENTS.md
- `./examples/AGENTS.md` — Example applications
- `./testutil/AGENTS.md` — Testing utilities
- `./docs/AGENTS.md` — Documentation
```
### 2. Scoped Files Follow 9-Section Schema
**Example: simple-ldap-go/examples/AGENTS.md**:
1. ✅ Overview
2. ✅ Setup & environment
3. ✅ Build & tests (file-scoped)
4. ✅ Code style & conventions
5. ✅ Security & safety
6. ✅ PR/commit checklist
7. ✅ Good vs. bad examples
8. ✅ When stuck
9. ⚠️ House Rules (rarely used, only when overriding)
### 3. Managed Header Usage
**Present in** (newer projects):
- simple-ldap-go (all files)
- ldap-selfservice-password-changer (all files)
- raybeam (some files)
**Missing in** (older projects):
- t3x-rte_ckeditor_image
- coding_agent_cli_toolset
**Format**:
```html
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
```
### 4. Precedence Rules - Consistent Pattern
All root files establish precedence clearly:
**Pattern 1** (verbose):
> "This file explains repo-wide conventions and where to find scoped rules. **Precedence:** the **closest `AGENTS.md`** to the files you're changing wins. Root holds global defaults only."
**Pattern 2** (concise):
> "**Precedence**: Nearest AGENTS.md wins. This is the root file with global defaults."
**Pattern 3** (index-focused):
> "## Precedence & Scoped Files
> Nearest AGENTS.md wins. Use this root for defaults only."
### 5. Docker-First vs Native-First
**Docker-first projects** (ldap-selfservice-password-changer, ldap-manager):
```markdown
### Setup
**Prerequisites**: Docker + Docker Compose (required), Go 1.25+, Node.js 24+, pnpm 10.18+ (for native dev)
# Docker (recommended)
docker compose --profile dev up
# Native development
pnpm install
```
**Native-first projects** (simple-ldap-go, t3x-rte_ckeditor_image):
```markdown
### Setup
**Prerequisites**: Go 1.24, golangci-lint
# Install
go mod download
```
### 6. Language-Specific Patterns
**Go Projects** (simple-ldap-go, ldap-manager, raybeam):
- Minimal pre-commit: `go build -v ./...`, `gofmt -w`, `go test -short`
- Go version in global rules (1.24, 1.25)
- golangci-lint for comprehensive checks
**PHP Project** (t3x-rte_ckeditor_image):
- Composer scripts for CI pipeline
- PHPStan + PHP-CS-Fixer + Rector
- Make targets preferred over composer commands
**Hybrid Projects** (ldap-selfservice-password-changer):
- Separate sections for Go backend vs TypeScript frontend
- Scoped AGENTS.md for `internal/` (Go) and `internal/web/` (TS)
- pnpm for package management (strict version)
### 7. Quick Start Patterns
**Best practice** (ldap-selfservice-password-changer):
```markdown
## Quick Navigation
- [internal/AGENTS.md](internal/AGENTS.md) - Go backend services
- [internal/web/AGENTS.md](internal/web/AGENTS.md) - TypeScript frontend
```
**Alternative** (simple-ldap-go):
```markdown
## Index of scoped AGENTS.md
- `./examples/AGENTS.md` — Example applications and usage patterns
- `./testutil/AGENTS.md` — Testing utilities and container management
```
### 8. House Rules Implementation
**Global defaults** typically include:
- Commits: Conventional Commits, small PRs (~≤300 LOC)
- Type-safety: Strict types when supported
- SOLID, KISS, DRY, YAGNI principles
- SemVer for versioning
- No secrets in VCS
- Structured logging
- WCAG AA for UI projects
**Scoped overrides** (rare):
- Different test coverage targets per module
- Module-specific commit conventions
- Technology-specific style guides
### 9. Common Gaps Across Projects
**Missing .envrc** in most projects (your prompt requires it)
**Missing .editorconfig** in some projects
**Husky + commitlint** not universally adopted
**lint-staged** not implemented in older projects
**CI parity section** often missing (should reference GitHub Actions)
## Recommendations for Your Skill
### Essential Features
1. **Template Selection**:
- Thin root (simple-ldap-go style) ⭐
- Verbose root (ldap-selfservice-password-changer style)
- Auto-detect based on project size
2. **Project Type Detection**:
- Go: Look for `go.mod`, detect version from `go.mod` directive
- PHP: Look for `composer.json`, detect TYPO3 from dependencies
- TypeScript: Look for `tsconfig.json`, detect strict mode
- Hybrid: Detect multiple languages, recommend scoped files
3. **Scoped File Generation**:
- **Required scopes**: backend/, frontend/, internal/
- **Optional scopes**: cmd/, scripts/, examples/, docs/, testutil/
- **Auto-create** if directory exists and has ≥5 files
4. **Content Extraction**:
- **Makefile**: Extract targets with `##` comments → Build & Test Commands
- **package.json scripts**: Extract npm/pnpm commands
- **go.mod**: Extract Go version → Prerequisites
- **composer.json scripts**: Extract PHP quality commands
- **GitHub Actions**: Extract CI checks → PR/commit checklist
5. **Managed Header**:
- Always add to new files
- Preserve in existing files
- Update timestamp on regeneration
6. **Precedence Rules**:
- Auto-add "Nearest AGENTS.md wins" statement
- Generate index of scoped files in root
- Link from root to scoped files
### Skill Structure Recommendation
```
agents-skill/
├── SKILL.md
├── README.md
├── templates/
│ ├── root-thin.md # simple-ldap-go style (recommended)
│ ├── root-verbose.md # ldap-selfservice style
│ ├── scoped-backend.md # Go/PHP backend
│ ├── scoped-frontend.md # TypeScript/JS frontend
│ ├── scoped-cli.md # CLI tools
│ ├── scoped-docs.md # Documentation
│ ├── scoped-tests.md # Testing utilities
│ └── sections/ # Modular sections
│ ├── header.md # Managed header template
│ ├── precedence.md # Precedence statement
│ ├── setup.md # Setup section
│ ├── build-commands.md # Build & test commands
│ ├── code-style.md # Code style guidelines
│ ├── security.md # Security practices
│ ├── pr-checklist.md # PR/commit checklist
│ ├── examples.md # Good vs bad examples
│ └── when-stuck.md # When stuck guidance
├── scripts/
│ ├── generate-agents.sh # Main generator
│ ├── detect-project.sh # Auto-detect project type
│ ├── extract-commands.sh # Extract from Makefile/package.json
│ └── validate-structure.sh # Validate generated files
└── references/
├── examples/ # Real-world examples
│ ├── go-library.md # simple-ldap-go
│ ├── go-web-app.md # ldap-manager
│ ├── php-typo3.md # t3x-rte_ckeditor_image
│ └── hybrid-app.md # ldap-selfservice-password-changer
└── best-practices.md # AGENTS.md writing guide
```
### Key Differentiators
**Thin root by default** (not verbose like some projects)
**Auto-scope detection** (create scoped files when needed)
**Command extraction** (don't make user write commands manually)
**Managed header** (mark files as agent-maintained)
**Language-agnostic** (works with Go, PHP, TypeScript, Python, etc.)
**Idempotent** (can be re-run without breaking existing structure)
## Sample Output Comparison
### Your Prompt's Expected Output
**Root AGENTS.md** (following simple-ldap-go pattern):
```markdown
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-18 -->
# AGENTS.md (root)
**Precedence:** The **closest AGENTS.md** to changed files wins. Root holds global defaults only.
## Global rules
- Keep PRs small (~≤300 net LOC)
- Conventional Commits: type(scope): subject
- Ask before: heavy deps, full e2e, repo rewrites
- Never commit secrets or PII
## Minimal pre-commit checks
- Typecheck: [auto-detected command]
- Lint: [auto-detected command]
- Format: [auto-detected command]
- Tests: [auto-detected command]
## Index of scoped AGENTS.md
- `./internal/AGENTS.md` — Go backend services
- `./internal/web/AGENTS.md` — TypeScript frontend
## When instructions conflict
Nearest AGENTS.md wins. User prompts override files.
```
**Scoped AGENTS.md** (e.g., internal/AGENTS.md):
```markdown
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-18 -->
# AGENTS.md — Backend Services
## Overview
[Auto-generated description of internal/ directory purpose]
## Setup & environment
[Auto-detected from go.mod, .env.example]
## Build & tests (prefer file-scoped)
[Auto-extracted from Makefile, go commands]
## Code style & conventions
[Auto-detected from golangci-lint config, gofmt]
## Security & safety
[Standard Go security practices + project-specific]
## PR/commit checklist
[Auto-extracted from GitHub Actions, Makefile]
## Good vs. bad examples
[Template with placeholders to fill]
## When stuck
- Check root AGENTS.md for global rules
- Review sibling modules for patterns
```

View File

@@ -0,0 +1,308 @@
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
# AI CLI Preparation - Agent Guide (Root)
**Thin root file**: See scoped AGENTS.md files for specific areas.
## Precedence & Scoped Files
This root AGENTS.md provides global defaults. **Nearest AGENTS.md wins** for specific rules.
**Scoped files:**
- [scripts/AGENTS.md](scripts/AGENTS.md) - Shell installation scripts
## Overview
AI CLI Preparation is an environment audit tool ensuring AI coding agents (like Claude Code) have access to necessary developer tools. It detects 50+ tools, reports versions, and provides installation guidance.
**Architecture:**
- **Phase 1 (Complete)**: Tool detection, version auditing, offline-first caching
- **Phase 2 (Planned)**: Context-aware installation/upgrade management (see [docs/PRD.md](docs/PRD.md))
**Tech Stack:**
- Python 3.10+ (standard library only, no external deps for core)
- Make for task automation
- Shell scripts (Bash) for installation
- JSON for caching (latest_versions.json, tools_snapshot.json)
**Key Files:**
- `cli_audit.py` (2,375 lines): Main audit engine, 50+ tool definitions
- `smart_column.py`: ANSI/emoji-aware table formatting
- `scripts/`: 13+ installation scripts (install/update/uninstall/reconcile)
- `docs/`: Comprehensive technical documentation (12 files, 189KB)
## Setup
**Requirements:**
- Python 3.10+ (Python 3.14.0rc2 tested)
- Standard library only (no pip install needed for core)
- Optional: pyflakes for linting
**First-time setup:**
```bash
# Allow direnv (if using)
direnv allow
# Show available commands
make help
# Update snapshot (requires network)
make update
# Run audit from snapshot (fast, offline-capable)
make audit
```
**Environment variables:**
```bash
# See .env.default for all options
CLI_AUDIT_COLLECT=1 # Collect-only mode (write snapshot)
CLI_AUDIT_RENDER=1 # Render-only mode (read snapshot)
CLI_AUDIT_OFFLINE=1 # Offline mode (manual cache only)
CLI_AUDIT_DEBUG=1 # Debug output
CLI_AUDIT_JSON=1 # JSON output
```
## Build & Tests
**Primary commands:**
```bash
make audit # Render from snapshot (no network, <100ms)
make update # Collect fresh data, write snapshot (~10s)
make audit-offline # Offline audit with hints
make lint # Run pyflakes (if installed)
make upgrade # Interactive upgrade guide
```
**Single tool audit:**
```bash
make audit-ripgrep # Audit specific tool
make audit-offline-python-core # Role-based preset
```
**Installation scripts:**
```bash
make install-python # Install Python toolchain (uv)
make install-node # Install Node.js (nvm)
make install-core # Install core tools (fd, fzf, ripgrep, jq, etc.)
```
**Testing:**
```bash
# Smoke test (verifies table output and JSON format)
./scripts/test_smoke.sh
# Test single tool detection
CLI_AUDIT_DEBUG=1 python3 cli_audit.py --only ripgrep
# Validate snapshot
jq '.__meta__' tools_snapshot.json
```
**No formal test suite yet** - README acknowledges: "currently ships without tests"
- Smoke tests exist (test_smoke.sh)
- Manual validation workflows documented
## Code Style
**Python:**
- PEP 8 style (4-space indent, snake_case)
- Type hints used (`from __future__ import annotations`)
- Frozen dataclasses for immutability (`@dataclass(frozen=True)`)
- Docstrings minimal (focus on inline comments)
**Formatting:**
- EditorConfig enforced: LF, UTF-8, 4 spaces, trim trailing whitespace
- No auto-formatter configured (manual formatting)
- Lint via pyflakes: `make lint`
**Shell scripts:**
- Bash with `set -euo pipefail`
- Shellcheck-compliant (best effort)
- Consistent error handling (see scripts/lib/)
**Conventions:**
- File paths: Absolute paths, no auto-commit
- Functions: Snake_case, descriptive names
- Constants: UPPER_CASE (e.g., MANUAL_LOCK, HINTS_LOCK)
- Lock ordering: MANUAL_LOCK → HINTS_LOCK (enforced for safety)
## Security
**Secrets:**
- No secrets in VCS
- GITHUB_TOKEN optional (for GitHub API rate limit increase)
- Set via environment: `export GITHUB_TOKEN=ghp_...`
**Network:**
- HTTPS-only for upstream queries
- Retry logic with exponential backoff
- Per-origin rate limits (GitHub: 5/min, PyPI: 10/min, crates.io: 5/min)
- Timeout enforcement (default: 3s, configurable)
**Input validation:**
- Tool names validated against TOOLS registry
- Version strings sanitized (extract_version_number)
- Subprocess calls use lists, not shell=True (where possible)
**Caching:**
- Atomic file writes prevent corruption
- Offline-first design (committed latest_versions.json)
- No arbitrary code execution (package manager commands only)
## PR/Commit Checklist
**Before commit:**
- [ ] Run `make lint` (pyflakes clean)
- [ ] Run `make audit` (verify snapshot renders)
- [ ] Test affected tool: `make audit-<tool>`
- [ ] Update docs if behavior changed (README.md, docs/, scripts/README.md)
- [ ] Add/update smoke test if new output format
**Commit messages:**
- Conventional Commits format: `type(scope): description`
- Examples:
- `feat(audit): add snapshot-based collect/render modes`
- `fix(locks): enforce MANUAL_LOCK→HINTS_LOCK ordering`
- `docs(prd): add Phase 2 specifications and ADRs`
- `chore(cache): update latest_versions.json`
**Pull requests:**
- Keep PRs small (~≤300 net LOC changed)
- Link to issue/ticket if exists
- Update CHANGELOG section in PR description
- Ensure CI passes (when added)
## Good vs Bad Examples
**Good: Atomic dataclass with type safety**
```python
@dataclass(frozen=True)
class Tool:
name: str
candidates: tuple[str, ...]
source_kind: str # gh|pypi|crates|npm|gnu|skip
source_args: tuple[str, ...]
```
**Bad: Mutable dict with unclear types**
```python
# Don't do this
tool = {
'name': 'ripgrep',
'candidates': ['rg'], # List instead of tuple
'source': 'github', # Unclear allowed values
}
```
**Good: Lock ordering enforcement**
```python
def update_manual_cache(tool: str, version: str) -> None:
with MANUAL_LOCK: # Acquire first
with HINTS_LOCK: # Then hints
# Safe: consistent ordering prevents deadlock
```
**Bad: Lock ordering violation**
```python
def update_cache(tool: str) -> None:
with HINTS_LOCK: # Wrong order!
with MANUAL_LOCK:
# Deadlock risk
```
**Good: Parallel execution with isolation**
```python
with ThreadPoolExecutor(max_workers=16) as executor:
futures = [executor.submit(audit_tool, tool) for tool in TOOLS]
for future in as_completed(futures):
result = future.result() # Failures isolated
```
**Bad: Sequential execution**
```python
results = []
for tool in TOOLS: # Slow: 50 tools * 3s = 150s
results.append(audit_tool(tool))
```
## When Stuck
**Tool detection failing:**
1. Check PATH: `echo $PATH | tr ':' '\n'`
2. Debug single tool: `CLI_AUDIT_DEBUG=1 python3 cli_audit.py --only <tool>`
3. Check version flag: `<tool> --version` or `<tool> -v`
4. See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#version-detection-failures)
**Network issues:**
1. Increase timeout: `CLI_AUDIT_TIMEOUT_SECONDS=10 make update`
2. More retries: `CLI_AUDIT_HTTP_RETRIES=5 make update`
3. Use offline mode: `make audit-offline`
4. See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#network-timeout-issues)
**Cache corruption:**
1. Remove caches: `rm latest_versions.json tools_snapshot.json`
2. Regenerate: `make update`
**Installation script fails:**
1. Check permissions: `make scripts-perms`
2. Debug script: `bash -x ./scripts/install_<tool>.sh`
3. See [scripts/README.md](scripts/README.md) for per-script troubleshooting
**Documentation:**
- Start with [docs/QUICK_REFERENCE.md](docs/QUICK_REFERENCE.md) for one-liners
- See [docs/INDEX.md](docs/INDEX.md) for navigation by role/task
- Architecture details: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
- API reference: [docs/API_REFERENCE.md](docs/API_REFERENCE.md)
## House Rules
**Defaults** (override in scoped AGENTS.md if needed):
**Commits:**
- Atomic commits (single logical change)
- Conventional Commits: `type(scope): description`
- Keep PRs small (~≤300 net LOC changed)
- Ticket IDs in commits/PRs if exists
**Type-safety:**
- Use type hints (`from __future__ import annotations`)
- Frozen dataclasses for immutability
- No `Any` unless truly dynamic
**Design principles:**
- SOLID, KISS, DRY, YAGNI
- Composition > Inheritance
- Law of Demeter (minimal coupling)
**Dependencies:**
- Standard library preferred (no external Python deps for core)
- Latest stable versions when external deps needed
- Document why in Decision Log (ADRs for Phase 2)
**Security:**
- No secrets in VCS
- HTTPS-only for network calls
- No arbitrary code execution
- Input validation on external data
**Documentation currency:**
- Update docs in same PR as behavior changes
- No drift between code and docs
- Document non-obvious decisions in ADRs (see docs/adr/)
**Testing:**
- Aim for ≥80% coverage on changed code (when test suite added)
- Bugfixes use TDD: failing test first, then fix
- New code paths need tests (future requirement)
**Current status:**
- Phase 1: Production-ready detection/audit
- Phase 2: Planned installation/upgrade (see [docs/PRD.md](docs/PRD.md))
- No unit tests yet (acknowledged in README)
---
**Quick Start:** New to the project? Start with [README.md](README.md) → [docs/QUICK_REFERENCE.md](docs/QUICK_REFERENCE.md) → [docs/INDEX.md](docs/INDEX.md)
**Contributing:** See [docs/DEVELOPER_GUIDE.md](docs/DEVELOPER_GUIDE.md) for detailed contribution guide

View File

@@ -0,0 +1,389 @@
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
# Installation Scripts - Agent Guide
**Scope:** Shell scripts for tool installation, update, uninstall, reconcile
## Overview
13+ Bash scripts for installing developer tools with multiple actions:
- **install**: Fresh installation (default action)
- **update**: Upgrade to latest version
- **uninstall**: Remove installation
- **reconcile**: Switch to preferred installation method (e.g., system → user)
**Key scripts:**
- `install_core.sh`: Core tools (fd, fzf, ripgrep, jq, yq, bat, delta, just)
- `install_python.sh`: Python toolchain via uv
- `install_node.sh`: Node.js via nvm
- `install_rust.sh`: Rust via rustup
- `install_go.sh`, `install_aws.sh`, `install_kubectl.sh`, etc.
- `guide.sh`: Interactive upgrade guide
- `test_smoke.sh`: Smoke test for audit output
**Shared utilities:** `lib/` directory (colors, logging, common functions)
## Setup
**Requirements:**
- Bash 4.0+
- `curl` or `wget` for downloads
- Internet access for fresh installs
- Appropriate permissions (user for `~/.local/bin`, sudo for system)
**Environment variables:**
```bash
INSTALL_PREFIX=${INSTALL_PREFIX:-~/.local} # Default: user-level
FORCE_INSTALL=1 # Skip confirmation prompts
DEBUG=1 # Verbose output
```
**Permissions:**
```bash
make scripts-perms # Ensure all scripts are executable
```
## Build & Tests
**Run individual script:**
```bash
# Install action (default)
./scripts/install_python.sh
# Update action
./scripts/install_python.sh update
# Uninstall action
./scripts/install_python.sh uninstall
# Reconcile action (switch installation method)
./scripts/install_node.sh reconcile
```
**Via Make:**
```bash
make install-python # Install Python toolchain
make update-python # Update Python toolchain
make uninstall-python # Uninstall Python toolchain
make reconcile-node # Switch Node.js to nvm-managed
```
**Smoke test:**
```bash
./scripts/test_smoke.sh # Verify audit output format
```
**Debug mode:**
```bash
DEBUG=1 ./scripts/install_python.sh
bash -x ./scripts/install_python.sh # Trace execution
```
## Code Style
**Shell standards:**
- Bash 4.0+ features allowed
- Shebang: `#!/usr/bin/env bash` or `#!/bin/bash`
- Set strict mode: `set -euo pipefail`
- `-e`: Exit on error
- `-u`: Error on undefined variables
- `-o pipefail`: Fail on pipe errors
**Formatting:**
- 4-space indentation (matches EditorConfig)
- Function names: lowercase_with_underscores
- Constants: UPPER_CASE
- Local variables: lowercase
**Structure:**
```bash
#!/usr/bin/env bash
set -euo pipefail
# Source shared utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib/colors.sh" || true
source "${SCRIPT_DIR}/lib/common.sh" || true
# Main function per action
install_tool() {
echo_info "Installing <tool>..."
# Implementation
}
update_tool() {
echo_info "Updating <tool>..."
# Implementation
}
uninstall_tool() {
echo_info "Uninstalling <tool>..."
# Implementation
}
reconcile_tool() {
echo_info "Reconciling <tool>..."
# Implementation
}
# Action dispatcher
ACTION="${1:-install}"
case "$ACTION" in
install) install_tool ;;
update) update_tool ;;
uninstall) uninstall_tool ;;
reconcile) reconcile_tool ;;
*) echo "Usage: $0 {install|update|uninstall|reconcile}"; exit 1 ;;
esac
```
**Error handling:**
```bash
# Good: Check command exists before using
if ! command -v curl >/dev/null 2>&1; then
echo_error "curl not found. Install it first."
exit 1
fi
# Good: Check return codes
if ! download_file "$URL" "$DEST"; then
echo_error "Download failed"
exit 1
fi
# Good: Cleanup on error
trap 'rm -rf "$TMPDIR"' EXIT ERR
```
**Confirmation prompts:**
```bash
# Good: Skip prompt if FORCE_INSTALL=1
if [[ "${FORCE_INSTALL:-0}" != "1" ]]; then
read -p "Install <tool>? [y/N] " -n 1 -r
echo
[[ ! $REPLY =~ ^[Yy]$ ]] && exit 0
fi
```
## Security
**Download verification:**
```bash
# Always use HTTPS
URL="https://github.com/owner/repo/releases/download/..."
# Verify checksums when available
EXPECTED_SHA256="abc123..."
ACTUAL_SHA256=$(sha256sum "$FILE" | awk '{print $1}')
if [[ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]]; then
echo_error "Checksum mismatch!"
exit 1
fi
```
**Path safety:**
```bash
# Good: Quote variables, use absolute paths
INSTALL_DIR="${HOME}/.local/bin"
mkdir -p "$INSTALL_DIR"
mv "$TMPFILE" "$INSTALL_DIR/tool"
# Bad: Unquoted, relative paths
mkdir -p $INSTALL_DIR
mv tool bin/
```
**Sudo usage:**
```bash
# Good: Prompt for sudo only when needed
if [[ "$INSTALL_PREFIX" == "/usr/local" ]]; then
if ! sudo -v; then
echo_error "Sudo required for system installation"
exit 1
fi
sudo mv "$FILE" "$INSTALL_PREFIX/bin/"
else
# User-level, no sudo
mv "$FILE" "$INSTALL_PREFIX/bin/"
fi
```
**No secrets in scripts:**
- No API keys, tokens, passwords in scripts
- Use environment variables: `${GITHUB_TOKEN:-}`
- Document required env vars in script comments
## PR/Commit Checklist
**Before commit:**
- [ ] Run `shellcheck <script>` (if available)
- [ ] Test install action: `./scripts/install_<tool>.sh`
- [ ] Test update action: `./scripts/install_<tool>.sh update`
- [ ] Test uninstall action: `./scripts/install_<tool>.sh uninstall`
- [ ] Update `scripts/README.md` if new script or behavior change
- [ ] Verify script permissions: `make scripts-perms`
**Script checklist:**
- [ ] Shebang: `#!/usr/bin/env bash`
- [ ] Strict mode: `set -euo pipefail`
- [ ] Source shared lib: `source "${SCRIPT_DIR}/lib/colors.sh"`
- [ ] Action dispatcher (install/update/uninstall/reconcile)
- [ ] Error handling (check return codes, trap on exit)
- [ ] Confirmation prompts (respect FORCE_INSTALL)
- [ ] PATH updates (add to ~/.bashrc or ~/.zshrc if needed)
**Commit messages:**
- `feat(scripts): add install_terraform.sh`
- `fix(install-python): handle uv bootstrap failure`
- `docs(scripts): update README with reconcile action`
## Good vs Bad Examples
**Good: Robust download with fallback**
```bash
download_file() {
local url="$1"
local dest="$2"
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$url" -o "$dest"
elif command -v wget >/dev/null 2>&1; then
wget -q "$url" -O "$dest"
else
echo_error "Neither curl nor wget found"
return 1
fi
}
```
**Bad: Assumes curl exists**
```bash
download_file() {
curl -fsSL "$1" -o "$2" # Fails if curl not installed
}
```
**Good: Version comparison**
```bash
version_gt() {
test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
}
CURRENT_VERSION="1.2.3"
LATEST_VERSION="1.3.0"
if version_gt "$LATEST_VERSION" "$CURRENT_VERSION"; then
echo "Upgrade available"
fi
```
**Bad: String comparison for versions**
```bash
if [[ "$LATEST_VERSION" > "$CURRENT_VERSION" ]]; then
# Wrong: "1.10.0" < "1.9.0" (string comparison)
echo "Upgrade available"
fi
```
**Good: Cleanup on exit**
```bash
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT ERR
# Download to temp
download_file "$URL" "$TMPDIR/file"
# ... process ...
# Cleanup happens automatically via trap
```
**Bad: Manual cleanup (error-prone)**
```bash
TMPDIR=$(mktemp -d)
download_file "$URL" "$TMPDIR/file"
# ... process ...
rm -rf "$TMPDIR" # Skipped if earlier command fails
```
**Good: Action-specific logic**
```bash
install_rust() {
if command -v rustup >/dev/null 2>&1; then
echo_warn "rustup already installed"
return 0
fi
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
}
update_rust() {
if ! command -v rustup >/dev/null 2>&1; then
echo_error "rustup not installed. Run install first."
return 1
fi
rustup update
}
```
## When Stuck
**Script fails silently:**
1. Add debug: `bash -x ./scripts/install_<tool>.sh`
2. Check logs: `./scripts/install_<tool>.sh 2>&1 | tee install.log`
3. Verify permissions: `ls -la scripts/`
**Download fails:**
1. Check network: `curl -I https://github.com`
2. Check URL: `echo "$URL"` (verify it's correct)
3. Try manual download: `curl -fsSL "$URL"`
**Installation fails:**
1. Check prerequisites (e.g., Python for uv, curl for rustup)
2. Check disk space: `df -h`
3. Check permissions: `ls -ld "$INSTALL_PREFIX"`
**PATH not updated:**
1. Source shell config: `source ~/.bashrc` or `source ~/.zshrc`
2. Check PATH: `echo $PATH | tr ':' '\n' | grep local`
3. Verify binary location: `ls -la ~/.local/bin/<tool>`
**Reconcile fails:**
1. Check current installation: `which <tool>`
2. Check installation method: `cli_audit.py --only <tool>`
3. Manually remove old version first: `apt remove <tool>` or `cargo uninstall <tool>`
**Documentation:**
- Script-specific docs: [README.md](README.md) (this directory)
- Troubleshooting: [../docs/TROUBLESHOOTING.md](../docs/TROUBLESHOOTING.md)
- Architecture: [../docs/DEPLOYMENT.md](../docs/DEPLOYMENT.md#installation-scripts)
## House Rules
**Installation preferences** (Phase 2 planning):
- User-level preferred: `~/.local/bin` (workstations)
- System-level for servers: `/usr/local/bin`
- Vendor tools first: rustup, nvm, uv over system packages
- See [../docs/adr/ADR-002-package-manager-hierarchy.md](../docs/adr/ADR-002-package-manager-hierarchy.md)
**Reconciliation strategy:**
- Parallel approach: Keep both installations, prefer user via PATH
- No automatic removal (user chooses)
- See [../docs/adr/ADR-003-parallel-installation-approach.md](../docs/adr/ADR-003-parallel-installation-approach.md)
**Version policy:**
- Always latest by default
- Warn on major version upgrades
- See [../docs/adr/ADR-004-always-latest-version-policy.md](../docs/adr/ADR-004-always-latest-version-policy.md)
**Script structure:**
- Multi-action support: install, update, uninstall, reconcile
- Shared utilities in `lib/`
- Consistent error handling and logging
- Make integration via `make install-<tool>`
---
**Quick Start:** Run `make install-core` to install essential tools, then `make audit` to verify.
**Troubleshooting:** See [README.md](README.md) for per-script troubleshooting and [../docs/TROUBLESHOOTING.md](../docs/TROUBLESHOOTING.md) for general issues.

View File

@@ -0,0 +1,282 @@
# Agent Guidelines
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
**Precedence**: Nearest AGENTS.md wins. This is the root file with global defaults.
**Project**: LDAP Selfservice Password Changer — hybrid Go + TypeScript web application with WCAG 2.2 AAA accessibility compliance.
## Quick Navigation
- [internal/AGENTS.md](internal/AGENTS.md) - Go backend services
- [internal/web/AGENTS.md](internal/web/AGENTS.md) - TypeScript frontend & Tailwind CSS
## Global Defaults
### Project Overview
Self-service password change/reset web app for LDAP/ActiveDirectory with email-based password reset, rate limiting, and strict accessibility standards. Single binary deployment with embedded assets.
**Stack**: Go 1.25 + Fiber, TypeScript (ultra-strict), Tailwind CSS 4, Docker multi-stage builds, pnpm 10.18
**Key characteristics**:
- Docker-first: All dev/CI must work via Docker
- Accessibility: WCAG 2.2 AAA compliance (7:1 contrast, keyboard nav, screen readers)
- Type-safe: Go with testcontainers, TypeScript with all strict flags
- Security-focused: LDAPS, rate limiting, token-based reset, no password storage
### Setup
**Prerequisites**: Docker + Docker Compose (required), Go 1.25+, Node.js 24+, pnpm 10.18+ (for native dev)
```bash
# Clone and setup environment
git clone <repo>
cd ldap-selfservice-password-changer
cp .env.local.example .env.local # Edit with your LDAP config
# Docker (recommended)
docker compose --profile dev up
# Native development
pnpm install
go mod download
pnpm dev # Runs concurrent TS watch, CSS watch, and Go with hot-reload
```
See [docs/development-guide.md](docs/development-guide.md) for comprehensive setup.
### Build & Test Commands
**Package manager**: pnpm (specified in package.json: `pnpm@10.18.1`)
```bash
# Build everything
pnpm build # Build frontend assets + Go binary
# Frontend only
pnpm build:assets # TypeScript + Tailwind CSS
pnpm js:build # TypeScript compile + minify
pnpm css:build # Tailwind CSS + PostCSS
# Development (watch mode)
pnpm dev # Concurrent: TS watch, CSS watch, Go hot-reload
pnpm js:dev # TypeScript watch
pnpm css:dev # CSS watch
pnpm go:dev # Go with nodemon hot-reload
# Tests
go test -v ./... # All Go tests with verbose output
go test ./internal/... # Specific package tests
# Formatting
pnpm prettier --write . # Format TS, Go templates, config files
pnpm prettier --check . # Check formatting (CI)
# Type checking
pnpm js:build # TypeScript strict compilation (no emit in dev)
go build -v ./... # Go compilation + type checking
```
**CI commands** (from `.github/workflows/check.yml`):
- Type check: `pnpm js:build` and `go build -v ./...`
- Format check: `pnpm prettier --check .`
- Tests: `go test -v ./...`
### Code Style
**TypeScript**:
- Ultra-strict tsconfig: `strict: true`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `noPropertyAccessFromIndexSignature`
- Prettier formatting: 120 char width, semicolons, double quotes, 2-space tabs
- No `any` types - use proper type definitions
- Follow existing patterns in `internal/web/static/`
**Go**:
- Standard Go formatting (`go fmt`)
- Prettier with `prettier-plugin-go-template` for HTML templates
- Follow Go project layout: `internal/` for private packages, `main.go` at root
- Use testcontainers for integration tests (see `*_test.go` files)
- Error wrapping with context
**General**:
- Composition over inheritance
- SOLID, KISS, DRY, YAGNI principles
- Law of Demeter: minimize coupling
- No secrets in VCS (use .env.local, excluded from git)
### Security
- **No secrets in git**: Use `.env.local` (gitignored), never commit LDAP credentials
- **LDAPS required**: Production must use encrypted LDAP connections
- **Rate limiting**: 3 requests/hour per IP (configurable via `RATE_LIMIT_*` env vars)
- **Token security**: Cryptographic random tokens with configurable expiry
- **Input validation**: Strict validation on all user inputs (see `internal/validators/`)
- **Dependency scanning**: Renovate enabled, review changelogs for major updates
- **No PII logging**: Redact sensitive data in logs
- **Run as non-root**: Dockerfile uses UID 65534 (nobody)
### PR/Commit Checklist
**Before commit**:
- [ ] Run `pnpm prettier --write .` (format all)
- [ ] Run `pnpm js:build` (TypeScript strict check)
- [ ] Run `go test ./...` (all tests pass)
- [ ] Run `go build` (compilation check)
- [ ] No secrets in changed files
- [ ] Update docs if behavior changed
- [ ] WCAG 2.2 AAA compliance maintained (if UI changed)
**Commit format**: Conventional Commits
```
type(scope): description
Examples:
feat(auth): add password reset via email
fix(validators): correct password policy regex
docs(api): update JSON-RPC examples
chore(deps): update pnpm to v10.18.1
```
**No Claude attribution** in commit messages.
**PR requirements**:
- [ ] All CI checks pass (types, formatting, tests)
- [ ] Keep PRs small (~≤300 net LOC if possible)
- [ ] Include ticket ID if applicable: `fix(rate-limit): ISSUE-123: fix memory leak`
- [ ] Update relevant docs in same PR (README, AGENTS.md, docs/)
### Good vs Bad Examples
**✅ Good - TypeScript strict types**:
```typescript
interface PasswordPolicy {
minLength: number;
requireNumbers: boolean;
}
function validatePassword(password: string, policy: PasswordPolicy): boolean {
const hasNumber = /\d/.test(password);
return password.length >= policy.minLength && (!policy.requireNumbers || hasNumber);
}
```
**❌ Bad - Using any or unsafe access**:
```typescript
function validatePassword(password: any, policy: any) {
// ❌ any types
return password.length >= policy.minLength; // ❌ unsafe access
}
```
**✅ Good - Go error handling**:
```go
func connectLDAP(config LDAPConfig) (*ldap.Conn, error) {
conn, err := ldap.DialURL(config.URL)
if err != nil {
return nil, fmt.Errorf("failed to connect to LDAP at %s: %w", config.URL, err)
}
return conn, nil
}
```
**❌ Bad - Ignoring errors**:
```go
func connectLDAP(config LDAPConfig) *ldap.Conn {
conn, _ := ldap.DialURL(config.URL) // ❌ ignoring error
return conn // ❌ may return nil
}
```
**✅ Good - Accessible UI**:
```html
<button type="submit" aria-label="Submit password change" class="bg-blue-600 hover:bg-blue-700 focus:ring-4">
Change Password
</button>
```
**❌ Bad - Inaccessible UI**:
```html
<div onclick="submit()">Submit</div>
❌ not keyboard accessible, wrong semantics
```
### When Stuck
1. **Check existing docs**: [docs/](docs/) has comprehensive guides
2. **Review similar code**: Look for patterns in `internal/` packages
3. **Run tests**: `go test -v ./...` often reveals issues
4. **Check CI logs**: GitHub Actions shows exact failure points
5. **Verify environment**: Ensure `.env.local` is properly configured
6. **Docker issues**: `docker compose down -v && docker compose --profile dev up --build`
7. **Type errors**: Review `tsconfig.json` strict flags, use proper types
8. **Accessibility**: See [docs/accessibility.md](docs/accessibility.md) for WCAG 2.2 AAA guidelines
### House Rules
**Docker-First Philosophy**:
- All dev and CI must work via Docker Compose
- Native setup is optional convenience, not requirement
- Use profiles in compose.yml: `--profile dev` or `--profile test`
**Documentation Currency**:
- Update docs in same PR as code changes
- No drift between code and documentation
- Keep README, AGENTS.md, and docs/ synchronized
**Testing Standards**:
- Aim for ≥80% coverage on changed code
- Use testcontainers for integration tests (see existing `*_test.go`)
- For bugfixes: write failing test first (TDD)
- Tests must pass before PR approval
**Scope Discipline**:
- Build only what's requested
- No speculative features
- MVP first, iterate based on feedback
- YAGNI: You Aren't Gonna Need It
**Accessibility Non-Negotiable**:
- WCAG 2.2 AAA compliance required
- 7:1 contrast ratios for text
- Full keyboard navigation support
- Screen reader tested (VoiceOver/NVDA)
- See [docs/accessibility.md](docs/accessibility.md)
**Commit Practices**:
- Atomic commits: one logical change per commit
- Conventional Commits format enforced
- Never commit secrets or `.env.local`
- Keep PRs focused and reviewable
**Type Safety**:
- TypeScript: No `any`, all strict flags enabled
- Go: Leverage type system, avoid `interface{}`
- Validate inputs at boundaries
**Dependency Management**:
- Renovate auto-updates enabled
- Major version updates require changelog review
- Use Context7 MCP or official docs for migrations
- Keep pnpm-lock.yaml and go.sum committed

View File

@@ -0,0 +1,371 @@
# Go Backend Services
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
**Scope**: Go backend packages in `internal/` directory
**See also**: [../AGENTS.md](../AGENTS.md) for global standards, [web/AGENTS.md](web/AGENTS.md) for frontend
## Overview
Backend services for LDAP selfservice password change/reset functionality. Organized as internal Go packages:
- **email/**: SMTP email service for password reset tokens
- **options/**: Configuration management from environment variables
- **ratelimit/**: IP-based rate limiting (3 req/hour default)
- **resettoken/**: Cryptographic token generation and validation
- **rpc/**: JSON-RPC 2.0 API handlers (password change/reset)
- **validators/**: Password policy validation logic
- **web/**: HTTP server setup, static assets, routing (see [web/AGENTS.md](web/AGENTS.md))
## Setup/Environment
**Required environment variables** (configure in `.env.local`):
```bash
# LDAP connection
LDAP_URL=ldaps://ldap.example.com:636
LDAP_USER_BASE_DN=ou=users,dc=example,dc=com
LDAP_BIND_DN=cn=admin,dc=example,dc=com
LDAP_BIND_PASSWORD=secret
# Email for password reset
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=noreply@example.com
SMTP_PASSWORD=secret
SMTP_FROM=noreply@example.com
APP_BASE_URL=https://passwd.example.com
# Rate limiting (optional)
RATE_LIMIT_REQUESTS=3
RATE_LIMIT_WINDOW=1h
# Token expiry (optional)
TOKEN_EXPIRY_DURATION=1h
```
**Go toolchain**: Requires Go 1.25+ (specified in `go.mod`)
**Key dependencies**:
- `github.com/gofiber/fiber/v2` - HTTP server
- `github.com/netresearch/simple-ldap-go` - LDAP client
- `github.com/testcontainers/testcontainers-go` - Integration testing
- `github.com/joho/godotenv` - Environment loading
## Build & Tests
```bash
# Development
go run . # Start server with hot-reload (via pnpm go:dev)
go build -v ./... # Compile all packages
go test -v ./... # Run all tests with verbose output
# Specific package testing
go test ./internal/validators/... # Test password validators
go test ./internal/ratelimit/... # Test rate limiter
go test ./internal/resettoken/... # Test token generation
go test -run TestSpecificFunction # Run specific test
# Integration tests (uses testcontainers)
go test -v ./internal/email/... # Requires Docker for MailHog container
# Coverage
go test -cover ./... # Coverage summary
go test -coverprofile=coverage.out ./... && go tool cover -html=coverage.out
# Build optimized binary
CGO_ENABLED=0 go build -ldflags="-w -s" -o ldap-passwd
```
**CI validation** (from `.github/workflows/check.yml`):
```bash
go mod download
go build -v ./...
go test -v ./...
```
## Code Style
**Go Standards**:
- Use `go fmt` (automatic via Prettier with go-template plugin)
- Follow [Effective Go](https://go.dev/doc/effective_go)
- Package-level documentation comments required
- Exported functions must have doc comments
**Project Conventions**:
- Internal packages only: No public API outside this project
- Error wrapping with context: `fmt.Errorf("context: %w", err)`
- Use structured logging (consider adding in future)
- Prefer explicit over implicit
- Use interfaces for testability (see `email/service.go`)
**Naming**:
- `internal/package/file.go` - implementation
- `internal/package/file_test.go` - tests
- Descriptive variable names (not `x`, `y`, `tmp`)
- No stuttering: `email.Service`, not `email.EmailService`
**Error Handling**:
```go
// ✅ Good: wrap with context
if err != nil {
return fmt.Errorf("failed to connect LDAP at %s: %w", config.URL, err)
}
// ❌ Bad: lose context
if err != nil {
return err
}
// ❌ Worse: ignore
conn, _ := ldap.Dial(url)
```
**Testing**:
- Table-driven tests preferred
- Use testcontainers for external dependencies (LDAP, SMTP)
- Test files colocated with code: `validators/validate_test.go`
- Descriptive test names: `TestPasswordValidation_RequiresMinimumLength`
## Security
**LDAP Security**:
- Always use LDAPS in production (`ldaps://` URLs)
- Bind credentials in environment, never hardcoded
- Validate user input before LDAP queries (prevent injection)
- Use `simple-ldap-go` helpers to avoid raw LDAP filter construction
**Password Security**:
- Never log passwords (plain or hashed)
- No password storage - passwords go directly to LDAP
- Passwords only in memory during request lifetime
- HTTPS required for transport security
**Token Security**:
- Cryptographic random tokens (see `resettoken/token.go`)
- Configurable expiry (default 1h)
- Single-use tokens (invalidated after use)
- No token storage in logs or metrics
**Rate Limiting**:
- IP-based limits: 3 requests/hour default
- Configurable via `RATE_LIMIT_*` env vars
- In-memory store (consider Redis for multi-instance)
- Apply to both change and reset endpoints
**Input Validation**:
- Strict validation on all user inputs (see `validators/`)
- Reject malformed requests early
- Validate email format, username format, password policies
- No HTML/script injection vectors
## PR/Commit Checklist
**Before committing Go code**:
- [ ] Run `go fmt ./...` (or `pnpm prettier --write .`)
- [ ] Run `go vet ./...` (static analysis)
- [ ] Run `go test ./...` (all tests pass)
- [ ] Run `go build` (compilation check)
- [ ] Update package doc comments if API changed
- [ ] Add/update tests for new functionality
- [ ] Check for sensitive data in logs
- [ ] Verify error messages provide useful context
**Testing requirements**:
- New features must have tests
- Bug fixes must have regression tests
- Aim for ≥80% coverage on changed packages
- Integration tests for external dependencies
**Documentation**:
- Update package doc comments (godoc)
- Update [docs/api-reference.md](../docs/api-reference.md) for RPC changes
- Update [docs/development-guide.md](../docs/development-guide.md) for new setup steps
- Update environment variable examples in `.env` and docs
## Good vs Bad Examples
**✅ Good: Type-safe configuration**
```go
type Config struct {
LDAPURL string `env:"LDAP_URL" validate:"required,url"`
BindDN string `env:"LDAP_BIND_DN" validate:"required"`
BindPassword string `env:"LDAP_BIND_PASSWORD" validate:"required"`
}
func LoadConfig() (*Config, error) {
var cfg Config
if err := env.Parse(&cfg); err != nil {
return nil, fmt.Errorf("parse config: %w", err)
}
return &cfg, nil
}
```
**❌ Bad: Unsafe configuration**
```go
func LoadConfig() *Config {
return &Config{
LDAPURL: os.Getenv("LDAP_URL"), // ❌ no validation, may be empty
}
}
```
**✅ Good: Table-driven tests**
```go
func TestPasswordValidation(t *testing.T) {
tests := []struct {
name string
password string
policy PasswordPolicy
wantErr bool
}{
{"valid password", "Test123!", PasswordPolicy{MinLength: 8}, false},
{"too short", "Ab1!", PasswordPolicy{MinLength: 8}, true},
{"no numbers", "TestTest", PasswordPolicy{RequireNumbers: true}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidatePassword(tt.password, tt.policy)
if (err != nil) != tt.wantErr {
t.Errorf("got error %v, wantErr %v", err, tt.wantErr)
}
})
}
}
```
**❌ Bad: Non-descriptive tests**
```go
func TestPassword(t *testing.T) {
err := ValidatePassword("test") // ❌ what policy? what's expected?
if err == nil {
t.Fail()
}
}
```
**✅ Good: Interface for testability**
```go
type EmailService interface {
SendResetToken(ctx context.Context, to, token string) error
}
type SMTPService struct {
host string
port int
}
func (s *SMTPService) SendResetToken(ctx context.Context, to, token string) error {
// real implementation
}
// In tests, use mock implementation
type MockEmailService struct {
SendFunc func(ctx context.Context, to, token string) error
}
```
**❌ Bad: Hard-to-test concrete dependency**
```go
func ResetPassword(username string) error {
service := NewSMTPService() // ❌ hardcoded, can't mock
return service.SendEmail(...)
}
```
## When Stuck
**Go-specific issues**:
1. **Module issues**: `go mod tidy` to clean dependencies
2. **Import errors**: Check `go.mod` requires correct versions
3. **Test failures**: `go test -v ./... -run FailingTest` for verbose output
4. **LDAP connection**: Verify `LDAP_URL` format and network access
5. **Email testing**: Ensure Docker running for testcontainers (MailHog)
6. **Rate limit testing**: Tests may fail if system time incorrect
**Debugging**:
```bash
# Verbose test output
go test -v ./internal/package/...
# Run specific test
go test -run TestName ./internal/package/
# Race detector (for concurrency issues)
go test -race ./...
# Build with debug info
go build -gcflags="all=-N -l"
```
**Common pitfalls**:
- **Nil pointer dereference**: Check error returns before using values
- **Context cancellation**: Always respect `context.Context` in long operations
- **Resource leaks**: Defer `Close()` calls immediately after acquiring resources
- **Goroutine leaks**: Ensure all goroutines can exit
- **Time zones**: Use `time.UTC` for consistency
## Package-Specific Notes
### email/
- Uses testcontainers for integration tests
- MailHog container spins up automatically in tests
- Mock `EmailService` interface for unit tests in other packages
### options/
- Configuration loaded from environment via `godotenv`
- Validation happens at startup (fail-fast)
- See `.env.local.example` for required variables
### ratelimit/
- In-memory store (map with mutex)
- Consider Redis for multi-instance deployments
- Tests use fixed time.Now for deterministic results
### resettoken/
- Crypto/rand for token generation (never math/rand)
- Base64 URL encoding (safe for URLs)
- Store tokens server-side with expiry
### rpc/
- JSON-RPC 2.0 specification compliance
- Error codes defined in [docs/api-reference.md](../docs/api-reference.md)
- Request validation before processing
### validators/
- Pure functions (no side effects)
- Configurable policies from environment
- Clear error messages for user feedback

View File

@@ -0,0 +1,448 @@
# Frontend - TypeScript & Tailwind CSS
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
**Scope**: Frontend assets in `internal/web/` directory - TypeScript, Tailwind CSS, HTML templates
**See also**: [../../AGENTS.md](../../AGENTS.md) for global standards, [../AGENTS.md](../AGENTS.md) for Go backend
## Overview
Frontend implementation for LDAP selfservice password changer with strict accessibility compliance:
- **static/**: Client-side TypeScript, compiled CSS, static assets
- **js/**: TypeScript source files (compiled to ES modules)
- **styles.css**: Tailwind CSS output
- Icons, logos, favicons, manifest
- **templates/**: Go HTML templates (\*.gohtml)
- **handlers.go**: HTTP route handlers
- **middleware.go**: Security headers, CORS, etc.
- **server.go**: Fiber server setup
**Key characteristics**:
- **WCAG 2.2 AAA**: 7:1 contrast, keyboard navigation, screen reader support, adaptive density
- **Ultra-strict TypeScript**: All strict flags enabled, no `any` types
- **Tailwind CSS 4**: Utility-first, dark mode, responsive, accessible patterns
- **Progressive enhancement**: Works without JavaScript (forms submit via HTTP)
- **Password manager friendly**: Proper autocomplete attributes
## Setup/Environment
**Prerequisites**: Node.js 24+, pnpm 10.18+ (from root `package.json`)
```bash
# From project root
pnpm install # Install dependencies
# Development (watch mode)
pnpm css:dev # Tailwind CSS watch
pnpm js:dev # TypeScript watch
# OR
pnpm dev # Concurrent: CSS + TS + Go hot-reload
```
**No .env needed for frontend** - all config comes from Go backend
**Browser targets**: Modern browsers with ES module support (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)
## Build & Tests
```bash
# Build frontend assets
pnpm build:assets # TypeScript + CSS (production builds)
# TypeScript
pnpm js:build # Compile TS → ES modules + minify
pnpm js:dev # Watch mode with preserveWatchOutput
tsc --noEmit # Type check only (no output)
# CSS
pnpm css:build # Tailwind + PostCSS → styles.css
pnpm css:dev # Watch mode
# Formatting
pnpm prettier --write internal/web/ # Format TS, CSS, HTML templates
pnpm prettier --check internal/web/ # Check formatting (CI)
```
**No unit tests yet** - TypeScript strict mode catches most errors, integration via Go tests
**CI validation** (from `.github/workflows/check.yml`):
```bash
pnpm install
pnpm js:build # TypeScript strict compilation
pnpm prettier --check .
```
**Accessibility testing**:
- Keyboard navigation: Tab through all interactive elements
- Screen reader: Test with VoiceOver (macOS/iOS) or NVDA (Windows)
- Contrast: Verify 7:1 ratios with browser dev tools
- See [../../docs/accessibility.md](../../docs/accessibility.md) for comprehensive guide
## Code Style
**TypeScript Ultra-Strict** (from `tsconfig.json`):
```json
{
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
```
**No `any` types allowed**:
```typescript
// ✅ Good: explicit types
function validatePassword(password: string, minLength: number): boolean {
return password.length >= minLength;
}
// ❌ Bad: any type
function validatePassword(password: any): boolean {
return password.length >= 8; // ❌ unsafe
}
```
**Prettier formatting**:
- 120 char width
- 2-space indentation
- Semicolons required
- Double quotes (not single)
- Trailing comma: none
**File organization**:
- TypeScript source: `static/js/*.ts`
- Output: `static/js/*.js` (minified ES modules)
- CSS input: `tailwind.css` (Tailwind directives)
- CSS output: `static/styles.css` (PostCSS processed)
## Accessibility Standards (WCAG 2.2 AAA)
**Required compliance** - not optional:
### Keyboard Navigation
- All interactive elements focusable with Tab
- Visual focus indicators (4px outline, 7:1 contrast)
- Logical tab order (top to bottom, left to right)
- No keyboard traps
- Skip links where needed
### Screen Readers
- Semantic HTML: `<button>`, `<input>`, `<label>`, not `<div onclick>`
- ARIA labels on icon-only buttons: `aria-label="Submit"`
- Error messages: `aria-describedby` linking to error text
- Live regions for dynamic content: `aria-live="polite"`
- Form field associations: `<label for="id">` + `<input id="id">`
### Color & Contrast
- Text: 7:1 contrast ratio (AAA)
- Large text (18pt+): 4.5:1 minimum
- Focus indicators: 3:1 against adjacent colors
- Dark mode: same contrast requirements
- Never rely on color alone (use icons, text, patterns)
### Responsive & Adaptive
- Responsive: layout adapts to viewport size
- Text zoom: 200% without horizontal scroll
- Adaptive density: spacing adjusts for user preferences
- Touch targets: 44×44 CSS pixels minimum (mobile)
### Examples
**✅ Good: Accessible button**
```html
<button type="submit" class="btn-primary focus:ring-4 focus:ring-blue-300" aria-label="Submit password change">
<svg aria-hidden="true">...</svg>
Change Password
</button>
```
**❌ Bad: Inaccessible div-button**
```html
<div onclick="submit()" class="button">❌ not keyboard accessible Submit</div>
```
**✅ Good: Form with error handling**
```html
<form>
<label for="password">New Password</label>
<input
id="password"
type="password"
aria-describedby="password-error"
aria-invalid="true"
autocomplete="new-password"
/>
<div id="password-error" role="alert">Password must be at least 8 characters</div>
</form>
```
**❌ Bad: Form without associations**
```html
<form>
<div>Password</div>
❌ not a label, no association <input type="password" /> ❌ no autocomplete, no error linkage
<div style="color: red">Error</div>
❌ no role="alert", only color
</form>
```
## Tailwind CSS Patterns
**Use utility classes**, not custom CSS:
**✅ Good: Utility classes**
```html
<button
class="rounded-lg bg-blue-600 px-4 py-2 font-semibold text-white hover:bg-blue-700 focus:ring-4 focus:ring-blue-300"
>
Submit
</button>
```
**❌ Bad: Custom CSS**
```html
<button class="custom-button">Submit</button>
<style>
.custom-button {
background: blue;
} /* ❌ Use Tailwind utilities */
</style>
```
**Dark mode support**:
```html
<div class="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">Content</div>
```
**Responsive design**:
```html
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<!-- Responsive grid: 1 col mobile, 2 tablet, 3 desktop -->
</div>
```
**Focus states (required)**:
```html
<button class="focus:ring-4 focus:ring-blue-300 focus:outline-none">
<!-- 4px focus ring, 7:1 contrast -->
</button>
```
## TypeScript Patterns
**Strict null checking**:
```typescript
// ✅ Good: handle nulls explicitly
function getElement(id: string): HTMLElement | null {
return document.getElementById(id);
}
const el = getElement("password");
if (el) {
// ✅ null check
el.textContent = "Hello";
}
// ❌ Bad: assume non-null
const el = getElement("password");
el.textContent = "Hello"; // ❌ may crash if null
```
**Type guards**:
```typescript
// ✅ Good: type guard for forms
function isHTMLFormElement(element: Element): element is HTMLFormElement {
return element instanceof HTMLFormElement;
}
const form = document.querySelector("form");
if (form && isHTMLFormElement(form)) {
form.addEventListener("submit", handleSubmit);
}
```
**No unsafe array access**:
```typescript
// ✅ Good: check array bounds
const items = ["a", "b", "c"];
const first = items[0]; // string | undefined (noUncheckedIndexedAccess)
if (first) {
console.log(first.toUpperCase());
}
// ❌ Bad: unsafe access
console.log(items[0].toUpperCase()); // ❌ may crash if empty array
```
## PR/Commit Checklist
**Before committing frontend code**:
- [ ] Run `pnpm js:build` (TypeScript strict check)
- [ ] Run `pnpm prettier --write internal/web/`
- [ ] Verify keyboard navigation works
- [ ] Test with screen reader (VoiceOver/NVDA)
- [ ] Check contrast ratios (7:1 for text)
- [ ] Test dark mode
- [ ] Verify password manager autofill works
- [ ] No console errors in browser
- [ ] Test on mobile viewport (responsive)
**Accessibility checklist**:
- [ ] All interactive elements keyboard accessible
- [ ] Focus indicators visible (4px outline, 7:1 contrast)
- [ ] ARIA labels on icon-only buttons
- [ ] Form fields properly labeled
- [ ] Error messages linked with aria-describedby
- [ ] No color-only information conveyance
- [ ] Touch targets ≥44×44 CSS pixels (mobile)
**Performance checklist**:
- [ ] Minified JS (via `pnpm js:minify`)
- [ ] CSS optimized (cssnano via PostCSS)
- [ ] No unused Tailwind classes (purged automatically)
- [ ] No console.log in production code
## Good vs Bad Examples
**✅ Good: Type-safe DOM access**
```typescript
function setupPasswordToggle(): void {
const toggle = document.getElementById("toggle-password");
const input = document.getElementById("password");
if (!toggle || !(input instanceof HTMLInputElement)) {
return; // Guard against missing elements
}
toggle.addEventListener("click", () => {
input.type = input.type === "password" ? "text" : "password";
});
}
```
**❌ Bad: Unsafe DOM access**
```typescript
function setupPasswordToggle() {
const toggle = document.getElementById("toggle-password")!; // ❌ non-null assertion
const input = document.getElementById("password") as any; // ❌ any type
toggle.addEventListener("click", () => {
input.type = input.type === "password" ? "text" : "password"; // ❌ may crash
});
}
```
**✅ Good: Accessible form validation**
```typescript
function showError(input: HTMLInputElement, message: string): void {
const errorId = `${input.id}-error`;
let errorEl = document.getElementById(errorId);
if (!errorEl) {
errorEl = document.createElement("div");
errorEl.id = errorId;
errorEl.setAttribute("role", "alert");
errorEl.className = "text-red-600 dark:text-red-400 text-sm mt-1";
input.parentElement?.appendChild(errorEl);
}
errorEl.textContent = message;
input.setAttribute("aria-invalid", "true");
input.setAttribute("aria-describedby", errorId);
}
```
**❌ Bad: Inaccessible validation**
```typescript
function showError(input: any, message: string) {
// ❌ any type
input.style.borderColor = "red"; // ❌ color only, no text
alert(message); // ❌ blocks UI, not persistent
}
```
## When Stuck
**TypeScript issues**:
1. **Type errors**: Check `tsconfig.json` flags, use proper types (no `any`)
2. **Null errors**: Add null checks or type guards
3. **Module errors**: Ensure ES module syntax (`import`/`export`)
4. **Build errors**: `pnpm install` to refresh dependencies
**CSS issues**:
1. **Styles not applying**: Check Tailwind purge config, rebuild with `pnpm css:build`
2. **Dark mode broken**: Use `dark:` prefix on utilities
3. **Responsive broken**: Use `md:`, `lg:` breakpoint prefixes
4. **Custom classes**: Don't - use Tailwind utilities instead
**Accessibility issues**:
1. **Keyboard nav broken**: Check tab order, focus indicators
2. **Screen reader confusion**: Verify ARIA labels, semantic HTML
3. **Contrast failure**: Use darker colors, test with dev tools
4. **See**: [../../docs/accessibility.md](../../docs/accessibility.md)
**Browser dev tools**:
- Accessibility tab: Check ARIA, contrast, structure
- Lighthouse: Run accessibility audit (aim for 100 score)
- Console: No errors in production code
## Testing Workflow
**Manual testing required** (no automated frontend tests yet):
1. **Visual testing**: Check all pages in light/dark mode
2. **Keyboard testing**: Tab through all interactive elements
3. **Screen reader testing**: Use VoiceOver (Cmd+F5) or NVDA
4. **Responsive testing**: Test mobile, tablet, desktop viewports
5. **Browser testing**: Chrome, Firefox, Safari, Edge
6. **Password manager**: Test autofill with 1Password, LastPass, etc.
**Accessibility testing tools**:
- Browser dev tools Lighthouse
- axe DevTools extension
- WAVE browser extension
- Manual keyboard/screen reader testing (required)
**Integration testing**: Go backend tests exercise full request/response flow including frontend templates

View File

@@ -0,0 +1,27 @@
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: 2025-09-29 -->
# AGENTS.md (root)
This file explains repo-wide conventions and where to find scoped rules.
**Precedence:** the **closest `AGENTS.md`** to the files you're changing wins. Root holds global defaults only.
## Global rules
- Keep diffs small; add tests for new code paths
- Ask first before: adding heavy deps, running full e2e suites, or repo-wide rewrites
- Never commit secrets or sensitive data to the repository
- Follow Go 1.24 conventions and idioms
- Maintain minimum test coverage of 40%
## Minimal pre-commit checks
- Typecheck (all packages): `go build -v ./...`
- Lint/format (file scope): `gofmt -w <file.go>` and `~/go/bin/golangci-lint run ./...`
- Unit tests (fast): `go test -v -race -short -timeout=10s ./...`
## Index of scoped AGENTS.md
- `./examples/AGENTS.md` — Example applications and usage patterns
- `./testutil/AGENTS.md` — Testing utilities and container management
- `./docs/AGENTS.md` — Documentation and guides
## When instructions conflict
- The nearest `AGENTS.md` wins. Explicit user prompts override files.
- For Go-specific patterns, defer to language idioms and standard library conventions

View File

@@ -0,0 +1,45 @@
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: 2025-09-29 -->
# AGENTS.md — Examples
## Overview
Example applications demonstrating library usage patterns for authentication, user management, performance optimization, context handling, and error patterns. Entry points are the main.go files in each subdirectory.
## Setup & environment
- Install: `go mod download`
- Run example: `go run examples/<name>/main.go`
- Env: Examples use environment variables from `.env` files when present
## Build & tests (prefer file-scoped)
- Typecheck a file: `go build -v examples/<name>/main.go`
- Format a file: `gofmt -w examples/<name>/main.go`
- Run example: `go run examples/<name>/main.go`
## Code style & conventions
- Examples should be self-contained and runnable
- Use clear variable names that explain the concept
- Include comments explaining non-obvious patterns
- Error handling should demonstrate best practices
- Keep examples focused on a single concept
## Security & safety
- Never include real credentials in examples
- Use placeholder values like "ldap.example.com"
- Document required permissions clearly
- Examples should fail gracefully without real LDAP server
## PR/commit checklist
- Examples must compile without errors
- Include README.md explaining the example's purpose
- Test example with both real and mock LDAP servers if possible
- Ensure examples follow library best practices
## Good vs. bad examples
- Good: `authentication/main.go` (clear flow, error handling)
- Good: `context-usage/main.go` (proper context propagation)
- Pattern to follow: Simple, focused, well-commented demonstrations
## When stuck
- Check the main library documentation in ../docs/
- Review similar examples in sibling directories
- Ensure you have the latest library version

View File

@@ -0,0 +1,348 @@
# AI Agent Development Guide
**Project:** rte_ckeditor_image - TYPO3 CKEditor 5 Image Extension
**Type:** TYPO3 CMS Extension (PHP 8.2+ + JavaScript/ES6)
**License:** GPL-2.0-or-later
## 📋 Documentation Structure
This project uses a three-tier documentation system:
- **[claudedocs/](claudedocs/)** - AI session context (Markdown, gitignored, temporary)
- **[Documentation/](Documentation/)** - Official TYPO3 docs (RST, published, permanent)
- **Root** - Project essentials (README, CONTRIBUTING, SECURITY, LICENSE)
See **[claudedocs/INDEX.md](claudedocs/INDEX.md)** for AI context navigation and **[Documentation/AGENTS.md](Documentation/AGENTS.md)** for TYPO3 documentation system guide.
## 🎯 Quick Start
```bash
# First time setup
composer install
make help # See all available targets
# Development workflow
make lint # Run all linters
make format # Fix code style
make test # Run tests
make ci # Full CI check (pre-commit)
# Composer shortcuts (if make unavailable)
composer ci:test:php:lint # PHP syntax check
composer ci:test:php:phpstan # Static analysis
composer ci:test:php:cgl # Code style check
composer ci:cgl # Fix code style
```
## 🏗️ Setup
### Prerequisites
- **PHP:** 8.2-8.9 with extensions: dom, libxml
- **Composer:** Latest stable
- **TYPO3:** 13.4+ (cms-core, cms-backend, cms-frontend, cms-rte-ckeditor)
- **direnv:** Optional but recommended
### Installation
```bash
# Clone and install
git clone https://github.com/netresearch/t3x-rte_ckeditor_image.git
cd t3x-rte_ckeditor_image
composer install
# Enable direnv (optional)
direnv allow
```
## 🔧 Build & Test Commands
### Fast Quality Checks (Pre-commit)
```bash
make lint # PHP lint + PHPStan + style check
make format # Auto-fix code style
make typecheck # PHPStan static analysis
```
### Full CI Suite
```bash
make ci # Complete CI pipeline
make test # All tests (when available)
```
### Individual Commands
```bash
# PHP Linting
composer ci:test:php:lint
# Static Analysis
composer ci:test:php:phpstan
# Code Style Check
composer ci:test:php:cgl
# Code Style Fix
composer ci:cgl
# Rector (PHP Modernization)
composer ci:test:php:rector
composer ci:rector # Apply changes
```
## 📝 Code Style
### PHP Standards
- **Base:** PSR-12 + PER-CS 2.0
- **Strict types:** Required in all files (`declare(strict_types=1);`)
- **Header comments:** Auto-managed by PHP-CS-Fixer
- **Config:** `Build/.php-cs-fixer.dist.php`
### Key Rules
```php
<?php
declare(strict_types=1);
/**
* This file is part of the package netresearch/rte-ckeditor-image.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace Netresearch\RteCKEditorImage\Controller;
// Imports: Classes, constants, functions
use TYPO3\CMS\Core\Utility\GeneralUtility;
// Alignment on = and =>
$config = [
'short' => 'value',
'longer' => 'another',
];
```
### JavaScript Standards
- **ES6 modules:** CKEditor 5 plugin format
- **No npm tooling:** TYPO3-managed assets
- **Style:** Follow CKEditor 5 conventions
- **Location:** `Resources/Public/JavaScript/`
## 🔒 Security
- **No secrets in VCS:** Use TYPO3's environment configuration
- **Dependency scanning:** Renovate enabled (see `renovate.json`)
- **Static analysis:** PHPStan with strict rules
- **TYPO3 security:** Follow TYPO3 Security Guidelines
- **File uploads:** Use FAL (File Abstraction Layer)
- **XSS prevention:** All output escaped via Fluid templates
## ✅ PR/Commit Checklist
Before committing:
1.**Lint passed:** `make lint` or `composer ci:test:php:lint`
2.**Style fixed:** `make format` or `composer ci:cgl`
3.**Static analysis:** `composer ci:test:php:phpstan` (no new errors)
4.**Rector check:** `composer ci:test:php:rector` (no suggestions)
5.**Docs updated:** Update relevant docs/ files if API changed
6.**CHANGELOG:** Add entry if user-facing change
7.**Conventional Commits:** Use format: `type(scope): message`
8.**Small PRs:** Keep ≤300 net LOC changed
### Commit Format
```
<type>(<scope>): <subject>
[optional body]
[optional footer]
```
**Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
**Scopes:** `backend`, `frontend`, `config`, `docs`, `build`
**Examples:**
```
feat(backend): add image processing hook for WebP
fix(frontend): resolve style drop-down disabled for typo3image
docs(api): update DataHandling API reference
```
## 🎓 Good vs Bad Examples
### ✅ Good: TYPO3 Pattern
```php
<?php
declare(strict_types=1);
namespace Netresearch\RteCKEditorImage\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Resource\ResourceFactory;
final class SelectImageController
{
public function __construct(
private readonly ResourceFactory $resourceFactory
) {
}
public function infoAction(ServerRequestInterface $request): ResponseInterface
{
$fileUid = (int)($request->getQueryParams()['fileId'] ?? 0);
// Implementation...
}
}
```
### ❌ Bad: Missing strict types, no DI
```php
<?php
namespace Netresearch\RteCKEditorImage\Controller;
class SelectImageController
{
public function infoAction($request)
{
$fileUid = $_GET['fileId']; // Direct superglobal access
$factory = new ResourceFactory(); // No DI
}
}
```
### ✅ Good: CKEditor 5 Plugin
```javascript
export default class Typo3Image extends Core.Plugin {
static get requires() {
return ['StyleUtils', 'GeneralHtmlSupport'];
}
init() {
const editor = this.editor;
this.listenTo(styleUtils, 'isStyleEnabledForBlock', (event, [style]) => {
// Event handling...
});
}
}
```
### ❌ Bad: Missing dependencies
```javascript
class Typo3Image extends Core.Plugin {
// Missing requires() - breaks style integration
init() {
// Implementation...
}
}
```
## 🆘 When Stuck
1. **AI Context:** Start with [claudedocs/INDEX.md](claudedocs/INDEX.md) for project navigation
2. **Architecture:** Review [claudedocs/ARCHITECTURE.md](claudedocs/ARCHITECTURE.md)
3. **Security:** Check [claudedocs/SECURITY.md](claudedocs/SECURITY.md)
4. **API Reference:** See [claudedocs/API_REFERENCE.md](claudedocs/API_REFERENCE.md)
5. **Development Guide:** Follow [claudedocs/DEVELOPMENT_GUIDE.md](claudedocs/DEVELOPMENT_GUIDE.md)
6. **TYPO3 Docs Guide:** Read [Documentation/AGENTS.md](Documentation/AGENTS.md)
7. **Published Manual:** https://docs.typo3.org/p/netresearch/rte-ckeditor-image/main/en-us/
8. **TYPO3 Core Docs:** https://docs.typo3.org/
9. **Issues:** https://github.com/netresearch/t3x-rte_ckeditor_image/issues
### Common Issues
- **Style drop-down disabled:** Missing `GeneralHtmlSupport` dependency (v13.0.0+)
- **Images not in frontend:** Missing static template include
- **PHPStan errors:** Run `composer ci:test:php:phpstan:baseline` to update baseline
- **Code style fails:** Run `composer ci:cgl` to auto-fix
## 📐 House Rules
### Commits & PRs
- **Atomic commits:** One logical change per commit
- **Conventional Commits:** Required format (see checklist)
- **Small PRs:** Target ≤300 net LOC changed
- **Branch naming:** `feature/short-description`, `fix/issue-123`
### Design Principles
- **SOLID:** Single responsibility, Open/closed, Liskov, Interface segregation, Dependency inversion
- **KISS:** Keep it simple, stupid
- **DRY:** Don't repeat yourself
- **YAGNI:** You aren't gonna need it
- **Composition > Inheritance:** Prefer composition
- **Law of Demeter:** Minimize coupling
### Dependencies
- **Latest stable:** Use current TYPO3 13.4+ versions
- **Renovate:** Auto-updates enabled
- **Major updates:** Require changelog review + migration notes
- **Composer:** Lock file committed
### API & Versioning
- **SemVer:** Semantic versioning (MAJOR.MINOR.PATCH)
- **TYPO3 compatibility:** Follow TYPO3 versioning
- **Breaking changes:** Increment major version
- **Deprecations:** Add `@deprecated` tag + removal plan
### Testing
- **TYPO3 Testing Framework:** Use `typo3/testing-framework`
- **Functional tests:** For database/integration scenarios
- **Unit tests:** For isolated logic
- **Test location:** `Tests/Functional/`, `Tests/Unit/`
### Licensing
- **License:** AGPL-3.0-or-later
- **SPDX:** Use SPDX identifiers
- **Headers:** Auto-managed by PHP-CS-Fixer
- **Third-party:** Document in CHANGELOG
## 🔗 Related Files
**Root Documentation:**
- **[README.md](README.md)** - Project overview and quick links
- **[CONTRIBUTING.md](CONTRIBUTING.md)** - Contribution guidelines
- **[SECURITY.md](SECURITY.md)** - Security policy
- **[LICENSE](LICENSE)** - GPL-2.0-or-later license
**AI Session Context (gitignored):**
- **[claudedocs/INDEX.md](claudedocs/INDEX.md)** - Navigation hub
- **[claudedocs/PROJECT_OVERVIEW.md](claudedocs/PROJECT_OVERVIEW.md)** - Project summary
- **[claudedocs/ARCHITECTURE.md](claudedocs/ARCHITECTURE.md)** - System design
- **[claudedocs/DEVELOPMENT_GUIDE.md](claudedocs/DEVELOPMENT_GUIDE.md)** - Development workflow
- **[claudedocs/API_REFERENCE.md](claudedocs/API_REFERENCE.md)** - PHP API docs
- **[claudedocs/SECURITY.md](claudedocs/SECURITY.md)** - Security analysis
**Official TYPO3 Documentation:**
- **[Documentation/](Documentation/)** - RST documentation (published)
- **[Documentation/AGENTS.md](Documentation/AGENTS.md)** - TYPO3 docs system guide
**Configuration:**
- **[composer.json](composer.json)** - Dependencies & scripts
- **[Build/](Build/)** - Development tools configuration
## 📚 Additional Resources
- **Repository:** https://github.com/netresearch/t3x-rte_ckeditor_image
- **Packagist:** https://packagist.org/packages/netresearch/rte-ckeditor-image
- **TYPO3 Ext:** https://extensions.typo3.org/extension/rte_ckeditor_image
- **TYPO3 Docs:** https://docs.typo3.org/
- **CKEditor 5:** https://ckeditor.com/docs/ckeditor5/

View File

@@ -0,0 +1,392 @@
# Classes/AGENTS.md
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-15 -->
**Scope:** PHP backend components (Controllers, EventListeners, DataHandling, Utils)
**Parent:** [../AGENTS.md](../AGENTS.md)
## 📋 Overview
PHP backend implementation for TYPO3 CKEditor Image extension. Components:
### Controllers
- **SelectImageController** - Image browser wizard, file selection, image info API
- **ImageRenderingController** - Image rendering and processing for frontend
- **ImageLinkRenderingController** - Link-wrapped image rendering
### EventListeners
- **RteConfigurationListener** - PSR-14 event for RTE configuration injection
### DataHandling
- **RteImagesDbHook** - Database hooks for image magic reference handling
- **RteImageSoftReferenceParser** - Soft reference parsing for RTE images
### Backend Components
- **RteImagePreviewRenderer** - Backend preview rendering
### Utilities
- **ProcessedFilesHandler** - File processing and manipulation utilities
## 🏗️ Architecture Patterns
### TYPO3 Patterns
- **FAL (File Abstraction Layer):** All file operations via ResourceFactory
- **PSR-7 Request/Response:** HTTP message interfaces for controllers
- **PSR-14 Events:** Event-driven configuration and hooks
- **Dependency Injection:** Constructor-based DI (TYPO3 v13+)
- **Service Configuration:** `Configuration/Services.yaml` for DI registration
### File Structure
```
Classes/
├── Backend/
│ └── Preview/
│ └── RteImagePreviewRenderer.php
├── Controller/
│ ├── ImageLinkRenderingController.php
│ ├── ImageRenderingController.php
│ └── SelectImageController.php
├── DataHandling/
│ └── SoftReference/
│ └── RteImageSoftReferenceParser.php
├── Database/
│ └── RteImagesDbHook.php
├── EventListener/
│ └── RteConfigurationListener.php
└── Utils/
└── ProcessedFilesHandler.php
```
## 🔧 Build & Tests
```bash
# PHP-specific quality checks
make lint # All linters (syntax + PHPStan + Rector + style)
composer ci:test:php:lint # PHP syntax check
composer ci:test:php:phpstan # Static analysis
composer ci:test:php:rector # Rector modernization check
composer ci:test:php:cgl # Code style check
# Fixes
make format # Auto-fix code style
composer ci:cgl # Alternative: fix style
composer ci:rector # Apply Rector changes
# Full CI
make ci # Complete pipeline
```
## 📝 Code Style
### Required Patterns
**1. Strict Types (Always First)**
```php
<?php
declare(strict_types=1);
```
**2. File Header (Auto-managed by PHP-CS-Fixer)**
```php
/**
* This file is part of the package netresearch/rte-ckeditor-image.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
```
**3. Import Order**
- Classes first
- Functions second
- Constants third
- One blank line before namespace
**4. Type Hints**
- All parameters must have type hints
- All return types must be declared
- Use nullable types `?Type` when appropriate
- Use union types `Type1|Type2` for PHP 8+
**5. Property Types**
```php
private ResourceFactory $resourceFactory; // Required type declaration
private readonly ResourceFactory $factory; // Readonly for immutability
```
**6. Alignment**
```php
$config = [
'short' => 'value', // Align on =>
'longer' => 'another',
];
```
## 🔒 Security
### FAL (File Abstraction Layer)
- **Always use FAL:** Never direct file system access
- **ResourceFactory:** For retrieving files by UID
- **File validation:** Check isDeleted(), isMissing()
- **ProcessedFile:** Use process() for image manipulation
```php
// ✅ Good: FAL usage
$file = $this->resourceFactory->getFileObject($id);
if ($file->isDeleted() || $file->isMissing()) {
throw new \Exception('File not found');
}
// ❌ Bad: Direct file access
$file = file_get_contents('/var/www/uploads/' . $filename);
```
### Input Validation
- **Type cast superglobals:** `(int)($request->getQueryParams()['id'] ?? 0)`
- **Validate before use:** Check ranges, formats, existence
- **Exit on error:** Use HTTP status codes with `HttpUtility::HTTP_STATUS_*`
### XSS Prevention
- **Fluid templates:** Auto-escaping enabled by default
- **JSON responses:** Use `JsonResponse` class
- **Localization:** Via `LocalizationUtility::translate()`
## ✅ PR/Commit Checklist
### PHP-Specific Checks
1.**Strict types:** `declare(strict_types=1);` in all files
2.**Type hints:** All parameters and return types declared
3.**PHPStan:** Zero errors (`composer ci:test:php:phpstan`)
4.**Code style:** PSR-12/PER-CS2.0 compliant (`make format`)
5.**Rector:** No modernization suggestions (`composer ci:test:php:rector`)
6.**FAL usage:** No direct file system access
7.**DI pattern:** Constructor injection, no `new ClassName()`
8.**PSR-7:** Request/Response for controllers
9.**Documentation:** PHPDoc for public methods
## 🎓 Good vs Bad Examples
### ✅ Good: Controller Pattern
```php
<?php
declare(strict_types=1);
namespace Netresearch\RteCKEditorImage\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Http\JsonResponse;
use TYPO3\CMS\Core\Resource\ResourceFactory;
final class SelectImageController
{
public function __construct(
private readonly ResourceFactory $resourceFactory
) {
}
public function infoAction(ServerRequestInterface $request): ResponseInterface
{
$fileUid = (int)($request->getQueryParams()['fileId'] ?? 0);
if ($fileUid <= 0) {
return new JsonResponse(['error' => 'Invalid file ID'], 400);
}
$file = $this->resourceFactory->getFileObject($fileUid);
return new JsonResponse([
'uid' => $file->getUid(),
'width' => $file->getProperty('width'),
'height' => $file->getProperty('height'),
]);
}
}
```
### ❌ Bad: Anti-patterns
```php
<?php
// ❌ Missing strict types
namespace Netresearch\RteCKEditorImage\Controller;
// ❌ Missing PSR-7 types
class SelectImageController
{
// ❌ No constructor DI
public function infoAction($request)
{
// ❌ Direct superglobal access
$fileUid = $_GET['fileId'];
// ❌ No DI - manual instantiation
$factory = new ResourceFactory();
// ❌ No type safety, no validation
$file = $factory->getFileObject($fileUid);
// ❌ Manual JSON encoding
header('Content-Type: application/json');
echo json_encode(['uid' => $file->getUid()]);
exit;
}
}
```
### ✅ Good: EventListener Pattern
```php
<?php
declare(strict_types=1);
namespace Netresearch\RteCKEditorImage\EventListener;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\RteCKEditor\Form\Element\Event\AfterPrepareConfigurationForEditorEvent;
final class RteConfigurationListener
{
public function __construct(
private readonly UriBuilder $uriBuilder
) {
}
public function __invoke(AfterPrepareConfigurationForEditorEvent $event): void
{
$configuration = $event->getConfiguration();
$configuration['style']['typo3image'] = [
'routeUrl' => (string)$this->uriBuilder->buildUriFromRoute('rteckeditorimage_wizard_select_image'),
];
$event->setConfiguration($configuration);
}
}
```
### ❌ Bad: EventListener Anti-pattern
```php
<?php
namespace Netresearch\RteCKEditorImage\EventListener;
class RteConfigurationListener
{
// ❌ Wrong signature - not invokable
public function handle($event)
{
// ❌ Manual instantiation instead of DI
$uriBuilder = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(UriBuilder::class);
// ❌ Array access without type safety
$config = $event->getConfiguration();
$config['style']['typo3image']['routeUrl'] = $uriBuilder->buildUriFromRoute('rteckeditorimage_wizard_select_image');
$event->setConfiguration($config);
}
}
```
### ✅ Good: FAL Usage
```php
protected function getImage(int $id): File
{
try {
$file = $this->resourceFactory->getFileObject($id);
if ($file->isDeleted() || $file->isMissing()) {
throw new FileNotFoundException('File not found or deleted', 1234567890);
}
} catch (\Exception $e) {
throw new FileNotFoundException('Could not load file', 1234567891, $e);
}
return $file;
}
```
### ❌ Bad: Direct File Access
```php
// ❌ Multiple issues
protected function getImage($id) // Missing return type, no type hint
{
// ❌ Direct file system access, bypassing FAL
$path = '/var/www/html/fileadmin/' . $id;
// ❌ No validation, no error handling
if (file_exists($path)) {
return file_get_contents($path);
}
return null; // ❌ Should throw exception or return typed null
}
```
## 🆘 When Stuck
### Documentation
- **API Reference:** [docs/API/Controllers.md](../docs/API/Controllers.md) - Controller APIs
- **Event Listeners:** [docs/API/EventListeners.md](../docs/API/EventListeners.md) - PSR-14 events
- **Data Handling:** [docs/API/DataHandling.md](../docs/API/DataHandling.md) - Database hooks
- **Architecture:** [docs/Architecture/Overview.md](../docs/Architecture/Overview.md) - System design
### TYPO3 Resources
- **FAL Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Fal/Index.html
- **PSR-14 Events:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/Index.html
- **Dependency Injection:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/Index.html
- **Controllers:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Backend/Controllers/Index.html
### Common Issues
- **ResourceFactory errors:** Check file exists, not deleted, proper UID
- **DI not working:** Verify `Configuration/Services.yaml` registration
- **PHPStan errors:** Update baseline: `composer ci:test:php:phpstan:baseline`
- **Type errors:** Enable strict_types, add all type hints
## 📐 House Rules
### Controllers
- **Extend framework controllers:** ElementBrowserController for browsers
- **Final by default:** Use `final class` unless inheritance required
- **PSR-7 types:** ServerRequestInterface → ResponseInterface
- **JSON responses:** Use `JsonResponse` class
- **Validation first:** Validate all input parameters at method start
### EventListeners
- **Invokable:** Use `__invoke()` method signature
- **Event type hints:** Type-hint specific event classes
- **Immutability aware:** Get, modify, set configuration/state
- **Final classes:** Event listeners should be final
### DataHandling
- **Soft references:** Implement soft reference parsing for data integrity
- **Database hooks:** Use for maintaining referential integrity
- **Transaction safety:** Consider rollback scenarios
### Dependencies
- **Constructor injection:** All dependencies via constructor
- **Readonly properties:** Use `readonly` for immutable dependencies
- **Interface over implementation:** Depend on interfaces when available
- **GeneralUtility::makeInstance:** Only for factories or when DI unavailable
### Error Handling
- **Type-specific exceptions:** Use TYPO3 exception hierarchy
- **HTTP status codes:** Via HttpUtility constants
- **Meaningful messages:** Include context in exception messages
- **Log important errors:** Use TYPO3 logging framework
### Testing
- **Functional tests:** For controllers, database operations
- **Unit tests:** For utilities, isolated logic
- **Mock FAL:** Use TYPO3 testing framework FAL mocks
- **Test location:** `Tests/Functional/` and `Tests/Unit/`
## 🔗 Related
- **[Resources/AGENTS.md](../Resources/AGENTS.md)** - JavaScript/CKEditor integration
- **[Tests/AGENTS.md](../Tests/AGENTS.md)** - Testing patterns
- **[Configuration/Services.yaml](../Configuration/Services.yaml)** - DI container configuration
- **[docs/API/](../docs/API/)** - Complete API documentation

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env bash
# Detect project type, language, version, and build tools
set -euo pipefail
PROJECT_DIR="${1:-.}"
cd "$PROJECT_DIR"
# Initialize variables
LANGUAGE="unknown"
VERSION="unknown"
BUILD_TOOL="unknown"
FRAMEWORK="none"
PROJECT_TYPE="unknown"
QUALITY_TOOLS=()
TEST_FRAMEWORK="unknown"
HAS_DOCKER=false
CI="none"
# Detect language and version
detect_language() {
if [ -f "go.mod" ]; then
LANGUAGE="go"
VERSION=$(grep '^go ' go.mod | awk '{print $2}' || echo "unknown")
BUILD_TOOL="go"
TEST_FRAMEWORK="testing"
# Detect Go project type
if [ -d "cmd" ]; then
PROJECT_TYPE="go-cli"
elif grep -q "github.com/gofiber/fiber" go.mod 2>/dev/null; then
PROJECT_TYPE="go-web-app"
FRAMEWORK="fiber"
elif grep -q "github.com/labstack/echo" go.mod 2>/dev/null; then
PROJECT_TYPE="go-web-app"
FRAMEWORK="echo"
elif grep -q "github.com/gin-gonic/gin" go.mod 2>/dev/null; then
PROJECT_TYPE="go-web-app"
FRAMEWORK="gin"
else
PROJECT_TYPE="go-library"
fi
# Detect Go quality tools
[ -f ".golangci.yml" ] || [ -f ".golangci.yaml" ] && QUALITY_TOOLS+=("golangci-lint")
command -v gofmt &>/dev/null && QUALITY_TOOLS+=("gofmt")
elif [ -f "composer.json" ]; then
LANGUAGE="php"
VERSION=$(jq -r '.require.php // "unknown"' composer.json 2>/dev/null || echo "unknown")
BUILD_TOOL="composer"
# Detect PHP framework
if jq -e '.require."typo3/cms-core"' composer.json &>/dev/null; then
PROJECT_TYPE="php-typo3"
FRAMEWORK="typo3"
TYPO3_VERSION=$(jq -r '.require."typo3/cms-core"' composer.json 2>/dev/null || echo "unknown")
elif jq -e '.require."laravel/framework"' composer.json &>/dev/null; then
PROJECT_TYPE="php-laravel"
FRAMEWORK="laravel"
elif jq -e '.require."symfony/symfony"' composer.json &>/dev/null; then
PROJECT_TYPE="php-symfony"
FRAMEWORK="symfony"
else
PROJECT_TYPE="php-library"
fi
# Detect PHP quality tools
jq -e '.require."phpstan/phpstan"' composer.json &>/dev/null && QUALITY_TOOLS+=("phpstan")
jq -e '.require."friendsofphp/php-cs-fixer"' composer.json &>/dev/null && QUALITY_TOOLS+=("php-cs-fixer")
[ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ] && TEST_FRAMEWORK="phpunit"
elif [ -f "package.json" ]; then
LANGUAGE="typescript"
VERSION=$(jq -r '.engines.node // "unknown"' package.json 2>/dev/null || echo "unknown")
# Detect JS/TS framework
if jq -e '.dependencies."next"' package.json &>/dev/null; then
PROJECT_TYPE="typescript-nextjs"
FRAMEWORK="next.js"
BUILD_TOOL="npm"
elif jq -e '.dependencies."react"' package.json &>/dev/null; then
PROJECT_TYPE="typescript-react"
FRAMEWORK="react"
BUILD_TOOL="npm"
elif jq -e '.dependencies."vue"' package.json &>/dev/null; then
PROJECT_TYPE="typescript-vue"
FRAMEWORK="vue"
BUILD_TOOL="npm"
elif jq -e '.dependencies."express"' package.json &>/dev/null; then
PROJECT_TYPE="typescript-node"
FRAMEWORK="express"
BUILD_TOOL="npm"
else
PROJECT_TYPE="typescript-library"
BUILD_TOOL="npm"
fi
# Check for yarn/pnpm
[ -f "yarn.lock" ] && BUILD_TOOL="yarn"
[ -f "pnpm-lock.yaml" ] && BUILD_TOOL="pnpm"
# Detect quality tools
jq -e '.devDependencies."eslint"' package.json &>/dev/null && QUALITY_TOOLS+=("eslint")
jq -e '.devDependencies."prettier"' package.json &>/dev/null && QUALITY_TOOLS+=("prettier")
jq -e '.devDependencies."typescript"' package.json &>/dev/null && QUALITY_TOOLS+=("tsc")
# Detect test framework
if jq -e '.devDependencies."jest"' package.json &>/dev/null; then
TEST_FRAMEWORK="jest"
elif jq -e '.devDependencies."vitest"' package.json &>/dev/null; then
TEST_FRAMEWORK="vitest"
fi
elif [ -f "pyproject.toml" ]; then
LANGUAGE="python"
VERSION=$(grep 'requires-python' pyproject.toml | cut -d'"' -f2 2>/dev/null || echo "unknown")
# Detect Python build tool
if grep -q '\[tool.poetry\]' pyproject.toml 2>/dev/null; then
BUILD_TOOL="poetry"
elif grep -q '\[tool.hatch\]' pyproject.toml 2>/dev/null; then
BUILD_TOOL="hatch"
else
BUILD_TOOL="pip"
fi
# Detect framework
if grep -q 'django' pyproject.toml 2>/dev/null; then
PROJECT_TYPE="python-django"
FRAMEWORK="django"
elif grep -q 'flask' pyproject.toml 2>/dev/null; then
PROJECT_TYPE="python-flask"
FRAMEWORK="flask"
elif grep -q 'fastapi' pyproject.toml 2>/dev/null; then
PROJECT_TYPE="python-fastapi"
FRAMEWORK="fastapi"
elif [ -d "scripts" ] && [ "$(find scripts -name '*.py' | wc -l)" -gt 3 ]; then
PROJECT_TYPE="python-cli"
else
PROJECT_TYPE="python-library"
fi
# Detect quality tools
grep -q 'ruff' pyproject.toml 2>/dev/null && QUALITY_TOOLS+=("ruff")
grep -q 'black' pyproject.toml 2>/dev/null && QUALITY_TOOLS+=("black")
grep -q 'mypy' pyproject.toml 2>/dev/null && QUALITY_TOOLS+=("mypy")
grep -q 'pytest' pyproject.toml 2>/dev/null && TEST_FRAMEWORK="pytest"
fi
}
# Detect if Makefile exists
if [ -f "Makefile" ]; then
BUILD_TOOL="make"
fi
# Detect Docker
[ -f "Dockerfile" ] || [ -f "docker-compose.yml" ] && HAS_DOCKER=true
# Detect CI
if [ -d ".github/workflows" ]; then
CI="github-actions"
elif [ -f ".gitlab-ci.yml" ]; then
CI="gitlab-ci"
elif [ -f ".circleci/config.yml" ]; then
CI="circleci"
fi
# Run detection
detect_language
# Output JSON
# Handle empty quality_tools array
if [ ${#QUALITY_TOOLS[@]} -eq 0 ]; then
TOOLS_JSON="[]"
else
TOOLS_JSON="$(printf '%s\n' "${QUALITY_TOOLS[@]}" | jq -R . | jq -s .)"
fi
jq -n \
--arg type "$PROJECT_TYPE" \
--arg lang "$LANGUAGE" \
--arg ver "$VERSION" \
--arg build "$BUILD_TOOL" \
--arg framework "$FRAMEWORK" \
--argjson docker "$HAS_DOCKER" \
--argjson tools "$TOOLS_JSON" \
--arg test "$TEST_FRAMEWORK" \
--arg ci "$CI" \
'{
type: $type,
language: $lang,
version: $ver,
build_tool: $build,
framework: $framework,
has_docker: $docker,
quality_tools: $tools,
test_framework: $test,
ci: $ci
}'

View File

@@ -0,0 +1,178 @@
#!/usr/bin/env bash
# Detect directories that should have scoped AGENTS.md files
set -euo pipefail
PROJECT_DIR="${1:-.}"
cd "$PROJECT_DIR"
MIN_FILES=5 # Minimum files to warrant scoped AGENTS.md
# Get project info
PROJECT_INFO=$(bash "$(dirname "$0")/detect-project.sh" "$PROJECT_DIR")
LANGUAGE=$(echo "$PROJECT_INFO" | jq -r '.language')
scopes=()
# Function to count source files in a directory
count_source_files() {
local dir="$1"
local pattern="$2"
find "$dir" -maxdepth 3 -type f -name "$pattern" 2>/dev/null | wc -l
}
# Function to add scope
add_scope() {
local path="$1"
local type="$2"
local count="$3"
scopes+=("{\"path\": \"$path\", \"type\": \"$type\", \"files\": $count}")
}
# Language-specific scope detection
case "$LANGUAGE" in
"go")
# Check common Go directories
[ -d "internal" ] && {
count=$(count_source_files "internal" "*.go")
[ "$count" -ge "$MIN_FILES" ] && add_scope "internal" "backend-go" "$count"
}
[ -d "pkg" ] && {
count=$(count_source_files "pkg" "*.go")
[ "$count" -ge "$MIN_FILES" ] && add_scope "pkg" "backend-go" "$count"
}
[ -d "cmd" ] && {
count=$(count_source_files "cmd" "*.go")
[ "$count" -ge 3 ] && add_scope "cmd" "cli" "$count"
}
[ -d "examples" ] && {
count=$(count_source_files "examples" "*.go")
[ "$count" -ge 3 ] && add_scope "examples" "examples" "$count"
}
[ -d "testutil" ] && {
count=$(count_source_files "testutil" "*.go")
[ "$count" -ge 3 ] && add_scope "testutil" "testing" "$count"
}
[ -d "docs" ] && {
count=$(find docs -type f \( -name "*.md" -o -name "*.rst" \) | wc -l)
[ "$count" -ge 3 ] && add_scope "docs" "documentation" "$count"
}
;;
"php")
# Check common PHP directories
[ -d "Classes" ] && {
count=$(count_source_files "Classes" "*.php")
[ "$count" -ge "$MIN_FILES" ] && add_scope "Classes" "backend-php" "$count"
}
[ -d "src" ] && {
count=$(count_source_files "src" "*.php")
[ "$count" -ge "$MIN_FILES" ] && add_scope "src" "backend-php" "$count"
}
[ -d "Tests" ] && {
count=$(count_source_files "Tests" "*.php")
[ "$count" -ge 3 ] && add_scope "Tests" "testing" "$count"
}
[ -d "tests" ] && {
count=$(count_source_files "tests" "*.php")
[ "$count" -ge 3 ] && add_scope "tests" "testing" "$count"
}
[ -d "Documentation" ] && {
count=$(find Documentation -type f \( -name "*.rst" -o -name "*.md" \) | wc -l)
[ "$count" -ge 3 ] && add_scope "Documentation" "documentation" "$count"
}
[ -d "Resources" ] && {
count=$(find Resources -type f | wc -l)
[ "$count" -ge 5 ] && add_scope "Resources" "resources" "$count"
}
;;
"typescript")
# Check common TypeScript/JavaScript directories
[ -d "src" ] && {
count=$(count_source_files "src" "*.ts")
ts_count=$count
count=$(count_source_files "src" "*.tsx")
tsx_count=$count
if [ "$tsx_count" -ge "$MIN_FILES" ]; then
add_scope "src" "frontend-typescript" "$tsx_count"
elif [ "$ts_count" -ge "$MIN_FILES" ]; then
add_scope "src" "backend-typescript" "$ts_count"
fi
}
[ -d "components" ] && {
count=$(count_source_files "components" "*.tsx")
[ "$count" -ge "$MIN_FILES" ] && add_scope "components" "frontend-typescript" "$count"
}
[ -d "pages" ] && {
count=$(count_source_files "pages" "*.tsx")
[ "$count" -ge 3 ] && add_scope "pages" "frontend-typescript" "$count"
}
[ -d "app" ] && {
count=$(count_source_files "app" "*.tsx")
[ "$count" -ge 3 ] && add_scope "app" "frontend-typescript" "$count"
}
[ -d "server" ] || [ -d "backend" ] && {
dir=$([ -d "server" ] && echo "server" || echo "backend")
count=$(count_source_files "$dir" "*.ts")
[ "$count" -ge "$MIN_FILES" ] && add_scope "$dir" "backend-typescript" "$count"
}
[ -d "__tests__" ] || [ -d "tests" ] && {
dir=$([ -d "__tests__" ] && echo "__tests__" || echo "tests")
count=$(count_source_files "$dir" "*.test.ts")
[ "$count" -ge 3 ] && add_scope "$dir" "testing" "$count"
}
;;
"python")
# Check common Python directories
[ -d "src" ] && {
count=$(count_source_files "src" "*.py")
[ "$count" -ge "$MIN_FILES" ] && add_scope "src" "backend-python" "$count"
}
[ -d "tests" ] && {
count=$(count_source_files "tests" "*.py")
[ "$count" -ge 3 ] && add_scope "tests" "testing" "$count"
}
[ -d "scripts" ] && {
count=$(count_source_files "scripts" "*.py")
[ "$count" -ge 3 ] && add_scope "scripts" "cli" "$count"
}
[ -d "docs" ] && {
count=$(find docs -type f \( -name "*.md" -o -name "*.rst" \) | wc -l)
[ "$count" -ge 3 ] && add_scope "docs" "documentation" "$count"
}
;;
esac
# Check for web subdirectories (cross-language)
if [ -d "internal/web" ]; then
count=$(find internal/web -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) | wc -l)
[ "$count" -ge "$MIN_FILES" ] && add_scope "internal/web" "frontend-typescript" "$count"
fi
# Output JSON
if [ ${#scopes[@]} -eq 0 ]; then
echo '{"scopes": []}'
else
echo "{\"scopes\": [$(IFS=,; echo "${scopes[*]}")]}"
fi

View File

@@ -0,0 +1,183 @@
#!/usr/bin/env bash
# Extract build commands from various build tool files
set -euo pipefail
PROJECT_DIR="${1:-.}"
cd "$PROJECT_DIR"
# Get project info
PROJECT_INFO=$(bash "$(dirname "$0")/detect-project.sh" "$PROJECT_DIR")
LANGUAGE=$(echo "$PROJECT_INFO" | jq -r '.language')
BUILD_TOOL=$(echo "$PROJECT_INFO" | jq -r '.build_tool')
# Initialize command variables
TYPECHECK_CMD=""
LINT_CMD=""
FORMAT_CMD=""
TEST_CMD=""
BUILD_CMD=""
DEV_CMD=""
# Extract from Makefile
extract_from_makefile() {
[ ! -f "Makefile" ] && return
# Extract targets with ## comments
while IFS= read -r line; do
if [[ $line =~ ^([a-zA-Z_-]+):.*\#\#(.*)$ ]]; then
target="${BASH_REMATCH[1]}"
description="${BASH_REMATCH[2]}"
case "$target" in
lint|check) LINT_CMD="make $target" ;;
format|fmt) FORMAT_CMD="make $target" ;;
test|tests) TEST_CMD="make $target" ;;
build) BUILD_CMD="make $target" ;;
typecheck|types) TYPECHECK_CMD="make $target" ;;
dev|serve) DEV_CMD="make $target" ;;
esac
fi
done < Makefile
}
# Extract from package.json
extract_from_package_json() {
[ ! -f "package.json" ] && return
TYPECHECK_CMD=$(jq -r '.scripts.typecheck // .scripts["type-check"] // empty' package.json 2>/dev/null)
[ -n "$TYPECHECK_CMD" ] && TYPECHECK_CMD="npm run typecheck" || TYPECHECK_CMD="npx tsc --noEmit"
LINT_CMD=$(jq -r '.scripts.lint // empty' package.json 2>/dev/null)
[ -n "$LINT_CMD" ] && LINT_CMD="npm run lint" || LINT_CMD="npx eslint ."
FORMAT_CMD=$(jq -r '.scripts.format // empty' package.json 2>/dev/null)
[ -n "$FORMAT_CMD" ] && FORMAT_CMD="npm run format" || FORMAT_CMD="npx prettier --write ."
TEST_CMD=$(jq -r '.scripts.test // empty' package.json 2>/dev/null)
[ -n "$TEST_CMD" ] && TEST_CMD="npm test"
BUILD_CMD=$(jq -r '.scripts.build // empty' package.json 2>/dev/null)
[ -n "$BUILD_CMD" ] && BUILD_CMD="npm run build"
DEV_CMD=$(jq -r '.scripts.dev // .scripts.start // empty' package.json 2>/dev/null)
[ -n "$DEV_CMD" ] && DEV_CMD="npm run dev"
}
# Extract from composer.json
extract_from_composer_json() {
[ ! -f "composer.json" ] && return
LINT_CMD=$(jq -r '.scripts.lint // .scripts["cs:check"] // empty' composer.json 2>/dev/null)
[ -n "$LINT_CMD" ] && LINT_CMD="composer run lint"
FORMAT_CMD=$(jq -r '.scripts.format // .scripts["cs:fix"] // empty' composer.json 2>/dev/null)
[ -n "$FORMAT_CMD" ] && FORMAT_CMD="composer run format"
TEST_CMD=$(jq -r '.scripts.test // empty' composer.json 2>/dev/null)
[ -n "$TEST_CMD" ] && TEST_CMD="composer run test" || TEST_CMD="vendor/bin/phpunit"
TYPECHECK_CMD=$(jq -r '.scripts.phpstan // .scripts["stan"] // empty' composer.json 2>/dev/null)
[ -n "$TYPECHECK_CMD" ] && TYPECHECK_CMD="composer run phpstan" || {
if [ -f "phpstan.neon" ] || [ -f "Build/phpstan.neon" ]; then
TYPECHECK_CMD="vendor/bin/phpstan analyze"
fi
}
}
# Extract from pyproject.toml
extract_from_pyproject() {
[ ! -f "pyproject.toml" ] && return
# Check for ruff
if grep -q '\[tool.ruff\]' pyproject.toml; then
LINT_CMD="ruff check ."
FORMAT_CMD="ruff format ."
fi
# Check for black
if grep -q 'black' pyproject.toml; then
FORMAT_CMD="black ."
fi
# Check for mypy
if grep -q 'mypy' pyproject.toml; then
TYPECHECK_CMD="mypy ."
fi
# Check for pytest
if grep -q 'pytest' pyproject.toml; then
TEST_CMD="pytest"
fi
}
# Language-specific defaults
set_language_defaults() {
case "$LANGUAGE" in
"go")
[ -z "$TYPECHECK_CMD" ] && TYPECHECK_CMD="go build -v ./..."
[ -z "$LINT_CMD" ] && {
if [ -f ".golangci.yml" ] || [ -f ".golangci.yaml" ]; then
LINT_CMD="golangci-lint run ./..."
fi
}
[ -z "$FORMAT_CMD" ] && FORMAT_CMD="gofmt -w ."
[ -z "$TEST_CMD" ] && TEST_CMD="go test -v -race -short ./..."
[ -z "$BUILD_CMD" ] && BUILD_CMD="go build -v ./..."
;;
"php")
[ -z "$TYPECHECK_CMD" ] && {
if [ -f "phpstan.neon" ] || [ -f "Build/phpstan.neon" ]; then
TYPECHECK_CMD="vendor/bin/phpstan analyze"
fi
}
[ -z "$LINT_CMD" ] && LINT_CMD="vendor/bin/php-cs-fixer fix --dry-run"
[ -z "$FORMAT_CMD" ] && FORMAT_CMD="vendor/bin/php-cs-fixer fix"
[ -z "$TEST_CMD" ] && TEST_CMD="vendor/bin/phpunit"
;;
"typescript")
[ -z "$TYPECHECK_CMD" ] && TYPECHECK_CMD="npx tsc --noEmit"
[ -z "$LINT_CMD" ] && LINT_CMD="npx eslint ."
[ -z "$FORMAT_CMD" ] && FORMAT_CMD="npx prettier --write ."
[ -z "$TEST_CMD" ] && {
if [ -f "jest.config.js" ] || [ -f "jest.config.ts" ]; then
TEST_CMD="npm test"
elif grep -q 'vitest' package.json 2>/dev/null; then
TEST_CMD="npx vitest"
fi
}
;;
"python")
[ -z "$LINT_CMD" ] && LINT_CMD="ruff check ."
[ -z "$FORMAT_CMD" ] && FORMAT_CMD="ruff format ."
[ -z "$TYPECHECK_CMD" ] && TYPECHECK_CMD="mypy ."
[ -z "$TEST_CMD" ] && TEST_CMD="pytest"
;;
esac
}
# Run extraction
extract_from_makefile
extract_from_package_json
extract_from_composer_json
extract_from_pyproject
set_language_defaults
# Output JSON
jq -n \
--arg typecheck "$TYPECHECK_CMD" \
--arg lint "$LINT_CMD" \
--arg format "$FORMAT_CMD" \
--arg test "$TEST_CMD" \
--arg build "$BUILD_CMD" \
--arg dev "$DEV_CMD" \
'{
typecheck: $typecheck,
lint: $lint,
format: $format,
test: $test,
build: $build,
dev: $dev
}'

View File

@@ -0,0 +1,292 @@
#!/usr/bin/env bash
# Main AGENTS.md generator script
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
TEMPLATE_DIR="$SKILL_DIR/templates"
# Source helper library
source "$SCRIPT_DIR/lib/template.sh"
# Default options
PROJECT_DIR="${1:-.}"
STYLE="${STYLE:-thin}"
DRY_RUN=false
UPDATE_ONLY=false
FORCE=false
VERBOSE=false
# Parse flags
while [[ $# -gt 0 ]]; do
case $1 in
--style=*)
STYLE="${1#*=}"
shift
;;
--dry-run)
DRY_RUN=true
shift
;;
--update)
UPDATE_ONLY=true
shift
;;
--force)
FORCE=true
shift
;;
--verbose|-v)
VERBOSE=true
shift
;;
--help|-h)
cat <<EOF
Usage: generate-agents.sh [PROJECT_DIR] [OPTIONS]
Generate AGENTS.md files for a project following the public agents.md convention.
Options:
--style=thin|verbose Template style (default: thin)
--dry-run Preview what will be created
--update Update existing files only
--force Force regeneration of existing files
--verbose, -v Verbose output
--help, -h Show this help message
Examples:
generate-agents.sh . # Generate thin root + scoped files
generate-agents.sh . --dry-run # Preview changes
generate-agents.sh . --style=verbose # Use verbose root template
generate-agents.sh . --update # Update existing files
EOF
exit 0
;;
*)
PROJECT_DIR="$1"
shift
;;
esac
done
cd "$PROJECT_DIR"
log() {
[ "$VERBOSE" = true ] && echo "[INFO] $*" >&2
}
error() {
echo "[ERROR] $*" >&2
exit 1
}
# Detect project
log "Detecting project type..."
PROJECT_INFO=$("$SCRIPT_DIR/detect-project.sh" "$PROJECT_DIR")
[ "$VERBOSE" = true ] && echo "$PROJECT_INFO" | jq . >&2
LANGUAGE=$(echo "$PROJECT_INFO" | jq -r '.language')
VERSION=$(echo "$PROJECT_INFO" | jq -r '.version')
PROJECT_TYPE=$(echo "$PROJECT_INFO" | jq -r '.type')
[ "$LANGUAGE" = "unknown" ] && error "Could not detect project language"
# Detect scopes
log "Detecting scopes..."
SCOPES_INFO=$("$SCRIPT_DIR/detect-scopes.sh" "$PROJECT_DIR")
[ "$VERBOSE" = true ] && echo "$SCOPES_INFO" | jq . >&2
# Extract commands
log "Extracting build commands..."
COMMANDS=$("$SCRIPT_DIR/extract-commands.sh" "$PROJECT_DIR")
[ "$VERBOSE" = true ] && echo "$COMMANDS" | jq . >&2
# Generate root AGENTS.md
ROOT_FILE="$PROJECT_DIR/AGENTS.md"
if [ -f "$ROOT_FILE" ] && [ "$FORCE" = false ] && [ "$UPDATE_ONLY" = false ]; then
log "Root AGENTS.md already exists, skipping (use --force to regenerate)"
elif [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] Would create/update: $ROOT_FILE"
else
log "Generating root AGENTS.md..."
# Select template
if [ "$STYLE" = "verbose" ]; then
TEMPLATE="$TEMPLATE_DIR/root-verbose.md"
else
TEMPLATE="$TEMPLATE_DIR/root-thin.md"
fi
# Prepare template variables
declare -A vars
vars[TIMESTAMP]=$(get_timestamp)
vars[LANGUAGE_CONVENTIONS]=$(get_language_conventions "$LANGUAGE" "$VERSION")
vars[TYPECHECK_CMD]=$(echo "$COMMANDS" | jq -r '.typecheck')
vars[LINT_CMD]=$(echo "$COMMANDS" | jq -r '.lint')
vars[FORMAT_CMD]=$(echo "$COMMANDS" | jq -r '.format' | sed 's/^/ (file scope): /')
vars[TEST_CMD]=$(echo "$COMMANDS" | jq -r '.test')
vars[SCOPE_INDEX]=$(build_scope_index "$SCOPES_INFO")
# Verbose template additional vars
if [ "$STYLE" = "verbose" ]; then
vars[PROJECT_DESCRIPTION]="TODO: Add project description"
vars[VERSION]="$VERSION"
vars[BUILD_TOOL]=$(echo "$PROJECT_INFO" | jq -r '.build_tool')
vars[FRAMEWORK]=$(echo "$PROJECT_INFO" | jq -r '.framework')
vars[PROJECT_TYPE]="$PROJECT_TYPE"
vars[BUILD_CMD]=$(echo "$COMMANDS" | jq -r '.build')
vars[QUALITY_STANDARDS]="TODO: Add quality standards"
vars[SECURITY_SPECIFIC]="TODO: Add security-specific guidelines"
vars[TEST_COVERAGE]="40"
vars[TEST_FAST_CMD]=$(echo "$COMMANDS" | jq -r '.test')
vars[TEST_FULL_CMD]=$(echo "$COMMANDS" | jq -r '.test')
vars[ARCHITECTURE_DOC]="./docs/architecture.md"
vars[API_DOC]="./docs/api.md"
vars[CONTRIBUTING_DOC]="./CONTRIBUTING.md"
fi
# Language-specific conflict resolution
case "$LANGUAGE" in
"go")
vars[LANGUAGE_SPECIFIC_CONFLICT_RESOLUTION]="- For Go-specific patterns, defer to language idioms and standard library conventions"
;;
*)
vars[LANGUAGE_SPECIFIC_CONFLICT_RESOLUTION]=""
;;
esac
# Render template
render_template "$TEMPLATE" "$ROOT_FILE" vars
echo "✅ Created: $ROOT_FILE"
fi
# Generate scoped AGENTS.md files
SCOPE_COUNT=$(echo "$SCOPES_INFO" | jq '.scopes | length')
if [ "$SCOPE_COUNT" -eq 0 ]; then
log "No scopes detected (directories with <$MIN_FILES source files)"
else
log "Generating $SCOPE_COUNT scoped AGENTS.md files..."
while read -r scope; do
SCOPE_PATH=$(echo "$scope" | jq -r '.path')
SCOPE_TYPE=$(echo "$scope" | jq -r '.type')
SCOPE_FILE="$PROJECT_DIR/$SCOPE_PATH/AGENTS.md"
if [ -f "$SCOPE_FILE" ] && [ "$FORCE" = false ] && [ "$UPDATE_ONLY" = false ]; then
log "Scoped AGENTS.md already exists: $SCOPE_PATH, skipping"
continue
fi
if [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] Would create/update: $SCOPE_FILE"
continue
fi
# Select template based on scope type
SCOPE_TEMPLATE="$TEMPLATE_DIR/scoped/$SCOPE_TYPE.md"
if [ ! -f "$SCOPE_TEMPLATE" ]; then
log "No template for scope type: $SCOPE_TYPE, skipping $SCOPE_PATH"
continue
fi
# Prepare scoped template variables
declare -A scope_vars
scope_vars[TIMESTAMP]=$(get_timestamp)
scope_vars[SCOPE_NAME]=$(basename "$SCOPE_PATH")
scope_vars[SCOPE_DESCRIPTION]=$(get_scope_description "$SCOPE_TYPE")
scope_vars[FILE_PATH]="<file>"
scope_vars[HOUSE_RULES]=""
# Language-specific variables
case "$SCOPE_TYPE" in
"backend-go")
scope_vars[GO_VERSION]="$VERSION"
scope_vars[GO_MINOR_VERSION]=$(echo "$VERSION" | cut -d. -f2)
scope_vars[GO_TOOLS]="golangci-lint, gofmt"
scope_vars[ENV_VARS]="See .env.example"
scope_vars[BUILD_CMD]=$(echo "$COMMANDS" | jq -r '.build')
;;
"backend-php")
scope_vars[PHP_VERSION]="$VERSION"
FRAMEWORK=$(echo "$PROJECT_INFO" | jq -r '.framework')
scope_vars[FRAMEWORK]="$FRAMEWORK"
scope_vars[PHP_EXTENSIONS]="json, mbstring, xml"
scope_vars[ENV_VARS]="See .env.example"
scope_vars[PHPSTAN_LEVEL]="10"
scope_vars[BUILD_CMD]=$(echo "$COMMANDS" | jq -r '.build')
if [ "$FRAMEWORK" = "typo3" ]; then
scope_vars[FRAMEWORK_CONVENTIONS]="- TYPO3-specific: Use dependency injection, follow TYPO3 CGL"
scope_vars[FRAMEWORK_DOCS]="- TYPO3 documentation: https://docs.typo3.org"
else
scope_vars[FRAMEWORK_CONVENTIONS]=""
scope_vars[FRAMEWORK_DOCS]=""
fi
;;
"frontend-typescript")
scope_vars[NODE_VERSION]="$VERSION"
FRAMEWORK=$(echo "$PROJECT_INFO" | jq -r '.framework')
scope_vars[FRAMEWORK]="$FRAMEWORK"
scope_vars[PACKAGE_MANAGER]=$(echo "$PROJECT_INFO" | jq -r '.build_tool')
scope_vars[ENV_VARS]="See .env.example"
scope_vars[BUILD_CMD]=$(echo "$COMMANDS" | jq -r '.build')
scope_vars[DEV_CMD]=$(echo "$COMMANDS" | jq -r '.dev')
scope_vars[CSS_APPROACH]="CSS Modules"
case "$FRAMEWORK" in
"react")
scope_vars[FRAMEWORK_CONVENTIONS]="- Use functional components with hooks\n- Avoid class components"
scope_vars[FRAMEWORK_DOCS]="https://react.dev"
;;
"next.js")
scope_vars[FRAMEWORK_CONVENTIONS]="- Use App Router (app/)\n- Server Components by default"
scope_vars[FRAMEWORK_DOCS]="https://nextjs.org/docs"
;;
"vue")
scope_vars[FRAMEWORK_CONVENTIONS]="- Use Composition API\n- Avoid Options API for new code"
scope_vars[FRAMEWORK_DOCS]="https://vuejs.org/guide"
;;
*)
scope_vars[FRAMEWORK_CONVENTIONS]=""
scope_vars[FRAMEWORK_DOCS]=""
;;
esac
;;
"cli")
scope_vars[LANGUAGE]="$LANGUAGE"
CLI_FRAMEWORK="standard"
[ -f "go.mod" ] && grep -q "github.com/spf13/cobra" go.mod 2>/dev/null && CLI_FRAMEWORK="cobra"
[ -f "go.mod" ] && grep -q "github.com/urfave/cli" go.mod 2>/dev/null && CLI_FRAMEWORK="urfave/cli"
scope_vars[CLI_FRAMEWORK]="$CLI_FRAMEWORK"
scope_vars[BUILD_OUTPUT_PATH]="./bin/"
scope_vars[SETUP_INSTRUCTIONS]="- Build: $(echo "$COMMANDS" | jq -r '.build')"
scope_vars[BUILD_CMD]=$(echo "$COMMANDS" | jq -r '.build')
scope_vars[RUN_CMD]="./bin/$(basename "$PROJECT_DIR")"
scope_vars[TEST_CMD]=$(echo "$COMMANDS" | jq -r '.test')
scope_vars[LINT_CMD]=$(echo "$COMMANDS" | jq -r '.lint')
;;
esac
# Render template
render_template "$SCOPE_TEMPLATE" "$SCOPE_FILE" scope_vars
echo "✅ Created: $SCOPE_FILE"
done < <(echo "$SCOPES_INFO" | jq -c '.scopes[]')
fi
if [ "$DRY_RUN" = true ]; then
echo ""
echo "[DRY-RUN] No files were modified. Remove --dry-run to apply changes."
fi
echo ""
echo "✅ AGENTS.md generation complete!"
[ "$SCOPE_COUNT" -gt 0 ] && echo " Generated: 1 root + $SCOPE_COUNT scoped files"

View File

@@ -0,0 +1,178 @@
#!/usr/bin/env bash
# Validate AGENTS.md structure compliance
set -euo pipefail
PROJECT_DIR="${1:-.}"
cd "$PROJECT_DIR"
ERRORS=0
WARNINGS=0
error() {
echo "❌ ERROR: $*"
((ERRORS++))
}
warning() {
echo "⚠️ WARNING: $*"
((WARNINGS++))
}
success() {
echo "$*"
}
# Check if file has managed header
check_managed_header() {
local file="$1"
if grep -q "^<!-- Managed by agent:" "$file"; then
success "Managed header present: $file"
return 0
else
warning "Missing managed header: $file"
return 1
fi
}
# Check if root is thin (≤50 lines or has scope index)
check_root_is_thin() {
local file="$1"
local line_count=$(wc -l < "$file")
if [ "$line_count" -le 50 ]; then
success "Root is thin: $line_count lines"
return 0
elif grep -q "## Index of scoped AGENTS.md" "$file"; then
success "Root has scope index (verbose style acceptable)"
return 0
else
error "Root is bloated: $line_count lines and no scope index"
return 1
fi
}
# Check if root has precedence statement
check_precedence_statement() {
local file="$1"
if grep -qi "precedence" "$file" && grep -qi "closest.*AGENTS.md.*wins" "$file"; then
success "Precedence statement present"
return 0
else
error "Missing precedence statement in root"
return 1
fi
}
# Check if scoped file has all 9 sections
check_scoped_sections() {
local file="$1"
local required_sections=(
"## Overview"
"## Setup & environment"
"## Build & tests"
"## Code style & conventions"
"## Security & safety"
"## PR/commit checklist"
"## Good vs. bad examples"
"## When stuck"
)
local missing=()
for section in "${required_sections[@]}"; do
if ! grep -q "^$section" "$file"; then
missing+=("$section")
fi
done
if [ ${#missing[@]} -eq 0 ]; then
success "All required sections present: $file"
return 0
else
error "Missing sections in $file: ${missing[*]}"
return 1
fi
}
# Check if scope index links work
check_scope_links() {
local root_file="$1"
if ! grep -q "## Index of scoped AGENTS.md" "$root_file"; then
return 0 # No index, skip check
fi
# Extract links from scope index
local links=$(sed -n '/## Index of scoped AGENTS.md/,/^##/p' "$root_file" | grep -o '\./[^)]*AGENTS.md' || true)
if [ -z "$links" ]; then
warning "Scope index present but no links found"
return 1
fi
local broken=()
while read -r link; do
# Remove leading ./
local clean_link="${link#./}"
local full_path="$PROJECT_DIR/$clean_link"
if [ ! -f "$full_path" ]; then
broken+=("$link")
fi
done <<< "$links"
if [ ${#broken[@]} -eq 0 ]; then
success "All scope index links work"
return 0
else
error "Broken scope index links: ${broken[*]}"
return 1
fi
}
# Main validation
echo "Validating AGENTS.md structure in: $PROJECT_DIR"
echo ""
# Check root AGENTS.md
ROOT_FILE="$PROJECT_DIR/AGENTS.md"
if [ ! -f "$ROOT_FILE" ]; then
error "Root AGENTS.md not found"
else
echo "=== Root AGENTS.md ==="
check_managed_header "$ROOT_FILE"
check_root_is_thin "$ROOT_FILE"
check_precedence_statement "$ROOT_FILE"
check_scope_links "$ROOT_FILE"
echo ""
fi
# Check scoped AGENTS.md files
SCOPED_FILES=$(find "$PROJECT_DIR" -name "AGENTS.md" -not -path "$ROOT_FILE" 2>/dev/null || true)
if [ -n "$SCOPED_FILES" ]; then
echo "=== Scoped AGENTS.md Files ==="
while read -r file; do
rel_path="${file#$PROJECT_DIR/}"
echo "Checking: $rel_path"
check_managed_header "$file"
check_scoped_sections "$file"
echo ""
done <<< "$SCOPED_FILES"
fi
# Summary
echo "=== Validation Summary ==="
if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then
echo "✅ All checks passed!"
exit 0
elif [ $ERRORS -eq 0 ]; then
echo "⚠️ Validation passed with $WARNINGS warning(s)"
exit 0
else
echo "❌ Validation failed with $ERRORS error(s) and $WARNINGS warning(s)"
exit 1
fi

View File

@@ -0,0 +1,24 @@
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: {{TIMESTAMP}} -->
# AGENTS.md (root)
This file explains repo-wide conventions and where to find scoped rules.
**Precedence:** the **closest `AGENTS.md`** to the files you're changing wins. Root holds global defaults only.
## Global rules
- Keep diffs small; add tests for new code paths
- Ask first before: adding heavy deps, running full e2e suites, or repo-wide rewrites
- Never commit secrets or sensitive data to the repository
{{LANGUAGE_CONVENTIONS}}
## Minimal pre-commit checks
- Typecheck: {{TYPECHECK_CMD}}
- Lint/format: {{LINT_CMD}}{{FORMAT_CMD}}
- Tests: {{TEST_CMD}}
## Index of scoped AGENTS.md
{{SCOPE_INDEX}}
## When instructions conflict
- The nearest `AGENTS.md` wins. Explicit user prompts override files.
{{LANGUAGE_SPECIFIC_CONFLICT_RESOLUTION}}

View File

@@ -0,0 +1,63 @@
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: {{TIMESTAMP}} -->
# AGENTS.md (root)
**Precedence:** The **closest AGENTS.md** to changed files wins. Root holds global defaults only.
## Project Overview
{{PROJECT_DESCRIPTION}}
**Tech Stack**: {{LANGUAGE}} {{VERSION}}, {{BUILD_TOOL}}, {{FRAMEWORK}}
**Type**: {{PROJECT_TYPE}}
## Global Rules
- Keep PRs small (~≤300 net LOC)
- Conventional Commits: `type(scope): subject`
- Ask before: heavy deps, full e2e, repo rewrites
- Never commit secrets or PII
{{LANGUAGE_CONVENTIONS}}
## Development Workflow
1. Create feature branch: `git checkout -b feature/description`
2. Make changes with tests
3. Run pre-commit checks (see below)
4. Commit with conventional format
5. Push and create PR
6. Address review feedback
7. Merge when approved
## Pre-commit Checks
**Always run before committing:**
- Typecheck: {{TYPECHECK_CMD}}
- Lint: {{LINT_CMD}}
- Format: {{FORMAT_CMD}}
- Tests: {{TEST_CMD}}
- Build: {{BUILD_CMD}}
## Code Quality Standards
{{QUALITY_STANDARDS}}
## Security & Safety
- Never commit secrets, credentials, or PII
- Validate all user inputs
- Use parameterized queries for database access
- Keep dependencies updated
{{SECURITY_SPECIFIC}}
## Testing Requirements
- Write tests for new features
- Maintain {{TEST_COVERAGE}}% minimum coverage
- Run fast tests locally: {{TEST_FAST_CMD}}
- Run full suite in CI: {{TEST_FULL_CMD}}
## Index of Scoped AGENTS.md
{{SCOPE_INDEX}}
## When Instructions Conflict
Nearest AGENTS.md wins. User prompts override files.
{{LANGUAGE_SPECIFIC_CONFLICT_RESOLUTION}}
## Documentation
- Architecture: {{ARCHITECTURE_DOC}}
- API docs: {{API_DOC}}
- Contributing: {{CONTRIBUTING_DOC}}

View File

@@ -0,0 +1,77 @@
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: {{TIMESTAMP}} -->
# AGENTS.md — {{SCOPE_NAME}}
## Overview
{{SCOPE_DESCRIPTION}}
## Setup & environment
- Install: `go mod download`
- Go version: {{GO_VERSION}}
- Required tools: {{GO_TOOLS}}
- Environment variables: {{ENV_VARS}}
## Build & tests (prefer file-scoped)
- Typecheck a file: `go build -v {{FILE_PATH}}`
- Format a file: `gofmt -w {{FILE_PATH}}`
- Lint a file: `golangci-lint run {{FILE_PATH}}`
- Test a file: `go test -v -race -short {{FILE_PATH}}`
- Build: {{BUILD_CMD}}
## Code style & conventions
- Follow Go 1.{{GO_MINOR_VERSION}} idioms
- Use standard library over external deps when possible
- Errors: wrap with `fmt.Errorf("context: %w", err)`
- Naming: `camelCase` for private, `PascalCase` for exported
- Struct tags: use canonical form (json, yaml, etc.)
- Comments: complete sentences ending with period
- Package docs: first sentence summarizes purpose
## Security & safety
- Validate all inputs from external sources
- Use `context.Context` for cancellation and timeouts
- Avoid goroutine leaks: always ensure termination paths
- Sensitive data: never log or include in errors
- SQL: use parameterized queries only
- File paths: validate and sanitize user-provided paths
## PR/commit checklist
- [ ] Tests pass: `go test -v -race ./...`
- [ ] Lint clean: `golangci-lint run ./...`
- [ ] Formatted: `gofmt -w .`
- [ ] No goroutine leaks
- [ ] Error messages are descriptive
- [ ] Public APIs have godoc comments
## Good vs. bad examples
**Good**: Descriptive error wrapping
```go
if err := db.Query(); err != nil {
return fmt.Errorf("failed to query users table: %w", err)
}
```
**Bad**: Generic error messages
```go
if err := db.Query(); err != nil {
return fmt.Errorf("error: %w", err)
}
```
**Good**: Proper context usage
```go
func (s *Service) FetchData(ctx context.Context, id string) error {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
return s.client.Get(ctx, id)
}
```
## When stuck
- Check Go documentation: https://pkg.go.dev
- Review existing patterns in this codebase
- Check root AGENTS.md for project-wide conventions
- Run `go doc <package>` for standard library help
## House Rules (optional)
{{HOUSE_RULES}}

View File

@@ -0,0 +1,85 @@
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: {{TIMESTAMP}} -->
# AGENTS.md — {{SCOPE_NAME}}
## Overview
{{SCOPE_DESCRIPTION}}
## Setup & environment
- Install: `composer install`
- PHP version: {{PHP_VERSION}}
- Framework: {{FRAMEWORK}}
- Required extensions: {{PHP_EXTENSIONS}}
- Environment variables: {{ENV_VARS}}
## Build & tests (prefer file-scoped)
- Typecheck a file: `vendor/bin/phpstan analyze {{FILE_PATH}} --level={{PHPSTAN_LEVEL}}`
- Format a file: `vendor/bin/php-cs-fixer fix {{FILE_PATH}}`
- Lint a file: `php -l {{FILE_PATH}}`
- Test a file: `vendor/bin/phpunit {{FILE_PATH}}`
- Build: {{BUILD_CMD}}
## Code style & conventions
- Follow PSR-12 coding standard
- Use strict types: `declare(strict_types=1);`
- Type hints: always use for parameters and return types
- Naming: `camelCase` for methods, `PascalCase` for classes
- Visibility: always declare (public, protected, private)
- PHPDoc: required for public APIs, include `@param` and `@return`
{{FRAMEWORK_CONVENTIONS}}
## Security & safety
- Validate and sanitize all user inputs
- Use prepared statements for database queries
- Escape output in templates
- Never use `eval()` or dynamic code execution
- Sensitive data: never log or expose in errors
- CSRF protection: enable for all forms
- XSS protection: escape all user-generated content
## PR/commit checklist
- [ ] Tests pass: `vendor/bin/phpunit`
- [ ] PHPStan Level {{PHPSTAN_LEVEL}} clean: `vendor/bin/phpstan analyze`
- [ ] PSR-12 compliant: `vendor/bin/php-cs-fixer fix --dry-run`
- [ ] No deprecated functions used
- [ ] Public methods have PHPDoc
- [ ] Security: inputs validated, outputs escaped
## Good vs. bad examples
**Good**: Proper type hints and strict types
```php
declare(strict_types=1);
public function calculateTotal(int $quantity, float $price): float
{
return $quantity * $price;
}
```
**Bad**: Missing type hints
```php
public function calculateTotal($quantity, $price)
{
return $quantity * $price;
}
```
**Good**: Prepared statements
```php
$stmt = $db->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $userId]);
```
**Bad**: String concatenation
```php
$result = $db->query("SELECT * FROM users WHERE id = " . $userId);
```
## When stuck
- Check PHP documentation: https://www.php.net
- {{FRAMEWORK_DOCS}}
- Review existing patterns in this codebase
- Check root AGENTS.md for project-wide conventions
## House Rules (optional)
{{HOUSE_RULES}}

View File

@@ -0,0 +1,84 @@
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: {{TIMESTAMP}} -->
# AGENTS.md — {{SCOPE_NAME}}
## Overview
{{SCOPE_DESCRIPTION}}
Command-line interface tools and entry points.
## Setup & environment
{{SETUP_INSTRUCTIONS}}
- CLI framework: {{CLI_FRAMEWORK}}
- Build output: {{BUILD_OUTPUT_PATH}}
## Build & tests (prefer file-scoped)
- Build CLI: {{BUILD_CMD}}
- Run CLI: {{RUN_CMD}}
- Test: {{TEST_CMD}}
- Lint: {{LINT_CMD}}
## Code style & conventions
- Use flag parsing library consistently ({{CLI_FRAMEWORK}})
- Provide `--help` for all commands and subcommands
- Use `--version` to display version information
- Exit codes: 0 = success, 1 = general error, 2 = usage error
- Output: structured (JSON) for scripts, human-readable for interactive
- Errors: write to stderr, not stdout
- Progress: show for long-running operations
- Interactive prompts: support non-interactive mode with flags
## Security & safety
- Validate all file paths and prevent directory traversal
- Never execute user-provided code without explicit confirmation
- Sensitive data: never log or display in plain text
- Config files: validate schema and permissions
- Network operations: timeout and retry with backoff
## PR/commit checklist
- [ ] `--help` text is clear and accurate
- [ ] `--version` displays correct version
- [ ] Exit codes are correct
- [ ] Errors go to stderr
- [ ] Long operations show progress
- [ ] Works in non-interactive mode
- [ ] Tests cover main workflows
## Good vs. bad examples
**Good**: Proper error handling
```{{LANGUAGE}}
if err := runCommand(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
```
**Bad**: Errors to stdout
```{{LANGUAGE}}
if err := runCommand(); err != nil {
fmt.Println("Error:", err)
}
```
**Good**: Clear help text
```
Usage: myapp <command> [options]
Commands:
init Initialize a new project
build Build the project
deploy Deploy to production
Options:
--config string Config file path (default: config.yaml)
--verbose Enable verbose output
```
## When stuck
- Review {{CLI_FRAMEWORK}} documentation
- Check existing commands for patterns
- Test with `--help` to ensure clarity
- Check root AGENTS.md for project conventions
## House Rules (optional)
{{HOUSE_RULES}}

View File

@@ -0,0 +1,96 @@
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: {{TIMESTAMP}} -->
# AGENTS.md — {{SCOPE_NAME}}
## Overview
{{SCOPE_DESCRIPTION}}
## Setup & environment
- Install: `npm install` or `yarn install`
- Node version: {{NODE_VERSION}}
- Framework: {{FRAMEWORK}}
- Package manager: {{PACKAGE_MANAGER}}
- Environment variables: {{ENV_VARS}}
## Build & tests (prefer file-scoped)
- Typecheck a file: `npx tsc --noEmit {{FILE_PATH}}`
- Lint a file: `npx eslint {{FILE_PATH}}`
- Format a file: `npx prettier --write {{FILE_PATH}}`
- Test a file: `npm test {{FILE_PATH}}`
- Build: {{BUILD_CMD}}
- Dev server: {{DEV_CMD}}
## Code style & conventions
- TypeScript strict mode enabled
- Use functional components with hooks (React)
- Naming: `camelCase` for variables/functions, `PascalCase` for components
- File naming: `ComponentName.tsx`, `utilityName.ts`
- Imports: group and sort (external, internal, types)
- CSS: {{CSS_APPROACH}} (CSS Modules, Tailwind, styled-components, etc.)
{{FRAMEWORK_CONVENTIONS}}
## Security & safety
- Sanitize user inputs before rendering
- Use `dangerouslySetInnerHTML` only with sanitized content
- Validate environment variables at build time
- Never expose secrets in client-side code
- Use HTTPS for all API calls
- Implement CSP headers
- WCAG 2.2 AA accessibility compliance
## PR/commit checklist
- [ ] Tests pass: `npm test`
- [ ] TypeScript compiles: `npx tsc --noEmit`
- [ ] Lint clean: `npm run lint`
- [ ] Formatted: `npm run format`
- [ ] Accessibility: keyboard navigation works, ARIA labels present
- [ ] Responsive: tested on mobile, tablet, desktop
- [ ] Performance: no unnecessary re-renders
## Good vs. bad examples
**Good**: Proper TypeScript typing
```typescript
interface User {
id: string;
name: string;
email: string;
}
function UserCard({ user }: { user: User }): JSX.Element {
return <div>{user.name}</div>;
}
```
**Bad**: Using `any`
```typescript
function UserCard({ user }: { user: any }) {
return <div>{user.name}</div>;
}
```
**Good**: Accessible button
```tsx
<button
onClick={handleClick}
aria-label="Close dialog"
type="button"
>
<CloseIcon />
</button>
```
**Bad**: Non-semantic click handler
```tsx
<div onClick={handleClick}>
<CloseIcon />
</div>
```
## When stuck
- Check {{FRAMEWORK}} documentation: {{FRAMEWORK_DOCS}}
- Review TypeScript handbook: https://www.typescriptlang.org/docs/
- Check root AGENTS.md for project-wide conventions
- Review existing components for patterns
## House Rules (optional)
{{HOUSE_RULES}}

View File

@@ -0,0 +1,345 @@
# Netresearch Brand Guidelines Skill
A comprehensive Claude Code skill for implementing Netresearch brand guidelines in web projects. Ensures consistent brand application across all digital touchpoints.
## Overview
This skill provides complete brand guidelines, reference documentation, and ready-to-use templates for implementing Netresearch's visual identity on web projects. It enables Claude Code to help developers and designers maintain brand consistency across websites, applications, and digital materials.
## Features
### 📋 Comprehensive Brand Guidelines
- **SKILL.md** - Complete brand implementation guide covering:
- Brand identity and core values
- Color system with accessibility compliance
- Typography standards (Raleway, Open Sans, Calibri)
- Logo usage guidelines and restrictions
- Web design principles and best practices
- Component styles and patterns
- Social media specifications
- Image guidelines
- Accessibility requirements (WCAG AA)
- Implementation checklists
### 🎨 Reference Documentation
- **colors.md** - Detailed color palette with:
- Technical specifications (Hex, RGB, CMYK, Pantone, NCS)
- Usage guidelines and approved combinations
- WCAG AA contrast ratios
- CSS custom properties
- Accessibility compliance details
- **typography.md** - Complete typography system with:
- Font specifications and licensing
- Web font loading strategies
- Typography scale and hierarchy
- Responsive typography patterns
- Line height and spacing guidelines
- Performance optimization
- **web-design.md** - Web implementation patterns:
- Responsive grid system
- Component library (buttons, cards, forms, navigation)
- Layout patterns
- Animations and transitions
- Accessibility considerations
### 🎯 Templates & Examples
- **landing-page.html** - Complete landing page template
- **styles.css** - Production-ready brand CSS with all components
- **components.html** - Interactive component showcase
## Installation
### Via Claude Code Marketplace
```bash
/plugin marketplace add netresearch/claude-code-marketplace
/plugin install netresearch-branding
```
### Manual Installation
```bash
# Using curl
curl -L https://github.com/netresearch/netresearch-branding-skill/archive/refs/heads/main.zip -o netresearch-branding.zip
unzip netresearch-branding.zip -d ~/.claude/skills/
mv ~/.claude/skills/netresearch-branding-skill-main ~/.claude/skills/netresearch-branding
# Or using git
git clone https://github.com/netresearch/netresearch-branding-skill.git ~/.claude/skills/netresearch-branding
```
## Usage
### Automatic Activation
The skill activates automatically when working on:
- Netresearch web projects
- Brand implementation tasks
- UI/UX development requiring brand compliance
- Documentation needing brand guidelines
### Manual Invocation
```bash
/skill netresearch-branding
```
### Example Workflows
#### **Creating a New Landing Page**
```
User: "Create a landing page following Netresearch brand guidelines"
Claude: [Activates netresearch-branding skill]
- Uses brand colors (Turquoise #2F99A4, Orange #FF4D00)
- Applies Raleway for headlines, Open Sans for body
- Implements high white space design principle
- Creates responsive layout with brand components
- Ensures WCAG AA accessibility compliance
- Delivers production-ready HTML/CSS
```
#### **Brand Compliance Check**
```
User: "Review this website design for Netresearch brand compliance"
Claude: [Activates netresearch-branding skill]
- Verifies color usage (primary vs accent)
- Checks typography (correct fonts and weights)
- Validates spacing and white space principles
- Reviews component implementations
- Assesses accessibility compliance
- Provides specific improvement recommendations
```
#### **Component Development**
```
User: "Create a contact form with Netresearch branding"
Claude: [Activates netresearch-branding skill]
- Uses branded form components
- Applies correct input styling
- Implements brand buttons
- Ensures proper spacing
- Validates accessibility
- Provides complete HTML/CSS
```
## Brand Quick Reference
### Colors
| Color | Hex | Usage |
|-------|-----|-------|
| **Turquoise** | `#2F99A4` | Primary brand color, links, CTAs |
| **Orange** | `#FF4D00` | Accent color, emphasis only |
| **Anthracite** | `#585961` | Primary text color |
| **Light Grey** | `#CCCDCC` | Borders, backgrounds |
| **White** | `#FFFFFF` | Primary background |
### Typography
| Element | Font | Size | Weight |
|---------|------|------|--------|
| **Headlines (Web)** | Raleway | 48px-56px | 700 |
| **Body (Web)** | Open Sans | 16px | 400 |
| **Buttons** | Raleway | 16px | 600 |
| **Documents** | Calibri | 11-12pt | 400/700 |
### Key Principles
1. **High White Space** - Generous padding and margins throughout
2. **Responsive Design** - Mobile-first approach with defined breakpoints
3. **Accessibility** - WCAG AA compliance mandatory
4. **Color Hierarchy** - Turquoise primary, orange accent only
5. **Clean Typography** - Clear hierarchy with Raleway + Open Sans
## File Structure
```
netresearch-branding-skill/
├── SKILL.md # Main skill file with guidelines
├── README.md # This file
├── references/
│ ├── colors.md # Color palette reference
│ ├── typography.md # Typography system
│ └── web-design.md # Web components & patterns
├── templates/
│ ├── landing-page.html # Landing page template
│ └── styles.css # Brand CSS framework
└── examples/
└── components.html # Interactive component showcase
```
## Brand Component Library
### Buttons
```html
<!-- Primary action -->
<button class="btn-primary">Get Started</button>
<!-- Secondary action -->
<button class="btn-secondary">Learn More</button>
<!-- Tertiary action -->
<button class="btn-outline">Contact Us</button>
```
### Cards
```html
<div class="card">
<div class="card-content">
<h3 class="card-title">Card Title</h3>
<p class="card-text">Card description.</p>
<a href="#" class="link-standalone">Learn more</a>
</div>
</div>
```
### Forms
```html
<div class="form-group">
<label for="email" class="form-label">Email*</label>
<input type="email" id="email" class="form-input"
placeholder="your@email.com" required>
</div>
```
## CSS Custom Properties
The skill includes a complete set of CSS custom properties for easy implementation:
```css
:root {
/* Brand Colors */
--color-primary: #2F99A4;
--color-accent: #FF4D00;
--color-text-primary: #585961;
/* Typography */
--font-headline: 'Raleway', sans-serif;
--font-body: 'Open Sans', sans-serif;
/* Spacing */
--spacing-xs: 8px;
--spacing-sm: 12px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-xxl: 48px;
--spacing-3xl: 64px;
--spacing-4xl: 96px;
}
```
## Accessibility Compliance
All brand implementations must meet **WCAG AA** standards:
**Text Contrast Ratios:**
- Anthracite on white: 8.3:1 (AAA)
- White on turquoise: 4.5:1 (AA)
- Turquoise on white: 3.8:1 (AA for large text)
**Keyboard Navigation:**
- All interactive elements accessible
- Visible focus states
- Logical tab order
**Screen Readers:**
- Semantic HTML
- ARIA labels where needed
- Alternative text for images
## Responsive Breakpoints
```css
/* Mobile First Approach */
/* Extra Small: 0-599px (default) */
/* Small: 600px+ */
/* Medium: 768px+ */
/* Large: 1024px+ */
/* Extra Large: 1440px+ */
```
## Social Media Specifications
### Profile Images
- **Facebook:** 180 × 180 px (displays at 170 × 170 px)
- **LinkedIn:** 400 × 400 px (displays at 300 × 300 px)
- **Twitter:** 400 × 400 px (displays at 200 × 200 px)
- **XING:** 1024 × 1024 px
### Header/Cover Images
- **Facebook:** 820 × 312 px
- **LinkedIn (Company):** 1128 × 191 px
- **Twitter:** 1500 × 500 px
- **XING:** 970 × 250 px
## Quality Checklist
Before deploying any branded material:
**Visual Design:**
- [ ] Colors match exact hex values
- [ ] Turquoise is primary, orange is accent only
- [ ] High white space maintained
- [ ] Typography uses Raleway + Open Sans
**Technical:**
- [ ] Responsive design implemented
- [ ] WCAG AA compliance verified
- [ ] Fonts loaded efficiently
- [ ] CSS follows naming conventions
**Content:**
- [ ] Logo used correctly
- [ ] Images follow brand guidelines
- [ ] Consistent tone and voice
- [ ] No unapproved color combinations
## Contributing
Contributions are welcome! Please follow these guidelines:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/improvement`)
3. Make your changes
4. Test thoroughly against brand guidelines
5. Commit your changes (`git commit -m 'Add improvement'`)
6. Push to the branch (`git push origin feature/improvement`)
7. Create a Pull Request
## License
This skill is licensed under GPL-2.0-or-later, matching the TYPO3 project license.
## Support
**Issues and Questions:**
- GitHub Issues: [Report issues](https://github.com/netresearch/netresearch-branding-skill/issues)
**Netresearch Contact:**
- **Website:** https://www.netresearch.de
- **Email:** info@netresearch.de
- **Phone:** +49 341 49288-0
- **Address:** Nonnenstraße 11d, 04229 Leipzig, Germany
## Related Skills
- **typo3-docs** - TYPO3 extension documentation creation
- **typo3-testing** - TYPO3 extension testing infrastructure
- **typo3-conformance** - TYPO3 coding standards evaluation
## Credits
Created by Netresearch DTT GmbH for consistent brand implementation across digital projects.
**Maintained By:** Netresearch DTT GmbH, Leipzig, Germany
---
**Version:** 1.0.0
**Last Updated:** 2025-10-18

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg
viewBox="-75 -75 440 440"
zoomAndPan="disable"
version="1.2"
baseProfile="tiny-ps"
xmlns="http://www.w3.org/2000/svg">
<title>Netresearch DTT GmbH</title>
<g>
<path
fill="#2999a4"
d="M209.6,0V31.62h32.77a26.38,26.38,0,0,1,26.44,26.43V242a26.38,26.38,0,0,1-26.44,26.44H209.6V300h47.93a42.77,42.77,0,0,0,42.86-42.86V42.89A42.76,42.76,0,0,0,257.53,0ZM43.25,0A42.76,42.76,0,0,0,.39,42.89V257.18A42.76,42.76,0,0,0,43.25,300H91.18V268.46H58.4A26.38,26.38,0,0,1,32,242v-184A26.37,26.37,0,0,1,58.4,31.62H91.18V0Z"
transform="translate(-0.39 -0.04)" />
<path
fill="#595a62"
d="M221.44,120.41c0-34.48-13.94-57.82-48.93-57.82-26.62,0-48.54,7.74-64.17,26.56l-.7-22.06-28.31.06V232.94h31.59V124.69c7.14-18.38,32.14-34.8,53-34.5,27.38.4,25.2,26.24,26,45.81v96.94h31.58"
transform="translate(-0.39 -0.04)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 921 B

View File

@@ -0,0 +1,54 @@
# SKILL.md Refactoring Summary
**Date:** 2025-11-14
**Version Change:** 1.0.0 → 1.1.0
**Skill:** netresearch-branding
## Changes Applied
### Pattern 1: Removed "## Overview" Section
- **Before:** Lines 14-23 contained "## Overview" section
- **After:** Removed entire section
- **Rationale:** Content duplicated YAML description; no unique value
### Pattern 2: Converted Do's/Don'ts to Imperative Form
- **Before:** "Do's:" and "Don'ts:" bullet lists throughout
- **After:** "When X" trigger-based imperative instructions
#### Color Application Rules
- **Before:** Separate "Do's" and "Don'ts" lists
- **After:** "When applying brand colors" with unified action-oriented instructions
- Combined positive actions and avoidances into cohesive workflow
#### Typography Application Standards
- **Before:** "Typography Do's and Don'ts" with separate lists
- **After:** "When applying typography" with unified standards
- Converted each guideline to action-oriented instruction
### Pattern 3: Converted "## Implementation Checklist" to Procedures
- **Before:** Three checklists (Design Phase, Development Phase, Quality Assurance) with checkbox items
- **After:** "## Pre-Launch Validation Procedures" with numbered procedural steps
- **Changes:**
- Design Phase → Design Phase Validation (6 numbered steps)
- Development Phase → Development Phase Validation (8 numbered steps)
- Quality Assurance → Quality Assurance Validation (8 numbered steps)
- Brand Compliance Review → Brand Compliance Validation (3 check categories with numbered steps)
## Impact Analysis
**Readability:** Significantly improved - clear action-oriented instructions
**Consistency:** Aligned with skill-creator best practices
**Usability:** Enhanced - readers execute procedures rather than check boxes
**Clarity:** Removed redundant overview; eliminated Do's/Don'ts duplication
## Files Modified
- `/SKILL.md` (lines 1-866)
## Verification
- Version number updated in YAML frontmatter: ✓
- Overview section removed: ✓
- All Do's/Don'ts converted to imperative: ✓
- Checklists converted to procedures: ✓
- No broken internal references: ✓

View File

@@ -0,0 +1,313 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Netresearch Brand Components</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@400;600;700&family=Open+Sans:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../templates/styles.css">
<style>
.component-showcase {
padding: 48px 24px;
max-width: 1200px;
margin: 0 auto;
}
.component-section {
margin-bottom: 64px;
padding: 32px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.component-section h2 {
border-bottom: 3px solid #2F99A4;
padding-bottom: 12px;
margin-bottom: 24px;
}
.component-demo {
margin: 24px 0;
padding: 24px;
background: #F5F5F5;
border-radius: 4px;
}
.code-block {
background: #585961;
color: #fff;
padding: 16px;
border-radius: 4px;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 14px;
margin-top: 16px;
}
</style>
</head>
<body>
<div class="component-showcase">
<h1 style="text-align: center; margin-bottom: 48px;">Netresearch Brand Components</h1>
<!-- Buttons -->
<div class="component-section">
<h2>Buttons</h2>
<div class="component-demo">
<h3>Primary Buttons</h3>
<button class="btn-primary btn-small">Small Button</button>
<button class="btn-primary">Default Button</button>
<button class="btn-primary btn-large">Large Button</button>
</div>
<div class="component-demo">
<h3>Secondary Buttons</h3>
<button class="btn-secondary btn-small">Small Button</button>
<button class="btn-secondary">Default Button</button>
<button class="btn-secondary btn-large">Large Button</button>
</div>
<div class="component-demo" style="background: #2F99A4; color: white;">
<h3 style="color: white;">Outline Buttons (on dark background)</h3>
<button class="btn-outline btn-small">Small Button</button>
<button class="btn-outline">Default Button</button>
<button class="btn-outline btn-large">Large Button</button>
</div>
<div class="code-block">
&lt;button class="btn-primary"&gt;Primary Action&lt;/button&gt;
&lt;button class="btn-secondary"&gt;Secondary Action&lt;/button&gt;
&lt;button class="btn-outline"&gt;Tertiary Action&lt;/button&gt;
&lt;!-- Sizes --&gt;
&lt;button class="btn-primary btn-small"&gt;Small&lt;/button&gt;
&lt;button class="btn-primary"&gt;Default&lt;/button&gt;
&lt;button class="btn-primary btn-large"&gt;Large&lt;/button&gt;
</div>
</div>
<!-- Cards -->
<div class="component-section">
<h2>Cards</h2>
<div class="component-demo">
<div class="three-column">
<div class="card">
<div class="card-content">
<h3 class="card-title">Simple Card</h3>
<p class="card-text">This is a basic card with title and text content.</p>
<a href="#" class="link-standalone">Learn more</a>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="card-title">Card with Footer</h3>
<p class="card-text">This card includes a footer section with metadata.</p>
</div>
<div class="card-footer">
<span class="card-meta">Published: Jan 1, 2025</span>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="card-title">Card with Button</h3>
<p class="card-text">This card has a primary action button.</p>
<button class="btn-primary">Take Action</button>
</div>
</div>
</div>
</div>
<div class="code-block">
&lt;div class="card"&gt;
&lt;div class="card-content"&gt;
&lt;h3 class="card-title"&gt;Card Title&lt;/h3&gt;
&lt;p class="card-text"&gt;Card description.&lt;/p&gt;
&lt;a href="#" class="link-standalone"&gt;Learn more&lt;/a&gt;
&lt;/div&gt;
&lt;div class="card-footer"&gt;
&lt;span class="card-meta"&gt;Metadata&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
</div>
</div>
<!-- Typography -->
<div class="component-section">
<h2>Typography</h2>
<div class="component-demo">
<h1>Heading 1 - Raleway Bold 48px</h1>
<h2>Heading 2 - Raleway Bold 36px</h2>
<h3>Heading 3 - Raleway SemiBold 28px</h3>
<h4>Heading 4 - Raleway SemiBold 22px</h4>
<p class="lead">Lead paragraph - Open Sans Regular 20px with comfortable line height for improved readability.</p>
<p>Body text - Open Sans Regular 16px. This is the standard paragraph text used throughout the website. It maintains excellent readability with a line-height of 1.6.</p>
<p><small>Small text - Open Sans Regular 14px for captions and metadata.</small></p>
</div>
<div class="code-block">
&lt;h1&gt;Heading 1&lt;/h1&gt;
&lt;h2&gt;Heading 2&lt;/h2&gt;
&lt;h3&gt;Heading 3&lt;/h3&gt;
&lt;p class="lead"&gt;Lead paragraph&lt;/p&gt;
&lt;p&gt;Body text&lt;/p&gt;
&lt;small&gt;Small text&lt;/small&gt;
</div>
</div>
<!-- Links -->
<div class="component-section">
<h2>Links</h2>
<div class="component-demo">
<p>This is a paragraph with an <a href="#">inline link</a> within the text.</p>
<p><a href="#" class="link-standalone">Standalone link with arrow</a></p>
<p><a href="document.pdf" download>Download link with icon</a></p>
<p><a href="https://example.com" target="_blank">External link with icon</a></p>
</div>
<div class="code-block">
&lt;!-- Inline link --&gt;
&lt;a href="#"&gt;Link text&lt;/a&gt;
&lt;!-- Standalone link with arrow --&gt;
&lt;a href="#" class="link-standalone"&gt;Learn more&lt;/a&gt;
&lt;!-- Download link --&gt;
&lt;a href="file.pdf" download&gt;Download PDF&lt;/a&gt;
&lt;!-- External link --&gt;
&lt;a href="https://example.com" target="_blank"&gt;External&lt;/a&gt;
</div>
</div>
<!-- Forms -->
<div class="component-section">
<h2>Forms</h2>
<div class="component-demo">
<form style="max-width: 600px;">
<div class="form-group">
<label for="demo-name" class="form-label">Name*</label>
<input type="text" id="demo-name" class="form-input" placeholder="Your name" required>
</div>
<div class="form-group">
<label for="demo-email" class="form-label">Email*</label>
<input type="email" id="demo-email" class="form-input" placeholder="your@email.com" required>
</div>
<div class="form-group">
<label for="demo-message" class="form-label">Message*</label>
<textarea id="demo-message" class="form-textarea" placeholder="Your message" required></textarea>
</div>
<button type="submit" class="btn-primary">Submit Form</button>
</form>
</div>
<div class="code-block">
&lt;form&gt;
&lt;div class="form-group"&gt;
&lt;label for="name" class="form-label"&gt;Name*&lt;/label&gt;
&lt;input type="text" id="name" class="form-input"
placeholder="Your name" required&gt;
&lt;/div&gt;
&lt;div class="form-group"&gt;
&lt;label for="message" class="form-label"&gt;Message*&lt;/label&gt;
&lt;textarea id="message" class="form-textarea"
placeholder="Your message" required&gt;&lt;/textarea&gt;
&lt;/div&gt;
&lt;button type="submit" class="btn-primary"&gt;Submit&lt;/button&gt;
&lt;/form&gt;
</div>
</div>
<!-- Color Palette -->
<div class="component-section">
<h2>Color Palette</h2>
<div class="component-demo">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;">
<div style="text-align: center;">
<div style="background: #2F99A4; height: 100px; border-radius: 8px; margin-bottom: 8px;"></div>
<strong>Turquoise Primary</strong><br>
<code>#2F99A4</code>
</div>
<div style="text-align: center;">
<div style="background: #FF4D00; height: 100px; border-radius: 8px; margin-bottom: 8px;"></div>
<strong>Orange Accent</strong><br>
<code>#FF4D00</code>
</div>
<div style="text-align: center;">
<div style="background: #585961; height: 100px; border-radius: 8px; margin-bottom: 8px;"></div>
<strong>Anthracite Text</strong><br>
<code>#585961</code>
</div>
<div style="text-align: center;">
<div style="background: #CCCDCC; height: 100px; border-radius: 8px; margin-bottom: 8px;"></div>
<strong>Light Grey</strong><br>
<code>#CCCDCC</code>
</div>
<div style="text-align: center;">
<div style="background: #FFFFFF; height: 100px; border-radius: 8px; margin-bottom: 8px; border: 1px solid #E5E5E5;"></div>
<strong>White</strong><br>
<code>#FFFFFF</code>
</div>
</div>
</div>
<div class="code-block">
/* CSS Custom Properties */
:root {
--color-primary: #2F99A4;
--color-accent: #FF4D00;
--color-text-primary: #585961;
--color-border: #CCCDCC;
--color-background: #FFFFFF;
}
</div>
</div>
<!-- Grid Layouts -->
<div class="component-section">
<h2>Grid Layouts</h2>
<div class="component-demo">
<h3>Two-Column Layout</h3>
<div class="two-column" style="margin-bottom: 32px;">
<div style="background: #F5F5F5; padding: 24px; border-radius: 4px;">Column 1</div>
<div style="background: #F5F5F5; padding: 24px; border-radius: 4px;">Column 2</div>
</div>
<h3>Three-Column Layout</h3>
<div class="three-column">
<div style="background: #F5F5F5; padding: 24px; border-radius: 4px;">Column 1</div>
<div style="background: #F5F5F5; padding: 24px; border-radius: 4px;">Column 2</div>
<div style="background: #F5F5F5; padding: 24px; border-radius: 4px;">Column 3</div>
</div>
</div>
<div class="code-block">
&lt;!-- Two-column layout --&gt;
&lt;div class="two-column"&gt;
&lt;div&gt;Content 1&lt;/div&gt;
&lt;div&gt;Content 2&lt;/div&gt;
&lt;/div&gt;
&lt;!-- Three-column layout --&gt;
&lt;div class="three-column"&gt;
&lt;div&gt;Content 1&lt;/div&gt;
&lt;div&gt;Content 2&lt;/div&gt;
&lt;div&gt;Content 3&lt;/div&gt;
&lt;/div&gt;
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,347 @@
# Netresearch Brand Colors Reference
Complete color palette with technical specifications and usage guidelines.
## Primary Brand Colors
### Turquoise (Primary)
**Technical Specifications:**
- **Hex:** `#2F99A4`
- **RGB:** `rgb(47, 153, 164)`
- **CMYK:** `C71 M7 Y17 K36`
- **Pantone:** 7709 C (approximate)
- **NCS:** S 3040-B30G
**Usage:**
- Primary brand color for all digital and print materials
- Main CTA buttons and interactive elements
- Headers and key visual elements
- Links in default state
- Primary navigation highlights
- Brand identity elements
**Contrast Ratios (WCAG AA):**
- On white background: 3.8:1 (Meets AA for large text)
- On #F5F5F5: 4.2:1 (Meets AA for large text)
- White text on turquoise: 4.5:1 (Meets AA for all text)
**Web Implementation:**
```css
:root {
--color-primary: #2F99A4;
--color-primary-rgb: 47, 153, 164;
}
.btn-primary {
background-color: var(--color-primary);
color: #FFFFFF;
}
.link-primary {
color: var(--color-primary);
}
```
---
### Orange (Accent)
**Technical Specifications:**
- **Hex:** `#FF4D00`
- **RGB:** `rgb(255, 77, 0)`
- **CMYK:** `C0 M70 Y100 K0`
- **Pantone:** 172 C (approximate)
- **NCS:** S 1080-Y60R
**Usage:**
- Accent color for emphasis and highlights
- Secondary CTA buttons
- Important notifications and alerts
- Hover states on primary elements
- Call-out boxes and highlights
- Limited use - accent only, not dominant
**Contrast Ratios (WCAG AA):**
- On white background: 3.9:1 (Meets AA for large text)
- On #F5F5F5: 4.3:1 (Meets AA for large text)
- White text on orange: 3.2:1 (Meets AA for large text only)
**Web Implementation:**
```css
:root {
--color-accent: #FF4D00;
--color-accent-rgb: 255, 77, 0;
}
.btn-secondary {
background-color: var(--color-accent);
color: #FFFFFF;
}
.highlight {
border-left: 4px solid var(--color-accent);
}
```
---
## Neutral Colors
### Anthracite (Text Primary)
**Technical Specifications:**
- **Hex:** `#585961`
- **RGB:** `rgb(88, 89, 97)`
- **CMYK:** C9 M2 Y0 K62`
- **NCS:** S 5502-B
**Usage:**
- Primary text color for body copy
- Headlines and subheadings
- Navigation text
- Form labels
- Default icon color
**Contrast Ratios (WCAG AA):**
- On white background: 8.3:1 (Meets AAA for all text)
- On #F5F5F5: 7.8:1 (Meets AAA for all text)
**Web Implementation:**
```css
:root {
--color-text-primary: #585961;
}
body, p, h1, h2, h3 {
color: var(--color-text-primary);
}
```
---
### Light Grey (Backgrounds)
**Technical Specifications:**
- **Hex:** `#CCCDCC`
- **RGB:** `rgb(204, 205, 204)`
- **CMYK:** C0 M0 Y0 K20`
- **NCS:** S 2000-N
**Usage:**
- Background sections and cards
- Subtle borders and dividers
- Disabled states
- Alternating table rows
- Input field borders
**Contrast Note:**
- Not suitable for text on white background
- Use for backgrounds and UI elements only
**Web Implementation:**
```css
:root {
--color-background-light: #CCCDCC;
--color-border-light: #CCCDCC;
}
.card {
background-color: #F5F5F5;
border: 1px solid var(--color-border-light);
}
```
---
### White (Background)
**Technical Specifications:**
- **Hex:** `#FFFFFF`
- **RGB:** `rgb(255, 255, 255)`
**Usage:**
- Primary background color
- Text on dark backgrounds
- Card backgrounds
- Clean, spacious layouts
---
## Color Combinations
### Approved Combinations
**Primary Palette:**
```css
/* Turquoise on white - Primary brand */
.combination-1 {
background: #FFFFFF;
color: #2F99A4;
}
/* White text on turquoise - High impact */
.combination-2 {
background: #2F99A4;
color: #FFFFFF;
}
/* Orange accent with turquoise */
.combination-3 {
background: #2F99A4;
border-color: #FF4D00;
color: #FFFFFF;
}
```
**Text Combinations:**
```css
/* Body text on white - Standard */
.text-standard {
background: #FFFFFF;
color: #585961;
}
/* Body text on light grey - Sections */
.text-section {
background: #F5F5F5;
color: #585961;
}
```
### Avoid These Combinations
**Never Use:**
- Orange on turquoise (poor contrast)
- Light grey text on white (fails WCAG)
- Turquoise text smaller than 18px on white (AA compliance)
- Orange text on white for small text (fails AA)
---
## Accessibility Guidelines
### WCAG AA Compliance
**Minimum Contrast Ratios:**
- **Normal text (< 18px):** 4.5:1
- **Large text (≥ 18px):** 3:1
- **UI components:** 3:1
**Approved Text Combinations:**
**Pass AA for All Text Sizes:**
- Anthracite (#585961) on white: 8.3:1 ✓
- Anthracite (#585961) on #F5F5F5: 7.8:1 ✓
- White on turquoise (#2F99A4): 4.5:1 ✓
**Pass AA for Large Text Only:**
- Turquoise (#2F99A4) on white: 3.8:1 ✓
- Orange (#FF4D00) on white: 3.9:1 ✓
- White on orange (#FF4D00): 3.2:1 ✓
**Fail AA (Do Not Use):**
- Light grey (#CCCDCC) on white: 1.6:1 ✗
---
## Usage Guidelines by Context
### Websites
- **Primary color:** Turquoise for headers, links, CTAs
- **Accent color:** Orange sparingly for emphasis
- **Background:** White (#FFFFFF) or subtle off-white (#F5F5F5)
- **Text:** Anthracite (#585961)
- **High white space:** Let colors breathe
### Social Media
- **Header images:** Turquoise primary with orange accents
- **Profile elements:** Turquoise logo on white
- **Post graphics:** Maximum 2 brand colors per graphic
### Documents
- **Headers:** Turquoise or anthracite
- **Highlights:** Orange for key information
- **Body text:** Anthracite
- **Backgrounds:** White primary, light grey for emphasis
### Presentations
- **Title slides:** Turquoise background with white text
- **Content slides:** White background with turquoise accents
- **Emphasis:** Orange for key points
---
## CSS Custom Properties (Complete Set)
```css
:root {
/* Primary Colors */
--color-primary: #2F99A4;
--color-primary-rgb: 47, 153, 164;
--color-primary-light: rgba(47, 153, 164, 0.1);
--color-primary-dark: #257880;
/* Accent Colors */
--color-accent: #FF4D00;
--color-accent-rgb: 255, 77, 0;
--color-accent-light: rgba(255, 77, 0, 0.1);
--color-accent-dark: #CC3D00;
/* Neutral Colors */
--color-text-primary: #585961;
--color-text-secondary: #8A8B93;
--color-background: #FFFFFF;
--color-background-alt: #F5F5F5;
--color-border: #CCCDCC;
--color-border-light: #E5E5E5;
/* Semantic Colors */
--color-success: #28A745;
--color-warning: #FFC107;
--color-error: #DC3545;
--color-info: var(--color-primary);
}
```
---
## Sass/SCSS Variables
```scss
// Primary Colors
$color-primary: #2F99A4;
$color-accent: #FF4D00;
// Neutral Colors
$color-anthracite: #585961;
$color-light-grey: #CCCDCC;
$color-white: #FFFFFF;
// Backgrounds
$color-bg-primary: $color-white;
$color-bg-secondary: #F5F5F5;
// Text
$color-text-primary: $color-anthracite;
$color-text-secondary: lighten($color-anthracite, 15%);
// Borders
$color-border: $color-light-grey;
$color-border-light: #E5E5E5;
```
---
## Color Testing Checklist
Before deploying any brand materials:
- [ ] All text meets WCAG AA contrast requirements
- [ ] Turquoise is primary, orange is accent only
- [ ] No unapproved color combinations used
- [ ] Colors match exact hex values
- [ ] High white space maintained
- [ ] Tested on multiple displays for color accuracy
- [ ] Print materials use CMYK values
- [ ] Digital materials use RGB/Hex values
---
*Last updated: 2025-10-18*
*Maintained by: Netresearch DTT GmbH*

View File

@@ -0,0 +1,609 @@
# Netresearch Typography Reference
Complete typography system with font specifications, hierarchy, and implementation guidelines.
## Font Family Overview
| Context | Typeface | Purpose |
|---------|----------|---------|
| Web Headlines | **Raleway** | Digital headers, navigation, buttons |
| Web Body Text | **Open Sans** | Digital body copy, captions, lists |
| Documents | **Calibri** | Word, PowerPoint, Excel, email |
| Print | **Raleway + Open Sans** | Brochures, flyers, business cards |
---
## Raleway (Headlines & Display)
### Font Specifications
**Family:** Raleway
**Designer:** Matt McInerney, Pablo Impallari, Rodrigo Fuenzalida
**License:** Open Font License (free for commercial use)
**Classification:** Sans-serif, Geometric
**Recommended Weights for Netresearch:**
- **Regular (400)** - Secondary headlines, navigation
- **Semi-Bold (600)** - Buttons, emphasized text
- **Bold (700)** - Primary headlines, h1-h2
### Web Font Loading
**Google Fonts CDN:**
```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@400;600;700&display=swap" rel="stylesheet">
```
**CSS @import:**
```css
@import url('https://fonts.googleapis.com/css2?family=Raleway:wght@400;600;700&display=swap');
```
**Self-Hosted (Recommended for Performance):**
```css
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/raleway-regular.woff2') format('woff2'),
url('/fonts/raleway-regular.woff') format('woff');
}
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('/fonts/raleway-semibold.woff2') format('woff2'),
url('/fonts/raleway-semibold.woff') format('woff');
}
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('/fonts/raleway-bold.woff2') format('woff2'),
url('/fonts/raleway-bold.woff') format('woff');
}
```
### Usage Guidelines
**Use Raleway For:**
- ✅ Page headlines (h1, h2, h3)
- ✅ Navigation menu items
- ✅ Button labels
- ✅ Section headers
- ✅ Pull quotes and callouts
- ✅ Hero text and taglines
**Do Not Use Raleway For:**
- ❌ Body paragraphs (use Open Sans)
- ❌ Long-form content (readability issues)
- ❌ Small text below 14px (legibility)
- ❌ Dense information tables
---
## Open Sans (Body Text)
### Font Specifications
**Family:** Open Sans
**Designer:** Steve Matteson
**License:** Apache License 2.0 (free for commercial use)
**Classification:** Sans-serif, Humanist
**Recommended Weights for Netresearch:**
- **Regular (400)** - Body text, lists, captions
- **Semi-Bold (600)** - Emphasized body text, labels
- **Bold (700)** - Strong emphasis, sub-headers
### Web Font Loading
**Google Fonts CDN:**
```html
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap" rel="stylesheet">
```
**CSS @import:**
```css
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap');
```
**Self-Hosted:**
```css
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/opensans-regular.woff2') format('woff2'),
url('/fonts/opensans-regular.woff') format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('/fonts/opensans-semibold.woff2') format('woff2'),
url('/fonts/opensans-semibold.woff') format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('/fonts/opensans-bold.woff2') format('woff2'),
url('/fonts/opensans-bold.woff') format('woff');
}
```
### Usage Guidelines
**Use Open Sans For:**
- ✅ All body copy and paragraphs
- ✅ Lists (ordered and unordered)
- ✅ Form input text
- ✅ Captions and metadata
- ✅ Table content
- ✅ Footer text
**Do Not Use Open Sans For:**
- ❌ Main page headlines (use Raleway)
- ❌ Large display text above 48px (use Raleway)
---
## Calibri (Documents)
### Font Specifications
**Family:** Calibri
**Designer:** Luc(as) de Groot
**License:** Microsoft bundled (available on Windows/Office)
**Classification:** Sans-serif, Humanist
**Weights Available:**
- Regular (400)
- Bold (700)
- Italic (400)
- Bold Italic (700)
### Usage Guidelines
**Use Calibri For:**
- ✅ Microsoft Word documents
- ✅ PowerPoint presentations
- ✅ Excel spreadsheets
- ✅ Outlook emails
- ✅ Internal documentation
**Fallback for Non-Microsoft Platforms:**
```css
font-family: Calibri, 'Open Sans', Arial, sans-serif;
```
**Document Templates:**
- **Headings:** Calibri Bold, Turquoise (#2F99A4)
- **Body Text:** Calibri Regular, Anthracite (#585961)
- **Emphasis:** Calibri Bold or Calibri Italic
---
## Typography Scale
### Web Typography Hierarchy
```css
/* h1 - Page Title */
h1 {
font-family: 'Raleway', sans-serif;
font-size: 48px;
font-weight: 700;
line-height: 1.2;
color: #585961;
margin-bottom: 24px;
}
/* h2 - Section Header */
h2 {
font-family: 'Raleway', sans-serif;
font-size: 36px;
font-weight: 700;
line-height: 1.3;
color: #585961;
margin-bottom: 20px;
}
/* h3 - Subsection Header */
h3 {
font-family: 'Raleway', sans-serif;
font-size: 28px;
font-weight: 600;
line-height: 1.4;
color: #585961;
margin-bottom: 16px;
}
/* h4 - Minor Header */
h4 {
font-family: 'Raleway', sans-serif;
font-size: 22px;
font-weight: 600;
line-height: 1.4;
color: #585961;
margin-bottom: 12px;
}
/* h5 - Small Header */
h5 {
font-family: 'Raleway', sans-serif;
font-size: 18px;
font-weight: 600;
line-height: 1.5;
color: #585961;
margin-bottom: 12px;
}
/* Body Text */
body, p {
font-family: 'Open Sans', sans-serif;
font-size: 16px;
font-weight: 400;
line-height: 1.6;
color: #585961;
margin-bottom: 16px;
}
/* Small Text / Captions */
.text-small, small {
font-family: 'Open Sans', sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 1.6;
color: #585961;
}
/* Lead Paragraph */
.lead {
font-family: 'Open Sans', sans-serif;
font-size: 20px;
font-weight: 400;
line-height: 1.6;
color: #585961;
}
```
### Responsive Typography
```css
/* Mobile First Approach */
/* Base (Mobile) */
h1 { font-size: 32px; }
h2 { font-size: 26px; }
h3 { font-size: 22px; }
h4 { font-size: 18px; }
body, p { font-size: 16px; }
/* Tablet (768px+) */
@media (min-width: 768px) {
h1 { font-size: 40px; }
h2 { font-size: 32px; }
h3 { font-size: 26px; }
h4 { font-size: 20px; }
}
/* Desktop (1024px+) */
@media (min-width: 1024px) {
h1 { font-size: 48px; }
h2 { font-size: 36px; }
h3 { font-size: 28px; }
h4 { font-size: 22px; }
}
/* Large Desktop (1440px+) */
@media (min-width: 1440px) {
h1 { font-size: 56px; }
h2 { font-size: 40px; }
}
```
---
## Line Height & Spacing
### Line Height Guidelines
```css
/* Headlines - Tighter Leading */
h1, h2, h3 {
line-height: 1.2;
}
h4, h5, h6 {
line-height: 1.4;
}
/* Body Text - Comfortable Reading */
p, li, td {
line-height: 1.6;
}
/* Small Text */
small, .caption {
line-height: 1.5;
}
```
### Vertical Rhythm
```css
/* Consistent spacing system */
:root {
--spacing-xs: 8px;
--spacing-sm: 12px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-xxl: 48px;
}
/* Apply to typography */
h1 { margin-bottom: var(--spacing-lg); }
h2 { margin-bottom: var(--spacing-lg); }
h3 { margin-bottom: var(--spacing-md); }
h4, h5 { margin-bottom: var(--spacing-sm); }
p { margin-bottom: var(--spacing-md); }
```
---
## Special Typography Elements
### Buttons
```css
.btn {
font-family: 'Raleway', sans-serif;
font-size: 16px;
font-weight: 600;
line-height: 1;
letter-spacing: 0.5px;
text-transform: none;
}
.btn-large {
font-size: 18px;
}
.btn-small {
font-size: 14px;
}
```
### Navigation
```css
.nav-link {
font-family: 'Raleway', sans-serif;
font-size: 16px;
font-weight: 400;
letter-spacing: 0.3px;
}
.nav-link:hover,
.nav-link.active {
font-weight: 600;
}
```
### Blockquotes / Pull Quotes
```css
blockquote {
font-family: 'Raleway', sans-serif;
font-size: 24px;
font-weight: 400;
line-height: 1.5;
font-style: italic;
color: #2F99A4;
border-left: 4px solid #FF4D00;
padding-left: 24px;
margin: 32px 0;
}
blockquote cite {
font-family: 'Open Sans', sans-serif;
font-size: 14px;
font-weight: 600;
font-style: normal;
color: #585961;
display: block;
margin-top: 12px;
}
```
### Lists
```css
ul, ol {
font-family: 'Open Sans', sans-serif;
font-size: 16px;
line-height: 1.6;
margin-bottom: 16px;
padding-left: 24px;
}
li {
margin-bottom: 8px;
}
/* Custom bullet points using brand color */
ul.branded {
list-style: none;
padding-left: 0;
}
ul.branded li::before {
content: '●';
color: #2F99A4;
font-weight: bold;
display: inline-block;
width: 1em;
margin-left: -1em;
}
```
---
## Font Loading Strategy
### Performance Optimization
```html
<!-- Critical CSS with system font fallback -->
<style>
body {
font-family: -apple-system, BlinkMacSystemFont,
'Segoe UI', sans-serif;
}
</style>
<!-- Preload critical fonts -->
<link rel="preload"
href="/fonts/raleway-bold.woff2"
as="font"
type="font/woff2"
crossorigin>
<link rel="preload"
href="/fonts/opensans-regular.woff2"
as="font"
type="font/woff2"
crossorigin>
```
### Font Display Strategy
```css
@font-face {
font-family: 'Raleway';
font-display: swap; /* Show fallback immediately, swap when loaded */
/* ... other properties */
}
```
### Fallback Stack
```css
/* Complete fallback chain */
--font-headlines: 'Raleway', 'Helvetica Neue', Arial, sans-serif;
--font-body: 'Open Sans', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, sans-serif;
--font-documents: Calibri, 'Open Sans', Arial, sans-serif;
```
---
## Accessibility Considerations
### Minimum Font Sizes
- **Body text:** Never below 16px (1rem)
- **Small text:** Never below 14px (0.875rem)
- **Buttons:** Minimum 16px for touch targets
### Contrast Requirements
All text must meet WCAG AA:
- **Normal text:** 4.5:1 contrast ratio
- **Large text (≥18px):** 3:1 contrast ratio
### Readability
**Line Length:**
- Optimal: 50-75 characters per line
- Maximum: 90 characters per line
```css
.content {
max-width: 75ch; /* Character-based width */
}
```
**Paragraph Spacing:**
```css
p + p {
margin-top: 16px; /* Clear paragraph separation */
}
```
---
## CSS Custom Properties (Complete Set)
```css
:root {
/* Font Families */
--font-headline: 'Raleway', sans-serif;
--font-body: 'Open Sans', sans-serif;
--font-document: Calibri, 'Open Sans', sans-serif;
/* Font Sizes */
--font-size-xs: 12px;
--font-size-sm: 14px;
--font-size-base: 16px;
--font-size-lg: 18px;
--font-size-xl: 20px;
--font-size-2xl: 24px;
--font-size-3xl: 28px;
--font-size-4xl: 36px;
--font-size-5xl: 48px;
--font-size-6xl: 56px;
/* Font Weights */
--font-weight-normal: 400;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* Line Heights */
--line-height-tight: 1.2;
--line-height-snug: 1.4;
--line-height-normal: 1.6;
--line-height-relaxed: 1.8;
/* Letter Spacing */
--letter-spacing-tight: -0.5px;
--letter-spacing-normal: 0;
--letter-spacing-wide: 0.5px;
}
```
---
## Typography Testing Checklist
Before deploying:
- [ ] Fonts load correctly on all major browsers
- [ ] Fallback fonts provide acceptable experience
- [ ] All text meets WCAG AA contrast requirements
- [ ] Font files optimized (woff2 format preferred)
- [ ] Font-display: swap implemented
- [ ] Line lengths within 50-90 character range
- [ ] Responsive typography scales appropriately
- [ ] No text smaller than 14px in production
- [ ] Headlines use Raleway, body uses Open Sans
- [ ] Documents use Calibri with proper fallbacks
---
*Last updated: 2025-10-18*
*Maintained by: Netresearch DTT GmbH*

View File

@@ -0,0 +1,860 @@
# Netresearch Web Design Guidelines
Comprehensive web design patterns, layouts, components, and responsive design standards.
## Core Design Principles
### 1. High White Space
**Philosophy:** Clean, uncluttered layouts that prioritize content and readability.
```css
/* Generous spacing throughout */
:root {
--spacing-xs: 8px;
--spacing-sm: 12px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-xxl: 48px;
--spacing-3xl: 64px;
--spacing-4xl: 96px;
}
/* Section spacing */
section {
padding: var(--spacing-4xl) 0;
}
/* Content spacing */
.content {
padding: var(--spacing-xxl);
}
```
**Guidelines:**
- Minimum 48px padding around major sections
- Minimum 24px margin between content blocks
- Generous line-height (1.6) for readability
- Never crowd elements - let designs breathe
---
### 2. Responsive Grid System
**12-Column Flexible Grid:**
```css
.grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: var(--spacing-lg);
padding: 0 var(--spacing-lg);
}
/* Content within container */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-lg);
}
/* Responsive columns */
.col-12 { grid-column: span 12; }
.col-6 { grid-column: span 6; }
.col-4 { grid-column: span 4; }
.col-3 { grid-column: span 3; }
@media (max-width: 768px) {
.col-6, .col-4, .col-3 {
grid-column: span 12;
}
}
```
---
### 3. Design Hierarchy
- **Primary:** Turquoise (#2F99A4) for key elements
- **Secondary:** Orange (#FF4D00) for accents only
- **Typography:** Clear hierarchy with Raleway headers and Open Sans body
- **Visual Flow:** Top to bottom, left to right reading patterns
---
## Responsive Breakpoints
### Standard Breakpoints
```css
/* Mobile First Approach */
/* Extra Small (Mobile Portrait) */
/* Default: 0px - 599px */
/* Small (Mobile Landscape) */
@media (min-width: 600px) {
/* Adjustments for landscape phones */
}
/* Medium (Tablet) */
@media (min-width: 768px) {
/* Two-column layouts start here */
}
/* Large (Desktop) */
@media (min-width: 1024px) {
/* Full desktop layouts */
}
/* Extra Large (Wide Desktop) */
@media (min-width: 1440px) {
/* Maximum content width, larger typography */
}
/* Ultra Wide */
@media (min-width: 1920px) {
/* Optional: Enhanced spacing for ultra-wide displays */
}
```
### Container Widths
```css
.container {
width: 100%;
margin: 0 auto;
padding: 0 16px;
}
@media (min-width: 600px) {
.container { max-width: 580px; }
}
@media (min-width: 768px) {
.container { max-width: 750px; }
}
@media (min-width: 1024px) {
.container { max-width: 1000px; }
}
@media (min-width: 1440px) {
.container { max-width: 1200px; }
}
```
---
## Component Library
### Buttons
```css
/* Primary Button */
.btn-primary {
display: inline-block;
font-family: 'Raleway', sans-serif;
font-size: 16px;
font-weight: 600;
line-height: 1;
text-align: center;
text-decoration: none;
padding: 12px 32px;
border: none;
border-radius: 4px;
background-color: #2F99A4;
color: #FFFFFF;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary:hover {
background-color: #257880;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(47, 153, 164, 0.3);
}
.btn-primary:active {
transform: translateY(0);
}
/* Secondary Button */
.btn-secondary {
background-color: #FF4D00;
color: #FFFFFF;
}
.btn-secondary:hover {
background-color: #CC3D00;
box-shadow: 0 4px 12px rgba(255, 77, 0, 0.3);
}
/* Outline Button */
.btn-outline {
background-color: transparent;
color: #2F99A4;
border: 2px solid #2F99A4;
}
.btn-outline:hover {
background-color: #2F99A4;
color: #FFFFFF;
}
/* Button Sizes */
.btn-small {
padding: 8px 24px;
font-size: 14px;
}
.btn-large {
padding: 16px 48px;
font-size: 18px;
}
/* Button States */
.btn:disabled,
.btn[disabled] {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
```
**Button Usage:**
```html
<!-- Primary action -->
<button class="btn-primary">Get Started</button>
<!-- Secondary action -->
<button class="btn-secondary">Learn More</button>
<!-- Tertiary action -->
<button class="btn-outline">Contact Us</button>
<!-- Sizes -->
<button class="btn-primary btn-small">Small</button>
<button class="btn-primary">Default</button>
<button class="btn-primary btn-large">Large</button>
```
---
### Cards
```css
.card {
background: #FFFFFF;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
overflow: hidden;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
transform: translateY(-4px);
}
.card-image {
width: 100%;
height: 240px;
object-fit: cover;
}
.card-content {
padding: var(--spacing-lg);
}
.card-title {
font-family: 'Raleway', sans-serif;
font-size: 22px;
font-weight: 600;
color: #585961;
margin-bottom: var(--spacing-sm);
}
.card-text {
font-family: 'Open Sans', sans-serif;
font-size: 16px;
line-height: 1.6;
color: #585961;
margin-bottom: var(--spacing-md);
}
.card-footer {
padding: var(--spacing-md) var(--spacing-lg);
border-top: 1px solid #E5E5E5;
background-color: #F5F5F5;
}
```
**Card Usage:**
```html
<div class="card">
<img src="image.jpg" alt="Card image" class="card-image">
<div class="card-content">
<h3 class="card-title">Card Title</h3>
<p class="card-text">Card description goes here.</p>
<a href="#" class="btn-primary">Read More</a>
</div>
<div class="card-footer">
<span class="card-meta">Posted: January 1, 2025</span>
</div>
</div>
```
---
### Navigation
```css
/* Main Navigation */
.navbar {
position: sticky;
top: 0;
z-index: 1000;
background: #FFFFFF;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
padding: var(--spacing-md) 0;
}
.navbar-container {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-lg);
}
.navbar-logo {
height: 48px;
}
.navbar-menu {
display: flex;
gap: var(--spacing-lg);
list-style: none;
margin: 0;
padding: 0;
}
.navbar-link {
font-family: 'Raleway', sans-serif;
font-size: 16px;
font-weight: 400;
color: #585961;
text-decoration: none;
padding: 8px 16px;
transition: all 0.3s ease;
}
.navbar-link:hover,
.navbar-link.active {
color: #2F99A4;
font-weight: 600;
}
/* Mobile Menu Toggle */
.navbar-toggle {
display: none;
flex-direction: column;
gap: 4px;
background: none;
border: none;
cursor: pointer;
}
.navbar-toggle span {
display: block;
width: 24px;
height: 3px;
background: #2F99A4;
transition: all 0.3s ease;
}
@media (max-width: 768px) {
.navbar-toggle {
display: flex;
}
.navbar-menu {
position: absolute;
top: 100%;
left: 0;
right: 0;
flex-direction: column;
background: #FFFFFF;
padding: var(--spacing-lg);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
transform: translateY(-100%);
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.navbar-menu.active {
transform: translateY(0);
opacity: 1;
visibility: visible;
}
}
```
---
### Forms
```css
.form-group {
margin-bottom: var(--spacing-lg);
}
.form-label {
display: block;
font-family: 'Open Sans', sans-serif;
font-size: 14px;
font-weight: 600;
color: #585961;
margin-bottom: var(--spacing-xs);
}
.form-input,
.form-textarea,
.form-select {
width: 100%;
font-family: 'Open Sans', sans-serif;
font-size: 16px;
color: #585961;
padding: 12px 16px;
border: 1px solid #CCCDCC;
border-radius: 4px;
background: #FFFFFF;
transition: all 0.3s ease;
}
.form-input:focus,
.form-textarea:focus,
.form-select:focus {
outline: none;
border-color: #2F99A4;
box-shadow: 0 0 0 3px rgba(47, 153, 164, 0.1);
}
.form-input::placeholder {
color: #8A8B93;
}
.form-textarea {
min-height: 120px;
resize: vertical;
}
.form-error {
color: #DC3545;
font-size: 14px;
margin-top: var(--spacing-xs);
}
.form-input.error {
border-color: #DC3545;
}
.form-help {
font-size: 14px;
color: #8A8B93;
margin-top: var(--spacing-xs);
}
```
**Form Example:**
```html
<form class="form">
<div class="form-group">
<label for="name" class="form-label">Name*</label>
<input type="text" id="name" class="form-input" placeholder="Your name" required>
</div>
<div class="form-group">
<label for="email" class="form-label">Email*</label>
<input type="email" id="email" class="form-input" placeholder="your@email.com" required>
<small class="form-help">We'll never share your email.</small>
</div>
<div class="form-group">
<label for="message" class="form-label">Message*</label>
<textarea id="message" class="form-textarea" placeholder="Your message" required></textarea>
</div>
<button type="submit" class="btn-primary">Send Message</button>
</form>
```
---
### Links
```css
/* Body Links */
a {
color: #2F99A4;
text-decoration: none;
transition: all 0.3s ease;
border-bottom: 1px solid transparent;
}
a:hover {
color: #257880;
border-bottom-color: #2F99A4;
}
/* External Links */
a[target="_blank"]::after {
content: ' ↗';
font-size: 0.9em;
}
/* Download Links */
a[download]::before {
content: '⬇ ';
}
/* Standalone Links */
.link-standalone {
font-family: 'Raleway', sans-serif;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 8px;
}
.link-standalone::after {
content: '→';
transition: transform 0.3s ease;
}
.link-standalone:hover::after {
transform: translateX(4px);
}
```
---
### Hero Sections
```css
.hero {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: 600px;
padding: var(--spacing-4xl) var(--spacing-lg);
background: linear-gradient(135deg, #2F99A4 0%, #257880 100%);
color: #FFFFFF;
text-align: center;
}
.hero-content {
max-width: 800px;
z-index: 1;
}
.hero-title {
font-family: 'Raleway', sans-serif;
font-size: 56px;
font-weight: 700;
margin-bottom: var(--spacing-lg);
}
.hero-subtitle {
font-family: 'Open Sans', sans-serif;
font-size: 20px;
line-height: 1.6;
margin-bottom: var(--spacing-xl);
opacity: 0.9;
}
.hero-cta {
display: flex;
gap: var(--spacing-md);
justify-content: center;
flex-wrap: wrap;
}
@media (max-width: 768px) {
.hero {
min-height: 400px;
padding: var(--spacing-xxl) var(--spacing-lg);
}
.hero-title {
font-size: 36px;
}
.hero-subtitle {
font-size: 18px;
}
.hero-cta {
flex-direction: column;
}
}
```
---
### Tables
```css
.table {
width: 100%;
border-collapse: collapse;
margin-bottom: var(--spacing-lg);
background: #FFFFFF;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.table thead {
background-color: #2F99A4;
color: #FFFFFF;
}
.table th {
font-family: 'Raleway', sans-serif;
font-weight: 600;
text-align: left;
padding: 16px;
}
.table td {
font-family: 'Open Sans', sans-serif;
padding: 12px 16px;
border-bottom: 1px solid #E5E5E5;
}
.table tbody tr:hover {
background-color: #F5F5F5;
}
.table tbody tr:last-child td {
border-bottom: none;
}
/* Responsive Table */
@media (max-width: 768px) {
.table {
display: block;
overflow-x: auto;
}
}
```
---
### Footer
```css
.footer {
background-color: #585961;
color: #FFFFFF;
padding: var(--spacing-4xl) 0 var(--spacing-lg);
}
.footer-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: var(--spacing-xl);
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-lg);
margin-bottom: var(--spacing-xl);
}
.footer-section h4 {
font-family: 'Raleway', sans-serif;
font-size: 18px;
font-weight: 600;
color: #FFFFFF;
margin-bottom: var(--spacing-md);
}
.footer-section ul {
list-style: none;
padding: 0;
margin: 0;
}
.footer-section li {
margin-bottom: var(--spacing-sm);
}
.footer-section a {
color: rgba(255, 255, 255, 0.8);
text-decoration: none;
transition: color 0.3s ease;
}
.footer-section a:hover {
color: #2F99A4;
}
.footer-bottom {
border-top: 1px solid rgba(255, 255, 255, 0.1);
padding-top: var(--spacing-lg);
text-align: center;
color: rgba(255, 255, 255, 0.6);
font-size: 14px;
}
```
---
## Layout Patterns
### Two-Column Layout
```css
.two-column {
display: grid;
grid-template-columns: 2fr 1fr;
gap: var(--spacing-xl);
}
@media (max-width: 768px) {
.two-column {
grid-template-columns: 1fr;
}
}
```
### Three-Column Layout
```css
.three-column {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-lg);
}
@media (max-width: 1024px) {
.three-column {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.three-column {
grid-template-columns: 1fr;
}
}
```
### Content + Sidebar
```css
.content-sidebar {
display: grid;
grid-template-columns: 3fr 1fr;
gap: var(--spacing-xxl);
}
@media (max-width: 1024px) {
.content-sidebar {
grid-template-columns: 1fr;
}
}
```
---
## Animations & Transitions
```css
/* Smooth transitions */
* {
transition-timing-function: ease-in-out;
}
/* Fade In */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.6s ease-out;
}
/* Slide In */
@keyframes slideIn {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
/* Pulse (for emphasis) */
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
```
---
## Web Design Checklist
Before launching any web project:
**Layout:**
- [ ] High white space maintained throughout
- [ ] Responsive grid implemented correctly
- [ ] All breakpoints tested (mobile, tablet, desktop)
- [ ] Content max-width appropriate (≤1200px)
**Components:**
- [ ] Buttons follow brand styles (Raleway font, correct colors)
- [ ] Cards have appropriate shadows and hover states
- [ ] Navigation sticky/fixed and accessible
- [ ] Forms styled consistently with validation
**Typography:**
- [ ] Headlines use Raleway
- [ ] Body text uses Open Sans
- [ ] Font sizes scale responsively
- [ ] Line-heights appropriate for readability
**Colors:**
- [ ] Turquoise primary, orange accent only
- [ ] WCAG AA contrast compliance verified
- [ ] Brand colors match exactly (no variations)
**Performance:**
- [ ] Images optimized and responsive
- [ ] Fonts loaded efficiently (preload critical)
- [ ] CSS minified for production
- [ ] Animations use transform/opacity (GPU-accelerated)
**Accessibility:**
- [ ] All interactive elements keyboard accessible
- [ ] ARIA labels where needed
- [ ] Focus states visible
- [ ] Screen reader tested
---
*Last updated: 2025-10-18*
*Maintained by: Netresearch DTT GmbH*

View File

@@ -0,0 +1,285 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Netresearch - Landing Page Template</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@400;600;700&family=Open+Sans:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- Navigation -->
<nav class="navbar">
<div class="navbar-container">
<img src="logo.svg" alt="Netresearch Logo" class="navbar-logo">
<button class="navbar-toggle" aria-label="Toggle navigation">
<span></span>
<span></span>
<span></span>
</button>
<ul class="navbar-menu">
<li><a href="#services" class="navbar-link active">Services</a></li>
<li><a href="#about" class="navbar-link">About</a></li>
<li><a href="#portfolio" class="navbar-link">Portfolio</a></li>
<li><a href="#contact" class="navbar-link">Contact</a></li>
</ul>
</div>
</nav>
<!-- Hero Section -->
<section class="hero">
<div class="hero-content">
<h1 class="hero-title">Your Digital Transformation Partner</h1>
<p class="hero-subtitle">
We create innovative digital solutions that drive business growth and enhance user experiences.
</p>
<div class="hero-cta">
<a href="#contact" class="btn-primary btn-large">Get Started</a>
<a href="#services" class="btn-outline btn-large">Learn More</a>
</div>
</div>
</section>
<!-- Services Section -->
<section id="services" class="section">
<div class="container">
<h2 class="section-title">Our Services</h2>
<p class="section-subtitle">
Comprehensive digital solutions tailored to your needs
</p>
<div class="three-column">
<div class="card">
<div class="card-content">
<h3 class="card-title">Web Development</h3>
<p class="card-text">
Modern, responsive websites built with the latest technologies and best practices.
</p>
<a href="#" class="link-standalone">Learn more</a>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="card-title">E-Commerce Solutions</h3>
<p class="card-text">
Powerful online stores that drive sales and provide exceptional shopping experiences.
</p>
<a href="#" class="link-standalone">Learn more</a>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="card-title">Digital Strategy</h3>
<p class="card-text">
Strategic consulting to help your business thrive in the digital landscape.
</p>
<a href="#" class="link-standalone">Learn more</a>
</div>
</div>
</div>
</div>
</section>
<!-- About Section -->
<section id="about" class="section section-alt">
<div class="container">
<div class="two-column">
<div>
<h2>About Netresearch</h2>
<p class="lead">
With over 20 years of experience, we've been helping businesses succeed online through innovative digital solutions.
</p>
<p>
Our team of experts combines technical excellence with creative thinking to deliver projects that exceed expectations. We specialize in TYPO3, e-commerce, and custom web applications.
</p>
<p>
Based in Leipzig, Germany, we serve clients across Europe and beyond, providing end-to-end digital services from strategy to implementation and support.
</p>
<a href="#contact" class="btn-primary">Work with us</a>
</div>
<div>
<img src="about-image.jpg" alt="Netresearch team" style="width: 100%; border-radius: 8px;">
</div>
</div>
</div>
</section>
<!-- Portfolio Section -->
<section id="portfolio" class="section">
<div class="container">
<h2 class="section-title">Our Work</h2>
<p class="section-subtitle">
Recent projects we're proud of
</p>
<div class="three-column">
<div class="card">
<img src="project-1.jpg" alt="Project 1" class="card-image">
<div class="card-content">
<h3 class="card-title">E-Commerce Platform</h3>
<p class="card-text">
Complete redesign and development of a multi-vendor marketplace.
</p>
<div class="card-footer">
<span class="card-meta">TYPO3 • Magento • 2024</span>
</div>
</div>
</div>
<div class="card">
<img src="project-2.jpg" alt="Project 2" class="card-image">
<div class="card-content">
<h3 class="card-title">Corporate Website</h3>
<p class="card-text">
Modern website with multilingual content and advanced integrations.
</p>
<div class="card-footer">
<span class="card-meta">TYPO3 • API • 2024</span>
</div>
</div>
</div>
<div class="card">
<img src="project-3.jpg" alt="Project 3" class="card-image">
<div class="card-content">
<h3 class="card-title">Custom Web Application</h3>
<p class="card-text">
Bespoke solution for complex business requirements and workflows.
</p>
<div class="card-footer">
<span class="card-meta">Laravel • Vue.js • 2024</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Contact Section -->
<section id="contact" class="section section-alt">
<div class="container">
<div class="two-column">
<div>
<h2>Get in Touch</h2>
<p class="lead">
Ready to start your next project? Let's talk about how we can help.
</p>
<p>
<strong>Netresearch DTT GmbH</strong><br>
Nonnenstraße 11d<br>
04229 Leipzig, Germany
</p>
<p>
<strong>Phone:</strong> +49 341 49288-0<br>
<strong>Email:</strong> <a href="mailto:info@netresearch.de">info@netresearch.de</a>
</p>
</div>
<div>
<form class="form">
<div class="form-group">
<label for="name" class="form-label">Name*</label>
<input type="text" id="name" class="form-input" placeholder="Your name" required>
</div>
<div class="form-group">
<label for="email" class="form-label">Email*</label>
<input type="email" id="email" class="form-input" placeholder="your@email.com" required>
</div>
<div class="form-group">
<label for="subject" class="form-label">Subject*</label>
<input type="text" id="subject" class="form-input" placeholder="Project inquiry" required>
</div>
<div class="form-group">
<label for="message" class="form-label">Message*</label>
<textarea id="message" class="form-textarea" placeholder="Tell us about your project" required></textarea>
</div>
<button type="submit" class="btn-primary btn-large">Send Message</button>
</form>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-content">
<div class="footer-section">
<h4>Company</h4>
<ul>
<li><a href="#about">About Us</a></li>
<li><a href="#services">Services</a></li>
<li><a href="#portfolio">Portfolio</a></li>
<li><a href="#careers">Careers</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Services</h4>
<ul>
<li><a href="#web-dev">Web Development</a></li>
<li><a href="#ecommerce">E-Commerce</a></li>
<li><a href="#typo3">TYPO3</a></li>
<li><a href="#consulting">Consulting</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="#blog">Blog</a></li>
<li><a href="#case-studies">Case Studies</a></li>
<li><a href="#documentation">Documentation</a></li>
<li><a href="#support">Support</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Legal</h4>
<ul>
<li><a href="#imprint">Imprint</a></li>
<li><a href="#privacy">Privacy Policy</a></li>
<li><a href="#terms">Terms of Service</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<div class="container">
<p>&copy; 2025 Netresearch DTT GmbH. All rights reserved.</p>
</div>
</div>
</footer>
<script>
// Mobile menu toggle
const toggle = document.querySelector('.navbar-toggle');
const menu = document.querySelector('.navbar-menu');
toggle.addEventListener('click', () => {
menu.classList.toggle('active');
});
// Smooth scrolling
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({ behavior: 'smooth' });
menu.classList.remove('active');
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,612 @@
/*
* Netresearch Brand Styles
* Complete CSS template following brand guidelines
*/
/* ============================================
CSS CUSTOM PROPERTIES
============================================ */
:root {
/* Brand Colors */
--color-primary: #2F99A4;
--color-primary-rgb: 47, 153, 164;
--color-primary-dark: #257880;
--color-accent: #FF4D00;
--color-accent-rgb: 255, 77, 0;
--color-accent-dark: #CC3D00;
/* Neutral Colors */
--color-text-primary: #585961;
--color-text-secondary: #8A8B93;
--color-background: #FFFFFF;
--color-background-alt: #F5F5F5;
--color-border: #CCCDCC;
--color-border-light: #E5E5E5;
/* Typography */
--font-headline: 'Raleway', sans-serif;
--font-body: 'Open Sans', sans-serif;
/* Font Sizes */
--font-size-xs: 12px;
--font-size-sm: 14px;
--font-size-base: 16px;
--font-size-lg: 18px;
--font-size-xl: 20px;
--font-size-2xl: 24px;
--font-size-3xl: 28px;
--font-size-4xl: 36px;
--font-size-5xl: 48px;
--font-size-6xl: 56px;
/* Spacing */
--spacing-xs: 8px;
--spacing-sm: 12px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-xxl: 48px;
--spacing-3xl: 64px;
--spacing-4xl: 96px;
/* Shadows */
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.12);
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.16);
}
/* ============================================
RESET & BASE STYLES
============================================ */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-body);
font-size: var(--font-size-base);
line-height: 1.6;
color: var(--color-text-primary);
background-color: var(--color-background);
}
/* ============================================
TYPOGRAPHY
============================================ */
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-headline);
font-weight: 700;
line-height: 1.2;
color: var(--color-text-primary);
}
h1 {
font-size: var(--font-size-5xl);
margin-bottom: var(--spacing-lg);
}
h2 {
font-size: var(--font-size-4xl);
margin-bottom: var(--spacing-lg);
}
h3 {
font-size: var(--font-size-3xl);
font-weight: 600;
margin-bottom: var(--spacing-md);
}
h4 {
font-size: var(--font-size-2xl);
font-weight: 600;
margin-bottom: var(--spacing-sm);
}
p {
margin-bottom: var(--spacing-md);
}
.lead {
font-size: var(--font-size-xl);
font-weight: 400;
line-height: 1.6;
}
a {
color: var(--color-primary);
text-decoration: none;
transition: all 0.3s ease;
}
a:hover {
color: var(--color-primary-dark);
}
/* ============================================
LAYOUT
============================================ */
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-lg);
}
.section {
padding: var(--spacing-4xl) 0;
}
.section-alt {
background-color: var(--color-background-alt);
}
.section-title {
text-align: center;
margin-bottom: var(--spacing-md);
}
.section-subtitle {
text-align: center;
font-size: var(--font-size-xl);
color: var(--color-text-secondary);
margin-bottom: var(--spacing-xxl);
}
.two-column {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-xl);
align-items: center;
}
.three-column {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-lg);
}
/* ============================================
NAVIGATION
============================================ */
.navbar {
position: sticky;
top: 0;
z-index: 1000;
background: var(--color-background);
box-shadow: var(--shadow-sm);
padding: var(--spacing-md) 0;
}
.navbar-container {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-lg);
}
.navbar-logo {
height: 48px;
}
.navbar-menu {
display: flex;
gap: var(--spacing-lg);
list-style: none;
}
.navbar-link {
font-family: var(--font-headline);
font-size: var(--font-size-base);
font-weight: 400;
color: var(--color-text-primary);
padding: var(--spacing-xs) var(--spacing-md);
transition: all 0.3s ease;
}
.navbar-link:hover,
.navbar-link.active {
color: var(--color-primary);
font-weight: 600;
}
.navbar-toggle {
display: none;
flex-direction: column;
gap: 4px;
background: none;
border: none;
cursor: pointer;
}
.navbar-toggle span {
display: block;
width: 24px;
height: 3px;
background: var(--color-primary);
transition: all 0.3s ease;
}
/* ============================================
HERO SECTION
============================================ */
.hero {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: 600px;
padding: var(--spacing-4xl) var(--spacing-lg);
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
color: var(--color-background);
text-align: center;
}
.hero-content {
max-width: 800px;
z-index: 1;
}
.hero-title {
color: var(--color-background);
margin-bottom: var(--spacing-lg);
}
.hero-subtitle {
font-size: var(--font-size-xl);
line-height: 1.6;
margin-bottom: var(--spacing-xl);
opacity: 0.95;
}
.hero-cta {
display: flex;
gap: var(--spacing-md);
justify-content: center;
flex-wrap: wrap;
}
/* ============================================
BUTTONS
============================================ */
.btn-primary,
.btn-secondary,
.btn-outline {
display: inline-block;
font-family: var(--font-headline);
font-size: var(--font-size-base);
font-weight: 600;
line-height: 1;
text-align: center;
text-decoration: none;
padding: 12px 32px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary {
background-color: var(--color-primary);
color: var(--color-background);
}
.btn-primary:hover {
background-color: var(--color-primary-dark);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(47, 153, 164, 0.3);
}
.btn-secondary {
background-color: var(--color-accent);
color: var(--color-background);
}
.btn-secondary:hover {
background-color: var(--color-accent-dark);
box-shadow: 0 4px 12px rgba(255, 77, 0, 0.3);
}
.btn-outline {
background-color: transparent;
color: var(--color-background);
border: 2px solid var(--color-background);
}
.btn-outline:hover {
background-color: var(--color-background);
color: var(--color-primary);
}
.btn-large {
padding: 16px 48px;
font-size: var(--font-size-lg);
}
.btn-small {
padding: 8px 24px;
font-size: var(--font-size-sm);
}
/* ============================================
CARDS
============================================ */
.card {
background: var(--color-background);
border-radius: 8px;
box-shadow: var(--shadow-sm);
overflow: hidden;
transition: all 0.3s ease;
height: 100%;
}
.card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-4px);
}
.card-image {
width: 100%;
height: 240px;
object-fit: cover;
}
.card-content {
padding: var(--spacing-lg);
}
.card-title {
font-size: var(--font-size-2xl);
font-weight: 600;
margin-bottom: var(--spacing-sm);
}
.card-text {
font-size: var(--font-size-base);
line-height: 1.6;
color: var(--color-text-primary);
margin-bottom: var(--spacing-md);
}
.card-footer {
padding: var(--spacing-md) var(--spacing-lg);
border-top: 1px solid var(--color-border-light);
background-color: var(--color-background-alt);
}
.card-meta {
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
}
/* ============================================
LINKS
============================================ */
.link-standalone {
font-family: var(--font-headline);
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--color-primary);
}
.link-standalone::after {
content: '→';
transition: transform 0.3s ease;
}
.link-standalone:hover::after {
transform: translateX(4px);
}
/* ============================================
FORMS
============================================ */
.form-group {
margin-bottom: var(--spacing-lg);
}
.form-label {
display: block;
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--color-text-primary);
margin-bottom: var(--spacing-xs);
}
.form-input,
.form-textarea {
width: 100%;
font-family: var(--font-body);
font-size: var(--font-size-base);
color: var(--color-text-primary);
padding: 12px 16px;
border: 1px solid var(--color-border);
border-radius: 4px;
background: var(--color-background);
transition: all 0.3s ease;
}
.form-input:focus,
.form-textarea:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(47, 153, 164, 0.1);
}
.form-input::placeholder,
.form-textarea::placeholder {
color: var(--color-text-secondary);
}
.form-textarea {
min-height: 120px;
resize: vertical;
}
/* ============================================
FOOTER
============================================ */
.footer {
background-color: var(--color-text-primary);
color: var(--color-background);
padding: var(--spacing-4xl) 0 var(--spacing-lg);
}
.footer-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: var(--spacing-xl);
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-lg);
margin-bottom: var(--spacing-xl);
}
.footer-section h4 {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--color-background);
margin-bottom: var(--spacing-md);
}
.footer-section ul {
list-style: none;
padding: 0;
margin: 0;
}
.footer-section li {
margin-bottom: var(--spacing-sm);
}
.footer-section a {
color: rgba(255, 255, 255, 0.8);
text-decoration: none;
transition: color 0.3s ease;
}
.footer-section a:hover {
color: var(--color-primary);
}
.footer-bottom {
border-top: 1px solid rgba(255, 255, 255, 0.1);
padding-top: var(--spacing-lg);
text-align: center;
}
.footer-bottom p {
color: rgba(255, 255, 255, 0.6);
font-size: var(--font-size-sm);
margin: 0;
}
/* ============================================
RESPONSIVE DESIGN
============================================ */
/* Tablet (768px and below) */
@media (max-width: 1024px) {
.three-column {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
/* Typography */
h1 { font-size: var(--font-size-4xl); }
h2 { font-size: var(--font-size-3xl); }
h3 { font-size: var(--font-size-2xl); }
/* Layout */
.two-column,
.three-column {
grid-template-columns: 1fr;
}
/* Hero */
.hero {
min-height: 400px;
padding: var(--spacing-xxl) var(--spacing-lg);
}
.hero-title {
font-size: var(--font-size-4xl);
}
.hero-subtitle {
font-size: var(--font-size-lg);
}
.hero-cta {
flex-direction: column;
}
/* Navigation */
.navbar-toggle {
display: flex;
}
.navbar-menu {
position: absolute;
top: 100%;
left: 0;
right: 0;
flex-direction: column;
background: var(--color-background);
padding: var(--spacing-lg);
box-shadow: var(--shadow-md);
transform: translateY(-100%);
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.navbar-menu.active {
transform: translateY(0);
opacity: 1;
visibility: visible;
}
/* Sections */
.section {
padding: var(--spacing-xxl) 0;
}
}
/* Mobile (600px and below) */
@media (max-width: 600px) {
h1 { font-size: var(--font-size-3xl); }
h2 { font-size: var(--font-size-2xl); }
.hero {
min-height: 300px;
}
.btn-large {
padding: 12px 32px;
font-size: var(--font-size-base);
}
}
/* ============================================
UTILITIES
============================================ */
.text-center {
text-align: center;
}
.mb-0 { margin-bottom: 0; }
.mb-sm { margin-bottom: var(--spacing-sm); }
.mb-md { margin-bottom: var(--spacing-md); }
.mb-lg { margin-bottom: var(--spacing-lg); }
.mb-xl { margin-bottom: var(--spacing-xl); }

View File

@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: TYPO3 Slack
url: https://typo3.org/community/meet/chat-slack
about: Join the TYPO3 community on Slack for discussions and support
- name: TYPO3 Extension Documentation
url: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/Index.html
about: Official TYPO3 extension architecture and development documentation
- name: TYPO3 Coding Guidelines
url: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/Index.html
about: TYPO3 coding guidelines (CGL) and PSR-12 standards

View File

@@ -0,0 +1,51 @@
name: Publish new extension version to TER
on:
release:
types: [published]
jobs:
publish:
name: Publish new version to TER
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
env:
TYPO3_EXTENSION_KEY: ${{ secrets.TYPO3_EXTENSION_KEY }}
TYPO3_API_TOKEN: ${{ secrets.TYPO3_TER_ACCESS_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Check tag
run: |
if ! [[ ${{ github.ref }} =~ ^refs/tags/v[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$ ]]; then
exit 1
fi
- name: Get version
id: get-version
run: echo "version=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV
- name: Get comment
id: get-comment
run: |
readonly local comment=$(git tag -n10 -l v${{ env.version }} | sed "s/^v[0-9.]*[ ]*//g")
if [[ -z "${comment// }" ]]; then
echo "comment=Released version ${{ env.version }} of ${{ env.TYPO3_EXTENSION_KEY }} -- for details see $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases" >> $GITHUB_ENV
else
echo "comment=$comment -- for details see $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases" >> $GITHUB_ENV
fi
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
extensions: intl, mbstring, json, zip, curl
tools: composer:v2
- name: Install tailor
run: composer global require typo3/tailor --prefer-dist --no-progress --no-suggest
- name: Publish to TER
run: php ~/.composer/vendor/bin/tailor ter:publish --comment "${{ env.comment }}" ${{ env.version }}

2
skills/typo3-conformance/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.serena/
claudedocs/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,991 @@
---
name: typo3-conformance
description: "Evaluate TYPO3 extensions for conformance to TYPO3 12/13 LTS standards, coding guidelines (PSR-12), and architecture patterns. Use when assessing extension quality, generating conformance reports, identifying technical debt, or planning modernization. Validates: extension architecture, composer.json, ext_emconf.php, ext_* files, v13 deprecations, backend module v13 compliance (ES6 modules, DocHeader, Modal/Notification APIs, Module.html layout, ARIA, extension key consistency, CSRF, icons), dependency injection, services, testing, Extbase patterns, Crowdin, GitHub workflows. Dual scoring (0-100 base + 0-22 excellence). Delegates to typo3-tests and typo3-docs skills for deep analysis. PHP 8.1-8.4 support."
license: Complete terms in LICENSE.txt
---
# TYPO3 Extension Conformance Checker
**Purpose:** Evaluate TYPO3 extensions for conformance to official TYPO3 coding standards, architecture patterns, and best practices.
**Activation:** This skill activates when analyzing TYPO3 extensions for standards compliance, code quality, or conformance checking.
## Skill Ecosystem Integration
Delegate to specialized skills for deep domain analysis:
**🔧 typo3-tests** (https://github.com/netresearch/typo3-testing-skill)
- Deep PHPUnit configuration analysis
- Test quality patterns (AAA, mocking, fixtures)
- TYPO3 Testing Framework validation
- Accurate coverage calculation
- Test anti-pattern detection
**📚 typo3-docs** (https://github.com/netresearch/typo3-docs-skill)
- RST syntax and TYPO3 directive validation
- Documentation rendering with Docker
- Modern tooling detection (guides.xml, screenshots.json)
- Cross-reference integrity checks
- Official TYPO3 documentation standards
**Delegation Strategy:**
- **Surface-level checks:** Perform directly with this skill
- **Deep analysis:** Delegate to specialized skills when available
- **Fallback:** Apply basic validation if specialized skills unavailable
- **Integration:** Incorporate results into conformance scoring
## Version Compatibility
**Target Standards:**
- **TYPO3:** 12.4 LTS / 13.x
- **PHP:** 8.1 / 8.2 / 8.3 / 8.4
- **TYPO3 12 LTS:** Supports PHP 8.1 - 8.4
- **TYPO3 13 LTS:** Requires PHP 8.2 - 8.4
**Reference:** See `references/version-requirements.md` for complete version compatibility matrix and migration paths.
## Critical Validation References
**New Advanced Validation Guides:**
- **`references/runtests-validation.md`** - Validate Build/Scripts/runTests.sh against Tea extension reference
- **`references/development-environment.md`** - Validate DDEV/Docker development environment setup
- **`references/directory-structure.md`** - Validate .Build/ vs Build/ directory separation
These guides provide line-by-line validation strategies, automated validation scripts, and scoring methodologies to ensure comprehensive conformance checks.
## Evaluation Workflow
### Step 1: Initial Assessment
**Identify Extension Context:**
- Extension key and location
- TYPO3 version compatibility (check composer.json)
- Extension type (Extbase plugin, backend module, content element, etc.)
- Scope of evaluation (full extension vs specific components)
**Quick Directory Scan:**
```bash
# Check for presence of key directories
ls -la | grep -E "Classes|Configuration|Resources|Tests|Documentation"
# Verify required files
ls -1 | grep -E "composer.json|ext_emconf.php"
# Check documentation
ls -1 Documentation/ | grep -E "Index.rst|Settings.cfg"
```
### Step 2: File Structure Analysis
**Reference:** `references/extension-architecture.md`
**Check Required Files:**
- [ ] `composer.json` - Complete with PSR-4 autoloading
- [ ] `ext_emconf.php` - Proper metadata structure
- [ ] `Documentation/Index.rst` - Main documentation entry
- [ ] `Documentation/Settings.cfg` - Documentation settings
- [ ] `Classes/` directory exists
- [ ] `Configuration/` directory exists
- [ ] `Resources/` directory exists
**Validate Directory Structure:**
```bash
# Check Classes/ organization
find Classes/ -type d | head -20
# Verify Configuration/ structure
ls -R Configuration/
# Check Resources/ separation
ls -R Resources/Private/ Resources/Public/
```
**Common Issues to Flag:**
- ❌ PHP files in extension root (except ext_* files)
- ❌ Mixed directory naming (Controllers/ vs Controller/)
- ❌ Tests not mirroring Classes/ structure
- ❌ Missing required documentation files
- ❌ Non-standard directory names
**Output Format:**
```markdown
## File Structure Conformance
### ✅ Passed
- composer.json present with PSR-4 autoloading
- Classes/ directory properly organized
- Documentation/ complete with Index.rst and Settings.cfg
### ❌ Failed
- Missing Tests/ directory
- Configuration/Backend/ not found (backend modules using deprecated ext_tables.php)
- Resources/Private/Language/ missing XLIFF files
### ⚠️ Warnings
- Unusual directory: Classes/Helpers/ (should use Utility/)
- ext_tables.php present (consider migrating to Configuration/Backend/)
```
### Step 3: Coding Standards Analysis
**Reference:** `references/coding-guidelines.md`
**PHP Code Style Checks:**
```bash
# Find all PHP files
find Classes/ Tests/ Configuration/ -name "*.php"
# Check for PSR-12 violations (sample)
grep -r "array(" Classes/ # Should use []
grep -r "<?php$" Classes/ | wc -l # Missing declare(strict_types=1)
```
**Manual Inspection Points:**
**Naming Conventions:**
- Variables/methods: camelCase
- Classes: UpperCamelCase
- Constants: SCREAMING_SNAKE_CASE
- Namespaces: match directory structure
**Code Sample Analysis:**
```php
// Read sample files
cat Classes/Controller/ProductController.php
cat Classes/Domain/Model/Product.php
cat Classes/Domain/Repository/ProductRepository.php
```
**Check for:**
- [ ] `declare(strict_types=1)` at top of all PHP files
- [ ] Proper PHPDoc comments on classes and public methods
- [ ] Type declarations on all properties and parameters
- [ ] Short array syntax `[]` (not `array()`)
- [ ] Proper namespace structure
- [ ] Correct use statements (sorted and grouped)
- [ ] Descriptive method names with verbs
- [ ] Proper indentation (4 spaces, no tabs)
- [ ] **Inclusive language** - no problematic terminology
**Inclusive Language Check:**
```bash
# Check for non-inclusive terminology
grep -ri "master\|slave\|blacklist\|whitelist\|sanity" Classes/ Documentation/ --include="*.php" --include="*.rst" --include="*.md"
```
**Problematic Terms to Avoid:**
- ❌ "master/slave" → ✅ "primary/replica", "leader/follower", "main/secondary"
- ❌ "blacklist/whitelist" → ✅ "blocklist/allowlist", "denylist/permitlist", "exclusion list/inclusion list"
- ❌ "sanity check" → ✅ "confidence check", "validation check", "coherence check"
- ❌ "dummy" → ✅ "placeholder", "sample", "test"
- ❌ "grandfathered" → ✅ "legacy status", "existing entitlement"
**TYPO3 Community Values:**
The TYPO3 community is committed to inclusive language that welcomes all contributors. Code, comments, and documentation should use terminology that is respectful and professional.
**Output Format:**
```markdown
## Coding Standards Conformance
### ✅ Strengths
- All classes use UpperCamelCase naming
- Proper type declarations on methods
- PHPDoc comments present and complete
### ❌ Violations
- 15 files missing `declare(strict_types=1)`
- Classes/Controller/ProductController.php:1
- Classes/Service/CalculationService.php:1
- 8 instances of old array syntax `array()`
- Classes/Utility/ArrayHelper.php:45
- Classes/Domain/Model/Product.php:78
- 3 methods missing PHPDoc comments
- Classes/Service/EmailService.php:calculate()
- **5 instances of non-inclusive terminology**
- Classes/Service/FilterService.php:12 - "whitelist" → use "inclusion list" or "allowlist"
- Classes/Service/FilterService.php:45 - "blacklist" → use "exclusion list" or "blocklist"
- Documentation/Configuration/Index.rst:78 - "master configuration" → use "primary configuration" or "main configuration"
### ⚠️ Style Issues
- Inconsistent spacing around concatenation operators (12 instances)
- Some variables using snake_case (5 instances)
```
### Step 3.5: Backend Module v13 Compliance (If Applicable)
**Reference:** `references/backend-module-v13.md`
**Trigger:** Extension contains backend modules (Configuration/Backend/Modules.php or ext_tables.php with registerModule)
**Critical Checks:**
**Extension Key Consistency:**
```bash
# Check for mixed extension keys
grep -rn "EXT:.*/" Resources/Private/Templates/ | grep -v "EXT:${EXTENSION_KEY}/"
# Verify JavaScript uses correct name
grep -rn "alert\|console" Resources/Public/JavaScript/
```
**JavaScript Modernization:**
```bash
# Check for inline JavaScript (VIOLATION)
grep -rn "FooterAssets" Resources/Private/Templates/
grep -rn "<script" Resources/Private/Templates/
# Verify ES6 module exists
ls Resources/Public/JavaScript/BackendModule.js
# Check Modal/Notification API usage
grep -E "Modal\.confirm|Notification\.(success|error)" Resources/Public/JavaScript/*.js
```
**Layout Pattern:**
```bash
# Verify Module.html layout
ls Resources/Private/Layouts/Module.html
# Check all templates use Module layout
grep -n "f:layout name=" Resources/Private/Templates/Backend/**/*.html
```
**DocHeader Integration:**
```bash
# Check IconFactory injection
grep "IconFactory" Classes/Controller/Backend/*.php
# Verify DocHeader buttons
grep "addDocHeaderButtons\|makeLinkButton\|makeShortcutButton" Classes/Controller/Backend/*.php
```
**CSRF Protection:**
```bash
# Check for hardcoded URLs (VIOLATION)
grep -rn '"/typo3/' Resources/
# Verify uriBuilder usage
grep "uriFor(" Classes/Controller/Backend/*.php
```
**Accessibility:**
```bash
# Check ARIA labels
grep -rn "aria-label\|role=" Resources/Private/Templates/
```
**Icon Registration:**
```bash
# Verify modern icon registration
ls Configuration/Icons.php
# Check for deprecated IconRegistry (VIOLATION)
grep -rn "IconRegistry" ext_localconf.php
```
**Scoring Impact:**
- Extension key consistency: -5 points if violations found
- Inline JavaScript: -8 points (non-CSP-compliant)
- Missing DocHeader: -4 points (poor UX)
- Hardcoded URLs: -6 points (security risk)
- No accessibility: -3 points (WCAG non-compliant)
- Deprecated icon registration: -2 points
**Read `references/backend-module-v13.md` for:**
- Complete before/after code examples
- ES6 module architecture patterns
- Modal/Notification API usage
- WCAG 2.1 accessibility requirements
- 10-phase modernization checklist
- Real-world modernization case study (45/100 → 95/100)
---
### Step 4: PHP Architecture Evaluation
**Reference:** `references/php-architecture.md`
**Dependency Injection Analysis:**
```bash
# Check for Services.yaml
cat Configuration/Services.yaml
# Find constructors in Controllers/Services
grep -A 10 "public function __construct" Classes/Controller/*.php
grep -A 10 "public function __construct" Classes/Service/*.php
```
**Check for:**
- [ ] `Configuration/Services.yaml` present and properly configured
- [ ] Constructor injection used (not GeneralUtility::makeInstance)
- [ ] PSR-14 event listeners instead of hooks
- [ ] Event classes properly structured (immutable with getters/setters)
- [ ] PSR-15 middlewares if applicable
- [ ] Extbase patterns (models, repositories, controllers)
- [ ] No global state access ($GLOBALS)
**Anti-Pattern Detection:**
```bash
# Find deprecated patterns
grep -r "GeneralUtility::makeInstance" Classes/
grep -r "\$GLOBALS\[" Classes/
grep -r "inject[A-Z]" Classes/ # Method injection (check if justified)
```
**Output Format:**
```markdown
## PHP Architecture Conformance
### ✅ Modern Patterns
- Dependency injection via constructor in all controllers
- PSR-14 events used for extensibility
- Configuration/Services.yaml properly configured
- Extbase repositories extend base Repository class
### ❌ Architecture Issues
- 12 instances of GeneralUtility::makeInstance()
- Classes/Service/LegacyService.php:45
- Classes/Utility/DatabaseHelper.php:89
- 5 instances of $GLOBALS access
- Classes/Controller/ProductController.php:123
- No event listeners found (using deprecated hooks)
### 💡 Recommendations
- Migrate hook implementations to PSR-14 events
- Refactor GeneralUtility::makeInstance() to constructor injection
- Remove global state dependencies
```
### Step 5: Testing Infrastructure Assessment
**Reference:** `references/testing-standards.md`
**DELEGATION STRATEGY: For Deep Testing Analysis**
When Testing Standards category needs comprehensive validation, use skill delegation:
```
🔧 Use /skill typo3-tests (if available) for deep analysis:
- PHPUnit configuration quality and best practices
- Test code patterns (AAA, proper mocking, fixtures)
- TYPO3 Testing Framework usage validation
- Functional test database handling
- Accurate test coverage calculation
- Test quality metrics and anti-patterns
- Integration with TYPO3 core testing infrastructure
Return: Detailed testing conformance report with specific issues
```
**Fallback: If typo3-tests skill unavailable, perform basic validation:**
**Test Coverage Analysis:**
```bash
# Check test structure
ls -R Tests/
# Verify PHPUnit configuration
cat Build/phpunit/UnitTests.xml
cat Build/phpunit/FunctionalTests.xml
# Count test files
find Tests/Unit/ -name "*Test.php" | wc -l
find Tests/Functional/ -name "*Test.php" | wc -l
```
**Basic Evaluation Checklist:**
- [ ] Tests/Unit/ mirrors Classes/ structure
- [ ] Tests/Functional/ present with fixtures
- [ ] PHPUnit configuration files present
- [ ] Unit tests extend UnitTestCase
- [ ] Functional tests extend FunctionalTestCase
- [ ] Acceptance tests configured (Codeception)
- [ ] Test coverage >70% for new code
**Note:** Basic validation provides surface-level checks. For production-ready conformance reports, delegate to typo3-tests skill for comprehensive analysis
**Sample Test Inspection:**
```php
# Read sample test files
cat Tests/Unit/Service/CalculationServiceTest.php
cat Tests/Functional/Domain/Repository/ProductRepositoryTest.php
```
**Output Format:**
```markdown
## Testing Standards Conformance
### ✅ Test Infrastructure
- PHPUnit configuration files present
- Tests/ directory mirrors Classes/ structure
- Unit tests properly extend UnitTestCase
- Functional tests use fixtures correctly
### ❌ Testing Gaps
- No Tests/Functional/ directory found
- Missing PHPUnit configuration for functional tests
- 15 classes without corresponding unit tests:
- Classes/Service/EmailService.php
- Classes/Utility/StringHelper.php
- Classes/Domain/Repository/CategoryRepository.php
### 📊 Coverage Estimate
- Unit test files: 12
- Classes without tests: 15
- Estimated coverage: ~45% (below 70% recommendation)
```
### Step 6: Standards Application
**Reference:** `references/best-practices.md`
**When evaluating build scripts** (see `references/runtests-validation.md`):
1. Verify Build/Scripts/runTests.sh exists and matches Tea extension reference
2. Confirm PHP_VERSION default matches composer.json minimum requirement
3. Validate TYPO3_VERSION default matches composer.json target version
4. Check PHP version regex includes only supported versions (8.1-8.4)
5. Ensure database version lists are current (no EOL versions)
6. Verify network name is customized (not "friendsoftypo3-tea")
7. Validate test suite paths match actual directory structure
**When evaluating development environment** (see `references/development-environment.md`):
1. Locate DDEV configuration (.ddev/config.yaml) or Docker Compose alternative
2. When DDEV present, verify type set to 'typo3'
3. Confirm DDEV PHP version matches composer.json minimum
4. Validate DDEV docroot matches composer.json web-dir
5. Check database is MariaDB 10.11+ or MySQL 8.0+
6. Flag missing DevContainer configuration as optional improvement
**When evaluating directory structure** (see `references/directory-structure.md`):
1. Verify Build/ directory exists with committed configuration
2. Confirm .Build/ is fully gitignored (entire directory)
3. Flag any .Build/ files committed to git as critical violation
4. Check cache files are in .Build/, not Build/
5. Validate Composer paths reference .Build/ (bin-dir, vendor-dir, web-dir)
6. Verify quality tool configs reference .Build/ for cache
**When evaluating project infrastructure**:
1. Check .editorconfig presence for consistent code formatting
2. Verify .gitignore properly configured with standard exclusions
3. Locate CI/CD pipeline (.github/workflows/ or .gitlab-ci.yml)
4. Confirm code quality tools configured (php-cs-fixer, phpstan)
5. Validate README.md provides clear setup instructions
6. Ensure LICENSE file present with appropriate open-source license
7. Check GitHub issue templates (.github/ISSUE_TEMPLATE/config.yml)
8. Verify TER publishing workflow (.github/workflows/publish-to-ter.yml)
**Security Practices:**
```bash
# Check for security issues
grep -r "->exec(" Classes/ # SQL injection risk
grep -r "htmlspecialchars" Resources/Private/Templates/ # XSS prevention
grep -r "GeneralUtility::validEmail" Classes/ # Email validation
```
**Documentation Quality:**
**DELEGATION STRATEGY: For Deep Documentation Analysis**
When Documentation Excellence validation is needed, use skill delegation:
```
📚 Use /skill typo3-docs (if available) for deep analysis:
- RST syntax validation and TYPO3 directive compliance
- Documentation structure conformance (Index.rst, Settings.cfg)
- TYPO3 documentation standards (guides.xml, screenshots.json)
- Rendering validation with Docker (official TYPO3 render-guides)
- Intersphinx references validation
- Code example syntax validation
- Cross-reference integrity
- Modern documentation tooling detection
Return: Comprehensive documentation conformance report
```
**Fallback: If typo3-docs skill unavailable, perform basic validation:**
```bash
# Check documentation completeness
ls -1 Documentation/ | wc -l
cat Documentation/Index.rst | head -50
# Check for required files
ls Documentation/Settings.cfg Documentation/guides.xml 2>/dev/null
# Count RST files for excellence scoring
find Documentation/ -name "*.rst" | wc -l
```
**Note:** Basic validation only checks file existence. For production-ready documentation conformance, delegate to typo3-docs skill for comprehensive RST validation and rendering checks
**Output Format:**
```markdown
## Best Practices Conformance
### ✅ Project Infrastructure
- .editorconfig present
- .gitignore configured properly
- README.md with installation instructions
- GitHub Actions CI/CD pipeline configured
### ❌ Missing Components
- No phpstan.neon configuration
- No .php-cs-fixer configuration
- LICENSE file missing
- Documentation/ incomplete (missing Developer/ section)
### 🔒 Security Review
- ✅ Query parameters properly escaped
- ⚠️ 3 instances of direct SQL queries (check for injection risks)
- ✅ Email validation using GeneralUtility::validEmail
- ❌ Missing CSRF tokens in 2 forms
```
## Comprehensive Report Generation
### Final Conformance Report Template
```markdown
# TYPO3 Extension Conformance Report
**Extension:** {extension_name} (v{version})
**Evaluation Date:** {date}
**TYPO3 Compatibility:** {typo3_versions}
---
## Executive Summary
**Base Conformance Score:** {score}/100
**Excellence Indicators:** {excellence_score}/20 (Bonus)
**Total Score:** {total_score}/120
### Base Conformance Breakdown (0-100 points)
- Extension Architecture: {score}/20
- Coding Guidelines: {score}/20
- PHP Architecture: {score}/20
- Testing Standards: {score}/20
- Best Practices: {score}/20
### Excellence Indicators (0-20 bonus points)
- Community & Internationalization: {score}/6
- Advanced Quality Tooling: {score}/7
- Documentation Excellence: {score}/4
- Extension Configuration: {score}/3
**Priority Issues:** {count_critical}
**Recommendations:** {count_recommendations}
---
## 1. Extension Architecture ({score}/20)
### ✅ Strengths
- {list strengths}
### ❌ Critical Issues
- {list critical issues with file:line references}
### ⚠️ Warnings
- {list warnings}
### 💡 Recommendations
1. {specific actionable recommendations}
---
## 2. Coding Guidelines ({score}/20)
{repeat same structure}
---
## 3. PHP Architecture ({score}/20)
{repeat same structure}
---
## 4. Testing Standards ({score}/20)
{repeat same structure}
---
## 5. Best Practices ({score}/20)
{repeat same structure}
---
## Priority Action Items
### High Priority (Fix Immediately)
1. {critical issues that break functionality or security}
### Medium Priority (Fix Soon)
1. {important conformance issues}
### Low Priority (Improve When Possible)
1. {minor style and optimization issues}
---
## Detailed Issue List
| Category | Severity | File | Line | Issue | Recommendation |
|----------|----------|------|------|-------|----------------|
| Architecture | Critical | ext_tables.php | - | Using deprecated ext_tables.php | Migrate to Configuration/Backend/Modules.php |
| Coding | High | Classes/Controller/ProductController.php | 12 | Missing declare(strict_types=1) | Add at top of file |
| Architecture | High | Classes/Service/EmailService.php | 45 | Using GeneralUtility::makeInstance() | Switch to constructor injection |
| Testing | Medium | Tests/ | - | No functional tests | Create Tests/Functional/ with fixtures |
---
## Migration Guide
### Migrating from ext_tables.php to Configuration/Backend/
```php
// Before (ext_tables.php) - DEPRECATED
ExtensionUtility::registerModule(...);
// After (Configuration/Backend/Modules.php) - MODERN
return [
'web_myext' => [
'parent' => 'web',
...
],
];
```
### Converting to Constructor Injection
```php
// Before - DEPRECATED
use TYPO3\CMS\Core\Utility\GeneralUtility;
$repository = GeneralUtility::makeInstance(ProductRepository::class);
// After - MODERN
public function __construct(
private readonly ProductRepository $repository
) {}
```
---
## Pre-Evaluation Validation Procedures
Execute these validation steps systematically during conformance evaluation:
**File Structure Validation**
1. Verify composer.json exists with PSR-4 autoloading configuration
2. Confirm Classes/ directory follows namespace hierarchy
3. Check Configuration/ uses modern structure (no ext_tables.php dependencies)
4. Validate Resources/ separates Private/ and Public/ correctly
5. Ensure Tests/ mirrors Classes/ structure exactly
6. Confirm Documentation/ contains Index.rst and Settings.cfg
**Coding Standards Validation**
1. Scan all PHP files for declare(strict_types=1) at file start
2. Verify type declarations on all properties, parameters, and return types
3. Check PHPDoc blocks on all public methods and classes
4. Run PSR-12 compliance validation via PHPCS
5. Validate naming conventions (classes, methods, variables)
**PHP Architecture Validation**
1. Verify constructor injection used throughout codebase
2. Check Configuration/Services.yaml exists and configures services
3. Confirm PSR-14 events replace deprecated hooks
4. Search for GeneralUtility::makeInstance() usage (flag as violation)
5. Search for $GLOBALS access (flag as violation)
**Testing Validation**
1. Verify unit tests exist and execute successfully
2. Check functional tests include proper fixtures
3. Calculate test coverage (target >70%)
4. Confirm PHPUnit configuration files present (UnitTests.xml, FunctionalTests.xml)
5. Check acceptance tests if web interface present
**Standards Application Validation**
1. Verify development environment configured (DDEV or Docker Compose)
2. Check Build/Scripts/runTests.sh exists with accurate configuration
3. Validate directory structure (.Build/ vs Build/) separation
4. Confirm code quality tools configured (phpstan, php-cs-fixer)
5. Verify CI/CD pipeline setup (.github/workflows/ or .gitlab-ci.yml)
6. Review security practices implementation
7. Validate documentation completeness
8. Confirm README.md and LICENSE files present
---
## Reference Material Usage
**When checking extension architecture patterns**, read `references/extension-architecture.md` for:
- Standard directory structures and naming conventions
- Required files and their purposes
- PSR-4 autoloading configuration examples
**When validating coding guidelines**, read `references/coding-guidelines.md` for:
- PSR-12 compliance requirements
- TYPO3-specific code style rules
- Type declaration standards
**When evaluating backend modules**, read `references/backend-module-v13.md` for:
- Extension key consistency validation
- JavaScript modernization (ES6 modules, no inline scripts)
- TYPO3 Modal and Notification API patterns
- DocHeader integration (ButtonBar, IconFactory)
- Module.html layout requirements
- ARIA accessibility standards
- Icon registration (Configuration/Icons.php)
- CSRF protection via uriBuilder
- 10-phase modernization checklist
- Real-world case study (45/100 → 95/100 compliance)
**When evaluating PHP architecture**, read `references/php-architecture.md` for:
- Dependency injection patterns
- Service configuration examples
- PSR-14 event system usage
**When analyzing testing standards**, read `references/testing-standards.md` for:
- PHPUnit configuration patterns
- Test structure requirements
- Coverage calculation methods
**When applying best practices**, read `references/best-practices.md` for:
- Development environment setup patterns
- Build script validation criteria
- Directory structure standards
**For build script validation**, read `references/runtests-validation.md` for:
- Line-by-line runTests.sh validation
- Tea extension reference comparison
- Common configuration errors
**For development environment checks**, read `references/development-environment.md` for:
- DDEV configuration standards
- Docker Compose alternatives
- PHP and database version requirements
**For directory structure evaluation**, read `references/directory-structure.md` for:
- .Build/ vs Build/ separation rules
- Composer path configuration
- Gitignore patterns
**For excellence indicators**, read `references/excellence-indicators.md` for:
- Crowdin integration validation
- Advanced quality tooling detection
- Documentation excellence criteria
**For Crowdin integration**, read `references/crowdin-integration.md` for:
- TYPO3-compliant crowdin.yml patterns
- Translation workflow validation
- Common misconfigurations
**For hooks and events migration**, read `references/hooks-and-events.md` for:
- PSR-14 event patterns
- Deprecated hook identification
- Migration strategies
**When encountering official documentation**, visit:
- TYPO3 Core API: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/
- Extension Architecture: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/
- Coding Guidelines: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/
- Testing Documentation: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/
- Tea Extension (Best Practice): https://github.com/TYPO3BestPractices/tea
```
## Scoring System
### Overall Score Calculation
Each category (Architecture, Coding, PHP Architecture, Testing, Best Practices) is scored out of 20 points:
**Extension Architecture (20 points)**
- Required files present: 8 points
- Directory structure conformant: 6 points
- Naming conventions followed: 4 points
- No critical violations: 2 points
**Coding Guidelines (20 points)**
- PSR-12 compliance: 8 points
- Type declarations: 4 points
- PHPDoc completeness: 4 points
- Naming conventions: 4 points
**PHP Architecture (20 points)**
- Dependency injection: 8 points
- No deprecated patterns: 6 points
- Modern event system: 4 points
- Service configuration: 2 points
**Testing Standards (20 points)**
- Test coverage >70%: 10 points
- Proper test structure: 6 points
- Configuration files present: 4 points
**Best Practices (20 points)**
- Development environment (DDEV/Docker): 6 points
- DDEV configuration present: 4 points
- Configuration matches extension requirements: 2 points
- OR Docker Compose alternative: 3 points
- Build scripts (runTests.sh): 6 points
- Script present and executable: 2 points
- PHP/TYPO3 versions match extension: 3 points
- Database versions current: 1 point
- Directory structure (.Build/ vs Build/): 4 points
- .Build/ properly gitignored: 2 points
- Cache files in correct location: 1 point
- Composer paths aligned: 1 point
- Quality tools configured: 2 points
- Documentation complete: 2 points
**Note:** Previously this category scored only quality tools (6) and documentation (4). The new comprehensive approach validates development environment setup, build script accuracy, and directory structure standards, providing more thorough conformance assessment.
### Excellence Indicators (Bonus 0-20 points)
**Reference:** `references/excellence-indicators.md`
Excellence indicators are **optional features** that demonstrate exceptional quality and community engagement. Extensions are NOT penalized for missing these features - they provide bonus points only.
**Total Possible Score: 122 points** (100 base conformance + 22 excellence bonus)
**Category 1: Community & Internationalization (0-6 points)**
- Crowdin integration: 0-2 points
- Basic (crowdin.yml exists): +1 point
- TYPO3-compliant (preserve_hierarchy, wildcards, proper patterns, no download job): +2 points
- See `references/crowdin-integration.md` for comprehensive validation
- GitHub issue templates (.github/ISSUE_TEMPLATE/): +1 point
- .gitattributes with export-ignore: +1 point
- Professional README badges (stability, versions, downloads, CI): +2 points
**Category 2: Advanced Quality Tooling (0-9 points)**
- Fractor configuration (Build/fractor/fractor.php): +2 points
- TYPO3 CodingStandards package (typo3/coding-standards in composer.json): +2 points
- StyleCI integration (.styleci.yml): +1 point
- Makefile with self-documenting help: +1 point
- CI testing matrix (multiple PHP/TYPO3 versions): +1 point
- TER publishing workflow (.github/workflows/publish-to-ter.yml): +2 points
**Category 3: Documentation Excellence (0-4 points)**
- 50-99 RST files in Documentation/: +1 point
- 100-149 RST files: +2 points
- 150+ RST files: +3 points
- Modern documentation tooling (guides.xml, screenshots.json): +1 point
**Category 4: Extension Configuration (0-3 points)**
- ext_conf_template.txt with proper categorization: +1 point
- Composer documentation scripts (doc-init, doc-make, doc-watch): +1 point
- Multiple Configuration/Sets/ presets (for different use cases): +1 point
**Excellence Score Interpretation:**
- **0-5 points:** Standard extension (meets requirements)
- **6-11 points:** Good practices (actively maintained)
- **12-16 points:** Excellent quality (community reference level)
- **17-22 points:** Outstanding (georgringer/news level)
**Example Report Format:**
```markdown
## TYPO3 Extension Conformance Report
**Extension:** my_extension (v2.0.0)
---
### Score Summary
**Base Conformance:** 94/100
- Extension Architecture: 18/20
- Coding Guidelines: 20/20
- PHP Architecture: 18/20
- Testing Standards: 18/20
- Best Practices: 20/20
**Excellence Indicators:** 14/22 (Bonus)
- Community & Internationalization: 5/6
- ✅ Crowdin integration (+2)
- ✅ Professional README badges (+2)
- ✅ GitHub issue templates (+1)
- ❌ No .gitattributes export-ignore
- Advanced Quality Tooling: 7/9
- ✅ Fractor configuration (+2)
- ✅ TYPO3 CodingStandards (+2)
- ✅ Makefile with help (+1)
- ✅ TER publishing workflow (+2)
- ❌ No StyleCI
- ❌ No CI testing matrix
- Documentation Excellence: 2/4
- ✅ 75 RST files (+1)
- ✅ Modern tooling (guides.xml) (+1)
- Extension Configuration: 1/3
- ✅ Composer doc scripts (+1)
- ❌ No ext_conf_template.txt
- ❌ Only one Configuration/Sets/ preset
**Total Score:** 108/122
**Rating:** Excellent - This extension demonstrates strong conformance and excellent quality practices.
```
**Important Notes:**
- Base conformance (0-100) is MANDATORY - this is pass/fail criteria
- Excellence indicators (0-22) are OPTIONAL - bonus points for exceptional quality
- Extensions scoring 100/100 base are fully conformant, regardless of excellence score
- Excellence indicators identify community reference extensions
**📚 When Documentation/ is Missing or Incomplete:**
If conformance check reveals missing or incomplete documentation (0-1 points in Documentation Excellence):
**Recommended Next Step:**
```
📚 Use typo3-docs skill to create comprehensive documentation:
- Creates complete Documentation/ structure (8 sections)
- Follows modern TYPO3 13.x standards
- Uses card-grid navigation (not plain toctree)
- Applies confval directives for configuration
- Includes Settings.cfg and proper RST structure
- Ready for docs.typo3.org deployment
After creation: Re-run conformance check to verify improvement
```
**Documentation is the most common conformance gap** - extensions often score 90-95/100 with 0/4 documentation. Creating proper documentation typically improves score to 94-99/100 base + 1-2/20 excellence.
### Severity Levels
**Critical (Blocker):**
- Security vulnerabilities
- Broken functionality
- Major architecture violations
**High (Must Fix):**
- Deprecated pattern usage
- Missing required files
- Significant PSR-12 violations
**Medium (Should Fix):**
- Missing tests
- Incomplete documentation
- Minor architecture issues
**Low (Nice to Have):**
- Code style inconsistencies
- Optional quality improvements
## Usage Examples
### Example 1: Quick Conformance Check
```
User: "Check if my TYPO3 extension follows current standards"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,917 @@
# TYPO3 Extension Best Practices
**Source:** TYPO3 Best Practices (Tea Extension) and Core API Standards
**Purpose:** Real-world patterns and organizational best practices for TYPO3 extensions
## Project Structure
### Complete Extension Layout
```
my_extension/
├── .ddev/ # DDEV configuration
│ └── config.yaml
├── .github/ # GitHub Actions CI/CD
│ └── workflows/
│ └── tests.yml
├── Build/ # Build tools and configs
│ ├── phpunit/
│ │ ├── UnitTests.xml
│ │ └── FunctionalTests.xml
│ └── Scripts/
│ └── runTests.sh
├── Classes/ # PHP source code
│ ├── Controller/
│ ├── Domain/
│ │ ├── Model/
│ │ └── Repository/
│ ├── Service/
│ ├── Utility/
│ ├── EventListener/
│ └── ViewHelper/
├── Configuration/ # TYPO3 configuration
│ ├── Backend/
│ │ └── Modules.php
│ ├── Services.yaml
│ ├── TCA/
│ ├── TypoScript/
│ │ ├── setup.typoscript
│ │ └── constants.typoscript
│ └── Sets/ # TYPO3 v13+
│ └── MySet/
│ └── config.yaml
├── Documentation/ # RST documentation
│ ├── Index.rst
│ ├── Settings.cfg
│ ├── Introduction/
│ ├── Installation/
│ ├── Configuration/
│ ├── Developer/
│ └── Editor/
├── Resources/
│ ├── Private/
│ │ ├── Language/
│ │ │ ├── locallang.xlf
│ │ │ └── de.locallang.xlf
│ │ ├── Layouts/
│ │ ├── Partials/
│ │ └── Templates/
│ └── Public/
│ ├── Css/
│ ├── Icons/
│ ├── Images/
│ └── JavaScript/
├── Tests/
│ ├── Unit/
│ ├── Functional/
│ │ └── Fixtures/
│ └── Acceptance/
│ ├── Support/
│ └── codeception.yml
├── .editorconfig # Editor configuration
├── .gitattributes # Git attributes
├── .gitignore # Git ignore rules
├── .php-cs-fixer.dist.php # PHP CS Fixer config
├── composer.json # Composer configuration
├── composer.lock # Locked dependencies
├── ext_emconf.php # Extension metadata
├── ext_localconf.php # Global configuration
├── LICENSE # License file
├── phpstan.neon # PHPStan configuration
└── README.md # Project README
```
## Best Practices by Category
### 1. Dependency Management
**composer.json Best Practices:**
```json
{
"name": "vendor/my-extension",
"type": "typo3-cms-extension",
"description": "Clear, concise extension description",
"license": "GPL-2.0-or-later",
"authors": [
{
"name": "Author Name",
"email": "author@example.com",
"role": "Developer"
}
],
"require": {
"php": "^8.1",
"typo3/cms-core": "^12.4 || ^13.0",
"typo3/cms-backend": "^12.4 || ^13.0",
"typo3/cms-extbase": "^12.4 || ^13.0",
"typo3/cms-fluid": "^12.4 || ^13.0"
},
"require-dev": {
"typo3/coding-standards": "^0.7",
"typo3/testing-framework": "^8.0",
"phpunit/phpunit": "^10.5",
"phpstan/phpstan": "^1.10",
"friendsofphp/php-cs-fixer": "^3.0"
},
"autoload": {
"psr-4": {
"Vendor\\MyExtension\\": "Classes/"
}
},
"autoload-dev": {
"psr-4": {
"Vendor\\MyExtension\\Tests\\": "Tests/"
}
},
"config": {
"vendor-dir": ".Build/vendor",
"bin-dir": ".Build/bin",
"sort-packages": true,
"allow-plugins": {
"typo3/class-alias-loader": true,
"typo3/cms-composer-installers": true
}
},
"extra": {
"typo3/cms": {
"extension-key": "my_extension",
"web-dir": ".Build/Web"
}
}
}
```
### 2. Code Quality Tools
**.php-cs-fixer.dist.php:**
```php
<?php
declare(strict_types=1);
$config = \TYPO3\CodingStandards\CsFixerConfig::create();
$config->getFinder()
->in(__DIR__ . '/Classes')
->in(__DIR__ . '/Configuration')
->in(__DIR__ . '/Tests');
return $config;
```
**phpstan.neon:**
```neon
includes:
- .Build/vendor/phpstan/phpstan/conf/bleedingEdge.neon
parameters:
level: 9
paths:
- Classes
- Configuration
- Tests
excludePaths:
- .Build
- vendor
```
#### PHPStan Level 10 Best Practices for TYPO3
**Handling $GLOBALS['TCA'] in Tests:**
PHPStan cannot infer types for runtime-configured `$GLOBALS` arrays. Use ignore annotations:
```php
// ✅ Right: Suppress offsetAccess warnings for $GLOBALS['TCA']
/** @var array<string, mixed> $tcaConfig */
$tcaConfig = [
'type' => 'text',
'enableRichtext' => true,
];
// @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible
$GLOBALS['TCA']['tt_content']['columns']['bodytext']['config'] = $tcaConfig;
// ❌ Wrong: No type annotation or suppression
$GLOBALS['TCA']['tt_content']['columns']['bodytext']['config'] = [
'type' => 'text',
]; // PHPStan error: offsetAccess.nonOffsetAccessible
```
**Factory Methods vs Property Initialization:**
Avoid uninitialized property errors in test classes:
```php
// ❌ Wrong: PHPStan warns about uninitialized property
final class MyServiceTest extends UnitTestCase
{
private MyService $subject; // Uninitialized property
protected function setUp(): void
{
parent::setUp();
$this->subject = new MyService();
}
}
// ✅ Right: Use factory method
final class MyServiceTest extends UnitTestCase
{
private function createSubject(): MyService
{
return new MyService();
}
#[Test]
public function testSomething(): void
{
$subject = $this->createSubject();
// Use $subject
}
}
```
**Type Assertions for Dynamic Arrays:**
When testing arrays modified by reference:
```php
// ❌ Wrong: PHPStan cannot verify type after modification
public function testFieldProcessing(): void
{
$fieldArray = ['bodytext' => '<p>Test</p>'];
$this->subject->processFields($fieldArray);
// PHPStan error: Cannot access offset on mixed
self::assertStringContainsString('Test', $fieldArray['bodytext']);
}
// ✅ Right: Add type assertions
public function testFieldProcessing(): void
{
$fieldArray = ['bodytext' => '<p>Test</p>'];
$this->subject->processFields($fieldArray);
self::assertArrayHasKey('bodytext', $fieldArray);
self::assertIsString($fieldArray['bodytext']);
self::assertStringContainsString('Test', $fieldArray['bodytext']);
}
```
**Intersection Types for Mocks:**
Use intersection types for proper PHPStan analysis of mocks:
```php
// ✅ Right: Intersection type for mock
/** @var ResourceFactory&MockObject $resourceFactoryMock */
$resourceFactoryMock = $this->createMock(ResourceFactory::class);
// Alternative: @phpstan-var annotation
$resourceFactoryMock = $this->createMock(ResourceFactory::class);
/** @phpstan-var ResourceFactory&MockObject $resourceFactoryMock */
```
**Common PHPStan Suppressions for TYPO3:**
```php
// Suppress $GLOBALS['TCA'] access
// @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible
$GLOBALS['TCA']['table']['columns']['field'] = $config;
// Suppress $GLOBALS['TYPO3_CONF_VARS'] access
// @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['key'] = MyClass::class;
// Suppress mixed type from legacy code
// @phpstan-ignore-next-line argument.type
$this->view->assign('data', $legacyArray);
```
**Type Hints for Service Container Retrieval:**
```php
// ✅ Right: Type hint service retrieval
/** @var DataHandler $dataHandler */
$dataHandler = $this->get(DataHandler::class);
/** @var ResourceFactory $resourceFactory */
$resourceFactory = $this->get(ResourceFactory::class);
```
### 3. Service Configuration
**Configuration/Services.yaml:**
```yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
# Auto-register all classes
Vendor\MyExtension\:
resource: '../Classes/*'
# Exclude specific directories
Vendor\MyExtension\Domain\Model\:
resource: '../Classes/Domain/Model/*'
autoconfigure: false
# Explicit service configuration example
Vendor\MyExtension\Service\EmailService:
arguments:
$fromEmail: '%env(DEFAULT_FROM_EMAIL)%'
$fromName: 'TYPO3 Extension'
# Tag configuration example
Vendor\MyExtension\Command\ImportCommand:
tags:
- name: 'console.command'
command: 'myext:import'
description: 'Import data from external source'
```
### 4. Backend Module Configuration
**Configuration/Backend/Modules.php:**
```php
<?php
return [
'web_myext' => [
'parent' => 'web',
'position' => ['after' => 'web_info'],
'access' => 'user',
'workspaces' => 'live',
'path' => '/module/web/myext',
'labels' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_mod.xlf',
'extensionName' => 'MyExtension',
'controllerActions' => [
\Vendor\MyExtension\Controller\BackendController::class => [
'list',
'show',
'edit',
'update',
],
],
],
];
```
### 5. Testing Infrastructure
**Build/Scripts/runTests.sh:**
```bash
#!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
# Run unit tests
if [ "$1" = "unit" ]; then
php vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
fi
# Run functional tests
if [ "$1" = "functional" ]; then
typo3DatabaseDriver=pdo_sqlite \
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
fi
# Run all tests
if [ "$1" = "all" ]; then
php vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
typo3DatabaseDriver=pdo_sqlite \
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
fi
```
### 6. CI/CD Configuration
**.github/workflows/tests.yml:**
```yaml
name: Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
tests:
name: Tests (PHP ${{ matrix.php }}, TYPO3 ${{ matrix.typo3 }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.1', '8.2', '8.3']
typo3: ['12.4', '13.0']
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring, xml, json, zip, curl
coverage: none
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Lint PHP
run: find . -name \*.php ! -path "./vendor/*" ! -path "./.Build/*" -exec php -l {} \;
- name: PHP CS Fixer
run: .Build/bin/php-cs-fixer fix --dry-run --diff
- name: PHPStan
run: .Build/bin/phpstan analyze
- name: Unit Tests
run: .Build/bin/phpunit -c Build/phpunit/UnitTests.xml
- name: Functional Tests
run: |
typo3DatabaseDriver=pdo_sqlite \
.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml
```
### 7. Documentation Standards
**Documentation/Index.rst:**
```rst
.. include:: /Includes.rst.txt
==============
My Extension
==============
:Extension key:
my_extension
:Package name:
vendor/my-extension
:Version:
|release|
:Language:
en
:Author:
Author Name
:License:
This document is published under the
`Creative Commons BY 4.0 <https://creativecommons.org/licenses/by/4.0/>`__
license.
:Rendered:
|today|
----
Clear and concise extension description explaining the purpose and main features.
----
**Table of Contents:**
.. toctree::
:maxdepth: 2
:titlesonly:
Introduction/Index
Installation/Index
Configuration/Index
Editor/Index
Developer/Index
Sitemap
```
**Page Size Guidelines:**
Follow TYPO3 documentation best practices for page organization and sizing:
**Index.rst (Landing Page):**
- **Target:** 80-150 lines
- **Maximum:** 200 lines
- **Purpose:** Entry point with metadata, brief description, and navigation only
- **Contains:** Extension metadata, brief description, card-grid (optional), toctree, license
- **Anti-pattern:** ❌ Embedding all content (introduction, requirements, contributing, credits, etc.)
**Content Pages:**
- **Target:** 100-300 lines per file
- **Optimal:** 150-200 lines
- **Maximum:** 400 lines (split if larger)
- **Structure:** Focused on single topic or logically related concepts
- **Split Strategy:** Create subdirectories for complex topics with multiple aspects
**Red Flags:**
- ❌ Index.rst >200 lines → Extract content to Introduction/, Contributing/, etc.
- ❌ Single file >400 lines → Split into multiple focused pages
- ❌ All content in Index.rst → Create proper section directories
- ❌ Navigation by scrolling → Use card-grid + toctree structure
**Proper Structure Example:**
```
Documentation/
├── Index.rst # Landing page (80-150 lines)
├── Introduction/ # Getting started
│ └── Index.rst # Features, requirements, quick start
├── Installation/ # Setup instructions
│ └── Index.rst
├── Configuration/ # Configuration guides
│ ├── Index.rst
│ ├── Basic.rst
│ └── Advanced.rst
├── Contributing/ # Contribution guidelines
│ └── Index.rst # Code, translations, credits, resources
├── Examples/ # Usage examples
├── Troubleshooting/ # Problem solving
└── API/ # Developer reference
```
**Benefits:**
- ✅ Better user experience (focused, scannable pages)
- ✅ Easier maintenance (smaller, manageable files)
- ✅ Improved search results (specific pages rank better)
- ✅ Clear information architecture
- ✅ Follows TYPO3 documentation standards
- ✅ Mobile-friendly navigation
**Reference:** [TYPO3 tea extension](https://github.com/TYPO3BestPractices/tea) - exemplary documentation structure
### 8. Version Control Best Practices
#### Default Branch Naming
**✅ Use `main` as the default branch instead of `master`**
**Rationale:**
- **Industry Standard**: GitHub, GitLab, and Bitbucket all default to `main` for new repositories
- **Modern Convention**: Aligns with current version control ecosystem standards
- **Inclusive Language**: Part of broader industry shift toward inclusive terminology
- **Consistency**: Matches TYPO3 Core and most modern TYPO3 extensions
**Migration from `master` to `main`:**
If your extension currently uses `master`, migrate to `main`:
```bash
# 1. Create main branch from master
git checkout master
git pull origin master
git checkout -b main
git push -u origin main
# 2. Change default branch on GitHub
gh repo edit --default-branch main
# 3. Update all branch references in codebase
# - CI/CD workflows (.github/workflows/*.yml)
# - Documentation (guides.xml, *.rst files)
# - URLs in CONTRIBUTING.md, README.md
# 4. Delete old master branch
git branch -d master
git push origin --delete master
```
**Example CI/CD workflow update:**
```yaml
# .github/workflows/tests.yml
on:
push:
branches: [main, develop] # Changed from: master
pull_request:
branches: [main] # Changed from: master
```
**Example documentation update:**
```xml
<!-- Documentation/guides.xml -->
<extension edit-on-github-branch="main" /> <!-- Changed from: master -->
```
#### Branch Protection Enforcement
**Prevent accidental `master` branch recreation** and **protect `main` branch** using GitHub Repository Rulesets.
**Block master branch - prevents creation and pushes:**
Create `ruleset-block-master.json`:
```json
{
"name": "Block master branch",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["refs/heads/master"],
"exclude": []
}
},
"rules": [
{
"type": "creation"
},
{
"type": "update"
},
{
"type": "deletion"
}
],
"bypass_actors": []
}
```
Apply the ruleset:
```bash
gh api -X POST repos/OWNER/REPO/rulesets \
--input ruleset-block-master.json
```
**Protect main branch - requires CI and prevents force pushes:**
Create `ruleset-protect-main.json`:
```json
{
"name": "Protect main branch",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["refs/heads/main"],
"exclude": []
}
},
"rules": [
{
"type": "required_status_checks",
"parameters": {
"required_status_checks": [
{
"context": "build"
}
],
"strict_required_status_checks_policy": false
}
},
{
"type": "non_fast_forward"
}
],
"bypass_actors": [
{
"actor_id": 5,
"actor_type": "RepositoryRole",
"bypass_mode": "always"
}
]
}
```
Apply the ruleset:
```bash
gh api -X POST repos/OWNER/REPO/rulesets \
--input ruleset-protect-main.json
```
**Verify rulesets are active:**
```bash
# List all rulesets
gh api repos/OWNER/REPO/rulesets
# Test master branch is blocked (should fail)
git push origin test-branch:master
# Expected: remote: error: GH013: Repository rule violations found
```
**Benefits of Repository Rulesets:**
- ✅ Prevents accidental `master` branch recreation
- ✅ Enforces CI status checks before merging to `main`
- ✅ Prevents force pushes to protected branches
- ✅ Allows admin bypass for emergency situations
- ✅ More flexible than legacy branch protection rules
- ✅ Supports complex conditions and multiple rule types
### 9. Language File Organization
**Resources/Private/Language/locallang.xlf:**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext"
original="EXT:my_extension/Resources/Private/Language/locallang.xlf"
date="2024-01-01T12:00:00Z"
product-name="my_extension">
<header/>
<body>
<trans-unit id="plugin.title" resname="plugin.title">
<source>My Extension Plugin</source>
</trans-unit>
<trans-unit id="plugin.description" resname="plugin.description">
<source>Displays product list with filters</source>
</trans-unit>
</body>
</file>
</xliff>
```
### 10. TCA Best Practices
**Configuration/TCA/tx_myext_domain_model_product.php:**
```php
<?php
return [
'ctrl' => [
'title' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_product',
'label' => 'title',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'delete' => 'deleted',
'sortby' => 'sorting',
'versioningWS' => true,
'origUid' => 't3_origuid',
'languageField' => 'sys_language_uid',
'transOrigPointerField' => 'l10n_parent',
'transOrigDiffSourceField' => 'l10n_diffsource',
'translationSource' => 'l10n_source',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime',
],
'searchFields' => 'title,description',
'iconfile' => 'EXT:my_extension/Resources/Public/Icons/product.svg',
],
'types' => [
'1' => [
'showitem' => '
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
title, description,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
hidden, starttime, endtime
',
],
],
'columns' => [
'title' => [
'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_product.title',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim,required',
'max' => 255,
],
],
'description' => [
'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_product.description',
'config' => [
'type' => 'text',
'enableRichtext' => true,
'richtextConfiguration' => 'default',
],
],
],
];
```
### 11. Security Best Practices
**✅ Input Validation:**
```php
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
// Validate integer input
if (!MathUtility::canBeInterpretedAsInteger($input)) {
throw new \InvalidArgumentException('Invalid integer value');
}
// Sanitize email
$email = GeneralUtility::validEmail($input) ? $input : '';
// Escape output in templates
{product.title -> f:format.htmlspecialchars()}
```
**✅ SQL Injection Prevention:**
```php
// Use QueryBuilder with bound parameters
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_myext_domain_model_product');
$products = $queryBuilder
->select('*')
->from('tx_myext_domain_model_product')
->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
)
)
->executeQuery()
->fetchAllAssociative();
```
**✅ CSRF Protection:**
```html
<!-- Always include form protection token -->
<f:form.hidden property="__trustedProperties" value="{formProtection}" />
```
## Common Anti-Patterns to Avoid
### ❌ Don't: Use GeneralUtility::makeInstance() for Services
```php
// Old way (deprecated)
$repository = GeneralUtility::makeInstance(ProductRepository::class);
```
### ✅ Do: Use Dependency Injection
```php
// Modern way
public function __construct(
private readonly ProductRepository $repository
) {}
```
### ❌ Don't: Access $GLOBALS directly
```php
// Avoid global state
$user = $GLOBALS['BE_USER'];
$tsfe = $GLOBALS['TSFE'];
```
### ✅ Do: Inject Context and Services
```php
public function __construct(
private readonly Context $context,
private readonly TypoScriptService $typoScriptService
) {}
```
### ❌ Don't: Use ext_tables.php for configuration
```php
// ext_tables.php (deprecated for most uses)
```
### ✅ Do: Use dedicated configuration files
```php
// Configuration/Backend/Modules.php
// Configuration/TCA/
// Configuration/Services.yaml
```
## Conformance Checklist
- [ ] Complete directory structure following best practices
- [ ] composer.json with proper PSR-4 autoloading
- [ ] Quality tools configured (php-cs-fixer, phpstan)
- [ ] CI/CD pipeline (GitHub Actions or GitLab CI)
- [ ] Comprehensive test coverage (unit, functional, acceptance)
- [ ] Complete documentation in RST format
- [ ] Service configuration in Services.yaml
- [ ] Backend modules in Configuration/Backend/
- [ ] TCA files in Configuration/TCA/
- [ ] Language files in XLIFF format
- [ ] Dependency injection throughout
- [ ] No global state access
- [ ] Security best practices followed
- [ ] .editorconfig for consistent formatting
- [ ] README.md with clear instructions
- [ ] LICENSE file present

View File

@@ -0,0 +1,610 @@
# TYPO3 Coding Guidelines
**Source:** TYPO3 Core API Reference - Coding Guidelines
**Purpose:** PHP code style, formatting standards, and PSR-12 compliance for TYPO3 extensions
## PSR-12 Compliance
TYPO3 follows **PSR-12: Extended Coding Style** as the foundation for PHP code style.
**Key PSR-12 Requirements:**
- 4 spaces for indentation (NO tabs)
- Unix line endings (LF)
- Maximum line length: 120 characters (soft limit), 80 recommended
- Opening braces for classes/methods on same line
- One statement per line
- Visibility MUST be declared on all properties and methods
## Identifier Naming Conventions
### Variables and Methods: camelCase
```php
// ✅ Right
$userName = 'John';
$totalPrice = 100;
public function calculateTotal() {}
public function getUserData() {}
// ❌ Wrong
$user_name = 'John'; // snake_case
$UserName = 'John'; // PascalCase
public function CalculateTotal() {} // PascalCase
public function get_user_data() {} // snake_case
```
### Classes: UpperCamelCase (PascalCase)
```php
// ✅ Right
class UserController {}
class PaymentService {}
class ProductRepository {}
// ❌ Wrong
class userController {} // camelCase
class payment_service {} // snake_case
class productRepository {} // camelCase
```
### Constants: SCREAMING_SNAKE_CASE
```php
// ✅ Right
const MAX_UPLOAD_SIZE = 1024;
const API_ENDPOINT = 'https://api.example.com';
private const DEFAULT_TIMEOUT = 30;
// ❌ Wrong
const maxUploadSize = 1024; // camelCase
const ApiEndpoint = '...'; // PascalCase
```
### Namespaces: UpperCamelCase
```php
// ✅ Right
namespace Vendor\ExtensionKey\Domain\Model;
namespace Vendor\ExtensionKey\Controller;
// ❌ Wrong
namespace vendor\extension_key\domain\model;
namespace Vendor\extension_key\Controller;
```
## Function and Method Naming
### Descriptive Names with Verbs
```php
// ✅ Right: Verb + noun, descriptive
public function getUserById(int $id): ?User {}
public function calculateTotalPrice(array $items): float {}
public function isValidEmail(string $email): bool {}
public function hasPermission(string $action): bool {}
// ❌ Wrong: No verb, ambiguous
public function user(int $id) {}
public function price(array $items) {}
public function email(string $email) {}
public function permission(string $action) {}
```
### Boolean Methods: is/has/can/should
```php
// ✅ Right
public function isActive(): bool {}
public function hasAccess(): bool {}
public function canEdit(): bool {}
public function shouldRender(): bool {}
// ❌ Wrong
public function active(): bool {}
public function access(): bool {}
public function checkEdit(): bool {}
```
## Array Formatting
### Short Syntax Only
```php
// ✅ Right: Short array syntax
$items = [];
$config = ['foo' => 'bar'];
$users = [
['name' => 'John', 'age' => 30],
['name' => 'Jane', 'age' => 25],
];
// ❌ Wrong: Long array syntax (deprecated)
$items = array();
$config = array('foo' => 'bar');
```
### Multi-line Array Formatting
```php
// ✅ Right: Proper indentation and trailing comma
$configuration = [
'key1' => 'value1',
'key2' => 'value2',
'nested' => [
'subkey1' => 'subvalue1',
'subkey2' => 'subvalue2',
], // Trailing comma
];
// ❌ Wrong: No trailing comma, inconsistent indentation
$configuration = [
'key1' => 'value1',
'key2' => 'value2',
'nested' => [
'subkey1' => 'subvalue1',
'subkey2' => 'subvalue2'
]
];
```
## Conditional Statement Layout
### If/ElseIf/Else
```php
// ✅ Right: Proper spacing and braces
if ($condition) {
doSomething();
} elseif ($otherCondition) {
doSomethingElse();
} else {
doDefault();
}
// ❌ Wrong: Missing spaces, wrong brace placement
if($condition){
doSomething();
}
else if ($otherCondition) {
doSomethingElse();
}
else {
doDefault();
}
```
### Switch Statements
```php
// ✅ Right
switch ($status) {
case 'active':
processActive();
break;
case 'pending':
processPending();
break;
default:
processDefault();
}
// ❌ Wrong: Inconsistent indentation
switch ($status) {
case 'active':
processActive();
break;
case 'pending':
processPending();
break;
default:
processDefault();
}
```
## String Handling
### Single Quotes Default
```php
// ✅ Right: Single quotes for simple strings
$message = 'Hello, World!';
$path = 'path/to/file.php';
// ❌ Wrong: Unnecessary double quotes
$message = "Hello, World!"; // No variable interpolation
$path = "path/to/file.php";
```
### Double Quotes for Interpolation
```php
// ✅ Right: Double quotes when interpolating
$name = 'John';
$message = "Hello, {$name}!";
// ❌ Wrong: Concatenation instead of interpolation
$message = 'Hello, ' . $name . '!'; // Less readable
```
### String Concatenation
```php
// ✅ Right: Spaces around concatenation operator
$fullPath = $basePath . '/' . $filename;
$message = 'Hello ' . $name . ', welcome!';
// ❌ Wrong: No spaces around operator
$fullPath = $basePath.'/'.$filename;
$message = 'Hello '.$name.', welcome!';
```
## PHPDoc Comment Standards
### Class Documentation
```php
// ✅ Right: Complete class documentation
/**
* Service for calculating product prices with tax and discounts
*
* This service handles complex price calculations including:
* - Tax rates based on country
* - Quantity discounts
* - Promotional codes
*
* @author John Doe <john@example.com>
* @license GPL-2.0-or-later
*/
final class PriceCalculationService
{
// ...
}
```
### Method Documentation
```php
// ✅ Right: Complete method documentation
/**
* Calculate total price with tax for given items
*
* @param array<int, array{product: Product, quantity: int}> $items
* @param string $countryCode ISO 3166-1 alpha-2 country code
* @param float $discountPercent Discount percentage (0-100)
* @return float Total price including tax
* @throws \InvalidArgumentException If country code is invalid
*/
public function calculateTotal(
array $items,
string $countryCode,
float $discountPercent = 0.0
): float {
// ...
}
// ❌ Wrong: Missing or incomplete documentation
/**
* Calculates total
*/
public function calculateTotal($items, $countryCode, $discountPercent = 0.0) {
// Missing param types, descriptions, return type
}
```
### Property Documentation
```php
// ✅ Right
/**
* @var UserRepository User data repository
*/
private readonly UserRepository $userRepository;
/**
* @var array<string, mixed> Configuration options
*/
private array $config = [];
// ❌ Wrong: No type hint or description
/**
* @var mixed
*/
private $userRepository;
```
## Curly Brace Placement
### Classes and Methods: Same Line
```php
// ✅ Right: Opening brace on same line
class MyController
{
public function indexAction(): ResponseInterface
{
// ...
}
}
// ❌ Wrong: Opening brace on new line (K&R style)
class MyController {
public function indexAction(): ResponseInterface {
// ...
}
}
```
### Control Structures: Same Line
```php
// ✅ Right
if ($condition) {
doSomething();
}
foreach ($items as $item) {
processItem($item);
}
// ❌ Wrong: Opening brace on new line
if ($condition)
{
doSomething();
}
```
## Namespace and Use Statements
### Namespace Structure
```php
// ✅ Right: Proper namespace declaration
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class Product extends AbstractEntity
{
// ...
}
```
### Use Statements Organization
```php
// ✅ Right: Grouped and sorted
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
// ❌ Wrong: Unsorted, mixed
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Imaging\IconFactory;
```
## Type Declarations
### Strict Types
```php
// ✅ Right: declare(strict_types=1) at the top
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Service;
class MyService
{
public function calculate(int $value): float
{
return $value * 1.19;
}
}
// ❌ Wrong: No strict types declaration
<?php
namespace Vendor\ExtensionKey\Service;
class MyService
{
public function calculate($value) // No type hints
{
return $value * 1.19;
}
}
```
### Property Type Declarations (PHP 7.4+)
```php
// ✅ Right: Typed properties
class User
{
private string $username;
private int $id;
private ?string $email = null;
private array $roles = [];
}
// ❌ Wrong: No type declarations
class User
{
private $username;
private $id;
private $email;
private $roles;
}
```
## File Structure
### Standard File Template
```php
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace Vendor\ExtensionKey\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/**
* Product model
*/
class Product extends AbstractEntity
{
/**
* @var string Product title
*/
private string $title = '';
public function getTitle(): string
{
return $this->title;
}
public function setTitle(string $title): void
{
$this->title = $title;
}
}
```
## PHPStan and Static Analysis
TYPO3 extensions should use **PHPStan level 10** (strictest) for maximum type safety and code quality.
### PHPStan Baseline Hygiene
**Critical Rule:** New code must NEVER add errors to `phpstan-baseline.neon`.
The baseline file exists only for legacy code that hasn't been refactored yet. All new code must pass PHPStan level 10 without baseline suppression.
**Validation:**
```bash
# Check if your changes added to baseline
git diff HEAD~1 Build/phpstan-baseline.neon
# If count increased, you MUST fix the underlying issues
# Example: count: 8 → count: 9 means you added 1 new error
```
### Type-Safe Mixed Value Handling
**Common PHPStan Error:** "Cannot cast mixed to int/string/bool"
**Occurs with:** TypoScript configuration, user input, API responses
**❌ Wrong (adds to baseline):**
```php
// PHPStan: Cannot cast mixed to int
$maxSize = (int) ($conf['maxSize'] ?? 0);
```
**✅ Right (passes level 10):**
```php
// Type-guard before casting
$value = $conf['maxSize'] ?? 0;
if (is_numeric($value)) {
$maxSize = (int) $value;
} else {
$maxSize = 0;
}
```
### Common Mixed Type Patterns
**Arrays from configuration:**
```php
// ❌ Wrong
$items = (array) $conf['items'];
// ✅ Right
$items = [];
if (isset($conf['items']) && is_array($conf['items'])) {
$items = $conf['items'];
}
```
**Strings from user input:**
```php
// ❌ Wrong
$name = (string) $_POST['name'];
// ✅ Right
$name = '';
if (isset($_POST['name']) && is_string($_POST['name'])) {
$name = $_POST['name'];
}
```
**Boolean from configuration:**
```php
// ❌ Wrong
$enabled = (bool) $conf['enabled'];
// ✅ Right
$enabled = isset($conf['enabled']) && (bool) $conf['enabled'];
```
### Pre-Commit PHPStan Check
Always run PHPStan before committing:
```bash
# Run PHPStan
composer ci:php:stan
# Verify no new baseline entries
git diff Build/phpstan-baseline.neon
# If baseline changed, fix the issues instead of committing the baseline
```
## Conformance Checklist
- [ ] All PHP files use 4 spaces for indentation (NO tabs)
- [ ] Variables and methods use camelCase
- [ ] Classes use UpperCamelCase
- [ ] Constants use SCREAMING_SNAKE_CASE
- [ ] Array short syntax [] used (not array())
- [ ] Multi-line arrays have trailing commas
- [ ] Strings use single quotes by default
- [ ] String concatenation has spaces around `.` operator
- [ ] All classes have PHPDoc comments
- [ ] All public methods have PHPDoc with @param and @return
- [ ] Opening braces on same line for classes/methods
- [ ] declare(strict_types=1) at top of all PHP files
- [ ] Proper namespace structure matching directory
- [ ] Use statements grouped and sorted
- [ ] Type declarations on all properties and method parameters
- [ ] Maximum line length 120 characters
- [ ] Unix line endings (LF)
- [ ] PHPStan level 10 passes with zero errors
- [ ] No new errors added to phpstan-baseline.neon
- [ ] Type-guards before casting mixed values (is_numeric, is_string, is_array)

View File

@@ -0,0 +1,468 @@
# Composer.json Validation Standards (TYPO3 v13)
**Source:** TYPO3 Core API Reference v13.4 - FileStructure/ComposerJson.html
**Purpose:** Complete validation rules for composer.json in TYPO3 extensions
## Mandatory Fields
### name
**Format:** `<vendor>/<dashed-extension-key>`
**Examples:**
```json
"name": "vendor-name/my-extension"
"name": "johndoe/some-extension"
```
**Validation:**
```bash
jq -r '.name' composer.json | grep -E '^[a-z0-9-]+/[a-z0-9-]+$' && echo "✅ Valid" || echo "❌ Invalid format"
```
### type
**Required Value:** `typo3-cms-extension` (for third-party extensions)
**Validation:**
```bash
jq -r '.type' composer.json | grep -q "typo3-cms-extension" && echo "✅ Correct type" || echo "❌ Wrong type"
```
### description
**Format:** Single-line summary describing what the extension does
**Requirements:**
- Clear, concise description of extension functionality
- Should identify the vendor/company for professional extensions
- Avoid vague descriptions like "An extension" or "Utility tools"
**Good Examples:**
```json
"description": "Adds image support to CKEditor5 RTE - by Netresearch"
"description": "TYPO3 extension for advanced content management by Vendor GmbH"
"description": "Provides custom form elements for newsletter subscription"
```
**Bad Examples:**
```json
"description": "Extension" // Too vague
"description": "Some tools" // Meaningless
"description": "" // Empty
```
**Validation:**
```bash
# Check description exists and is not empty
jq -r '.description' composer.json | grep -q . && echo "✅ Has description" || echo "❌ Missing description"
# Check description length (should be meaningful, >20 chars)
DESC_LEN=$(jq -r '.description | length' composer.json)
[[ $DESC_LEN -gt 20 ]] && echo "✅ Description is meaningful" || echo "⚠️ Description too short"
```
### license
**Recommended:** `GPL-2.0-only` or `GPL-2.0-or-later`
**Validation:**
```bash
jq -r '.license' composer.json | grep -qE "GPL-2.0-(only|or-later)" && echo "✅ GPL license" || echo "⚠️ Check license"
```
### require
**Minimum:** Must specify `typo3/cms-core` with version constraints
**Version Constraint Format:**
- `^12.4 || ^13.4` - Multiple major versions (recommended for v12/v13 compat)
- `^12.4` - Single major version
- `>=12.4` ❌ - NO upper bound (not recommended)
**Validation:**
```bash
# Check typo3/cms-core present
jq -r '.require["typo3/cms-core"]' composer.json | grep -q . && echo "✅ TYPO3 core required" || echo "❌ Missing typo3/cms-core"
# Check for upper bound (^ or specific upper version)
jq -r '.require["typo3/cms-core"]' composer.json | grep -qE '(\^|[0-9]+\.[0-9]+\.[0-9]+-[0-9]+\.[0-9]+\.[0-9]+)' && echo "✅ Has upper bound" || echo "⚠️ Missing upper bound"
```
### autoload
**Format:** PSR-4 mapping to Classes/ directory
**Example:**
```json
"autoload": {
"psr-4": {
"Vendor\\ExtensionName\\": "Classes/"
}
}
```
**Validation:**
```bash
jq -r '.autoload["psr-4"]' composer.json | grep -q "Classes" && echo "✅ PSR-4 autoload configured" || echo "❌ Missing autoload"
```
### extra.typo3/cms.extension-key
**Required:** Maps to underscored extension key
**Example:**
```json
"extra": {
"typo3/cms": {
"extension-key": "my_extension"
}
}
```
**Validation:**
```bash
jq -r '.extra."typo3/cms"."extension-key"' composer.json | grep -q . && echo "✅ Extension key defined" || echo "❌ Missing extension-key"
```
---
## Recommended Fields (Professional Extensions)
### authors
**Format:** Array of author objects with name, email, role, homepage
**Example:**
```json
"authors": [
{
"name": "Developer Name",
"email": "developer@company.com",
"role": "Developer",
"homepage": "https://www.company.com/"
}
]
```
**Required Sub-Fields:**
| Field | Format | Purpose |
|-------|--------|---------|
| `name` | String | Developer's full name |
| `email` | Email address | Contact email |
| `role` | String | `Developer`, `Maintainer`, `Lead Developer` |
| `homepage` | URL | Company or personal website |
**Validation:**
```bash
# Check authors array exists
jq -r '.authors' composer.json | grep -q "name" && echo "✅ Has authors" || echo "⚠️ Missing authors"
# Check authors have email
jq -r '.authors[].email' composer.json | grep -q "@" && echo "✅ Has author emails" || echo "⚠️ Missing author emails"
# Check authors have homepage
jq -r '.authors[].homepage' composer.json | grep -q "http" && echo "✅ Has author homepage" || echo "⚠️ Missing author homepage"
```
### homepage
**Format:** URL to project repository or documentation
**Example:**
```json
"homepage": "https://github.com/vendor/extension-name"
```
**Validation:**
```bash
jq -r '.homepage' composer.json | grep -qE "^https?://" && echo "✅ Has homepage" || echo "⚠️ Missing homepage"
```
### support
**Format:** Object with support channels
**Example:**
```json
"support": {
"issues": "https://github.com/vendor/extension/issues",
"source": "https://github.com/vendor/extension"
}
```
**Validation:**
```bash
jq -r '.support.issues' composer.json | grep -q "http" && echo "✅ Has issues URL" || echo "⚠️ Missing issues URL"
```
### keywords
**Format:** Array of relevant keywords for discoverability
**Example:**
```json
"keywords": [
"TYPO3",
"extension",
"content",
"management"
]
```
**Validation:**
```bash
jq -r '.keywords | length' composer.json | grep -qE '^[1-9]' && echo "✅ Has keywords" || echo "⚠️ Missing keywords"
```
---
## Complete Required Fields Checklist
**Mandatory (MUST have):**
- [ ] `name` - vendor/package format
- [ ] `type` - must be `typo3-cms-extension`
- [ ] `description` - clear, concise description
- [ ] `license` - SPDX identifier (GPL-2.0-or-later, AGPL-3.0-or-later)
- [ ] `require.typo3/cms-core` - with upper bound constraint
- [ ] `require.php` - PHP version constraint
- [ ] `autoload.psr-4` - mapping to Classes/
- [ ] `extra.typo3/cms.extension-key` - underscored extension key
**Recommended (SHOULD have):**
- [ ] `authors` - with name, email, role, homepage
- [ ] `homepage` - project repository URL
- [ ] `support.issues` - issue tracker URL
- [ ] `keywords` - for discoverability
---
## Deprecated Properties
### replace with typo3-ter vendor
**Status:** DEPRECATED - Legacy TER integration approach
**Detection:**
```bash
jq -r '.replace' composer.json | grep -q "typo3-ter" && echo "⚠️ Deprecated: typo3-ter in replace" || echo "✅ No deprecated replace"
```
### replace with "ext_key": "self.version"
**Status:** DEPRECATED - Legacy dependency specification
**Detection:**
```bash
jq -r '.replace' composer.json | grep -qE '"[a-z_]+": "self.version"' && echo "⚠️ Deprecated: self.version replace" || echo "✅ No self.version"
```
---
## TYPO3 v12-v13 Version Constraints
### Recommended Format
```json
"require": {
"typo3/cms-core": "^12.4 || ^13.4",
"php": "^8.1"
}
```
### PHP Version Constraints
```json
"require": {
"php": "^8.1" // TYPO3 v12: PHP 8.1-8.4
}
```
**Validation:**
```bash
# Check PHP constraint
jq -r '.require.php' composer.json | grep -qE '\^8\.[1-4]' && echo "✅ Valid PHP constraint" || echo "⚠️ Check PHP version"
```
---
## Synchronization with ext_emconf.php
**Critical:** `composer.json` and `ext_emconf.php` must have matching dependency constraints.
**Mapping:**
| composer.json | ext_emconf.php |
|--------------|----------------|
| `require.typo3/cms-core` | `constraints.depends.typo3` |
| `require.php` | `constraints.depends.php` |
| `require.*` | `constraints.depends.*` |
**Example Synchronization:**
composer.json:
```json
"require": {
"typo3/cms-core": "^12.4 || ^13.4",
"php": "^8.1",
"typo3/cms-fluid": "^12.4 || ^13.4"
}
```
ext_emconf.php:
```php
'constraints' => [
'depends' => [
'typo3' => '12.4.0-13.4.99',
'php' => '8.1.0-8.4.99',
'fluid' => '12.4.0-13.4.99',
],
],
```
---
## Complete Validation Script
```bash
#!/bin/bash
# validate-composer.sh
ERRORS=0
echo "=== Composer.json Validation ===="
# Check mandatory fields
jq -r '.name' composer.json > /dev/null 2>&1 || { echo "❌ Missing 'name'"; ((ERRORS++)); }
jq -r '.type' composer.json | grep -q "typo3-cms-extension" || { echo "❌ Wrong or missing 'type'"; ((ERRORS++)); }
jq -r '.description' composer.json | grep -q . || { echo "❌ Missing 'description'"; ((ERRORS++)); }
# Check description is meaningful (>20 chars)
DESC_LEN=$(jq -r '.description | length' composer.json 2>/dev/null)
[[ $DESC_LEN -lt 20 ]] && { echo "⚠️ Description too short (should be >20 chars)"; ((WARNINGS++)); }
# Check typo3/cms-core
jq -r '.require["typo3/cms-core"]' composer.json | grep -q . || { echo "❌ Missing typo3/cms-core"; ((ERRORS++)); }
# Check version constraints have upper bounds
jq -r '.require["typo3/cms-core"]' composer.json | grep -qE '(\^|[0-9]+\.[0-9]+\.[0-9]+-[0-9]+\.[0-9]+\.[0-9]+)' || { echo "⚠️ TYPO3 constraint missing upper bound"; ((ERRORS++)); }
# Check autoload
jq -r '.autoload["psr-4"]' composer.json | grep -q "Classes" || { echo "❌ Missing PSR-4 autoload"; ((ERRORS++)); }
# Check extension-key
jq -r '.extra."typo3/cms"."extension-key"' composer.json | grep -q . || { echo "❌ Missing extension-key"; ((ERRORS++)); }
# Check for deprecated replace
jq -r '.replace' composer.json 2>/dev/null | grep -q "typo3-ter\|self.version" && echo "⚠️ Deprecated replace property found"
echo ""
echo "Validation complete: $ERRORS critical errors"
exit $ERRORS
```
---
## Optional but Recommended Fields
### require-dev
**Purpose:** Development dependencies not needed in production
**Example:**
```json
"require-dev": {
"typo3/coding-standards": "^0.7",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.0"
}
```
### suggest
**Purpose:** Optional packages that enhance functionality
**Example:**
```json
"suggest": {
"typo3/cms-filelist": "For file browser functionality",
"typo3/cms-reactions": "For webhook support"
}
```
---
## Best Practices
1. **Packagist Publication:** Publishing to Packagist makes extensions available in TYPO3 Extension Repository automatically
2. **Documentation Rendering:** `composer.json` is **REQUIRED** for extensions with documentation on docs.typo3.org
3. **Version Constraint Strategy:**
- Use `^` for flexible upper bounds
- Specify both major version ranges for v12/v13 compatibility
- Always include upper bounds (avoid `>=` without upper limit)
4. **Namespace Alignment:** PSR-4 namespace should match vendor/extension structure
5. **Composer Priority:** Composer-based installations prioritize `composer.json` over `ext_emconf.php` for dependency resolution
---
## Common Violations and Fixes
### Missing extra.typo3/cms.extension-key
**Before:**
```json
{
"name": "vendor/my-extension",
"type": "typo3-cms-extension"
}
```
**After:**
```json
{
"name": "vendor/my-extension",
"type": "typo3-cms-extension",
"extra": {
"typo3/cms": {
"extension-key": "my_extension"
}
}
}
```
### Version Constraint Without Upper Bound
**Before:**
```json
"require": {
"typo3/cms-core": ">=12.4"
}
```
**After:**
```json
"require": {
"typo3/cms-core": "^12.4 || ^13.4"
}
```
### Deprecated replace Property
**Before:**
```json
"replace": {
"typo3-ter/my-extension": "self.version"
}
```
**After:**
```json
// Remove replace property entirely
```
---
## Additional Validation Commands
### Check all required dependencies have upper bounds
```bash
jq -r '.require | to_entries[] | select(.value | test(">=") and (test("\\^") | not)) | .key' composer.json
```
### Verify package type
```bash
jq -r '.type' composer.json | grep -q "typo3-cms-extension" && echo "✅" || echo "❌ Wrong package type"
```
### Check PSR-4 namespace format
```bash
jq -r '.autoload["psr-4"] | keys[]' composer.json | grep -E '^[A-Z][a-zA-Z0-9]*\\\\[A-Z][a-zA-Z0-9]*\\\\$' && echo "✅ Valid namespace" || echo "⚠️ Check namespace format"
```
### Validate JSON syntax
```bash
jq . composer.json > /dev/null && echo "✅ Valid JSON" || echo "❌ JSON syntax error"
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,524 @@
# Development Environment Standards
**Purpose:** Validate development environment setup for consistent, reproducible TYPO3 extension development
## Why Development Environment Matters
A properly configured development environment ensures:
-**Consistency** - All developers work with identical PHP/TYPO3/database versions
-**Onboarding** - New contributors can start immediately without complex setup
-**CI/CD Parity** - Local environment matches production/staging
-**Reproducibility** - Bugs are reproducible across all environments
-**Cross-Platform** - Works on macOS, Linux, Windows (WSL)
Without standardized dev environment:
- ❌ "Works on my machine" syndrome
- ❌ Inconsistent PHP/database versions causing bugs
- ❌ Complex setup discourages contributions
- ❌ CI failures that don't reproduce locally
## TYPO3 Community Standards
### DDEV - Primary Recommendation
**DDEV** is the **de facto standard** for TYPO3 development:
- ✅ Official TYPO3 core development uses DDEV
- ✅ TYPO3 Best Practices (Tea extension) uses DDEV
- ✅ TYPO3 documentation recommends DDEV
- ✅ Cross-platform support (Docker-based)
- ✅ Preconfigured for TYPO3 (`ddev config --project-type=typo3`)
**Alternative:** Docker Compose (acceptable, more manual configuration)
## Validation Checklist
### 1. DDEV Configuration
**Check for `.ddev/` directory:**
```bash
ls -la .ddev/
```
**Required files:**
- `.ddev/config.yaml` - Core DDEV configuration
- `.ddev/.gitignore` - Excludes dynamic files (import-db, .ddev-docker-compose-*.yaml)
**Optional but recommended:**
- `.ddev/config.typo3.yaml` - TYPO3-specific settings
- `.ddev/commands/` - Custom DDEV commands
- `.ddev/docker-compose.*.yaml` - Additional services
**Severity if missing:** 🟡 **Medium** - Indicates no standardized dev environment
### 2. DDEV config.yaml Structure
**Minimum DDEV Configuration:**
```yaml
name: extension-name
type: typo3
docroot: .Build/public
php_version: "8.2" # Match composer.json minimum
webserver_type: nginx-fpm
router_http_port: "80"
router_https_port: "443"
xdebug_enabled: false
additional_hostnames: []
additional_fqdns: []
database:
type: mariadb
version: "10.11"
omit_containers: [ddev-ssh-agent]
```
**Validation Rules:**
| Field | Validation | Example | Severity |
|-------|-----------|---------|----------|
| `name` | Should match extension key or composer name | `rte-ckeditor-image` | Low |
| `type` | Must be `typo3` | `typo3` | High |
| `docroot` | Should match composer.json web-dir | `.Build/public` | High |
| `php_version` | Should match composer.json minimum PHP | `"8.2"` | High |
| `database.type` | Should be `mariadb` (TYPO3 standard) | `mariadb` | Medium |
| `database.version` | Should be LTS version (10.11 or 11.x) | `"10.11"` | Medium |
**Example Check:**
```bash
# Extension composer.json
"require": {
"php": "^8.2 || ^8.3 || ^8.4",
"typo3/cms-core": "^13.4"
}
"extra": {
"typo3/cms": {
"web-dir": ".Build/public"
}
}
# DDEV config.yaml SHOULD have:
php_version: "8.2" # ✅ Matches minimum
docroot: .Build/public # ✅ Matches web-dir
type: typo3 # ✅ Correct type
# DDEV config.yaml SHOULD NOT have:
php_version: "7.4" # ❌ Below minimum
docroot: public # ❌ Doesn't match web-dir
type: php # ❌ Wrong type
```
### 3. Docker Compose (Alternative)
If DDEV not present, check for `docker-compose.yml`:
**Minimum Docker Compose Configuration:**
```yaml
version: '3.8'
services:
web:
image: ghcr.io/typo3/core-testing-php82:latest
volumes:
- .:/var/www/html
working_dir: /var/www/html
ports:
- "8000:80"
environment:
TYPO3_CONTEXT: Development
db:
image: mariadb:10.11
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: typo3
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
```
**Validation Rules:**
| Service | Validation | Severity |
|---------|-----------|----------|
| `web` service exists | Required | High |
| PHP version matches composer.json | Required | High |
| `db` service exists | Required | Medium |
| Database type is MariaDB/MySQL | Recommended | Low |
| Volumes preserve database data | Required | High |
**Severity if missing:** 🟡 **Medium** - Harder to onboard, but not critical
### 4. DevContainer (VS Code Remote Containers)
Check for `.devcontainer/devcontainer.json`:
**Example DevContainer Configuration:**
```json
{
"name": "TYPO3 Extension Development",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "web",
"workspaceFolder": "/var/www/html",
"customizations": {
"vscode": {
"extensions": [
"bmewburn.vscode-intelephense-client",
"xdebug.php-debug",
"EditorConfig.EditorConfig"
],
"settings": {
"php.validate.executablePath": "/usr/local/bin/php"
}
}
},
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {},
"ghcr.io/devcontainers/features/node:1": {}
}
}
```
**Validation:**
- File exists: ✅ Good (VS Code support)
- References docker-compose.yml or DDEV: ✅ Integrated approach
- Empty directory: ⚠️ Incomplete setup
**Severity if missing:** 🟢 **Low** - Nice to have, not required
## DDEV-Specific Best Practices
### TYPO3-Optimized Settings
**`.ddev/config.typo3.yaml`:**
```yaml
# TYPO3-specific DDEV configuration
override_config: false
web_extra_daemons:
- name: "typo3-backend-lock-handler"
command: "/var/www/html/.Build/bin/typo3 scheduler:run"
directory: /var/www/html
hooks:
post-start:
- exec: composer install
- exec: .Build/bin/typo3 cache:flush
# Additional PHP settings for TYPO3
php_ini:
memory_limit: 512M
max_execution_time: 240
upload_max_filesize: 32M
post_max_size: 32M
```
### Custom DDEV Commands
**`.ddev/commands/web/typo3`:**
```bash
#!/bin/bash
## Description: Run TYPO3 CLI commands
## Usage: typo3 [args]
## Example: "ddev typo3 cache:flush"
.Build/bin/typo3 "$@"
```
**`.ddev/commands/web/test-unit`:**
```bash
#!/bin/bash
## Description: Run unit tests
## Usage: test-unit [args]
.Build/bin/phpunit -c Build/phpunit/UnitTests.xml "$@"
```
**`.ddev/commands/web/test-functional`:**
```bash
#!/bin/bash
## Description: Run functional tests
## Usage: test-functional [args]
.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml "$@"
```
## Conformance Evaluation Workflow
### Step 1: Detect Development Environment Type
```bash
# Check for DDEV
if [ -f ".ddev/config.yaml" ]; then
DEV_ENV="ddev"
SCORE=20 # Full points for DDEV
# Check for Docker Compose
elif [ -f "docker-compose.yml" ]; then
DEV_ENV="docker-compose"
SCORE=15 # Good, but manual
# Check for DevContainer only
elif [ -f ".devcontainer/devcontainer.json" ]; then
DEV_ENV="devcontainer"
SCORE=10 # VS Code specific
# No dev environment
else
DEV_ENV="none"
SCORE=0
fi
```
### Step 2: Validate Configuration Against Extension
**For DDEV:**
```bash
# Extract extension requirements
MIN_PHP=$(jq -r '.require.php' composer.json | grep -oE '[0-9]+\.[0-9]+' | head -1)
WEB_DIR=$(jq -r '.extra.typo3.cms."web-dir"' composer.json)
# Validate DDEV config
DDEV_PHP=$(grep 'php_version:' .ddev/config.yaml | awk '{print $2}' | tr -d '"')
DDEV_DOCROOT=$(grep 'docroot:' .ddev/config.yaml | awk '{print $2}')
DDEV_TYPE=$(grep 'type:' .ddev/config.yaml | awk '{print $2}')
# Compare
if [ "${DDEV_PHP}" != "${MIN_PHP}" ]; then
echo "⚠️ PHP version mismatch: DDEV ${DDEV_PHP} vs required ${MIN_PHP}"
fi
if [ "${DDEV_DOCROOT}" != "${WEB_DIR}" ]; then
echo "⚠️ Docroot mismatch: DDEV ${DDEV_DOCROOT} vs composer ${WEB_DIR}"
fi
if [ "${DDEV_TYPE}" != "typo3" ]; then
echo "❌ DDEV type should be 'typo3', found '${DDEV_TYPE}'"
fi
```
### Step 3: Check for Recommended Enhancements
```bash
# DDEV commands
if [ -d ".ddev/commands/web" ]; then
COMMANDS=$(ls .ddev/commands/web/ 2>/dev/null | wc -l)
echo "✅ DDEV has ${COMMANDS} custom commands"
else
echo " No custom DDEV commands (consider adding typo3, test-unit, test-functional)"
fi
# TYPO3-specific config
if [ -f ".ddev/config.typo3.yaml" ]; then
echo "✅ TYPO3-specific DDEV configuration present"
else
echo " No TYPO3-specific config (optional)"
fi
```
## Conformance Report Integration
### When Evaluating Development Environment:
**In "Best Practices" Section:**
```markdown
### Development Environment
**Configuration:**
- ✅ DDEV configured (.ddev/config.yaml present)
- ✅ PHP version matches composer.json minimum (8.2)
- ✅ Docroot matches composer.json web-dir (.Build/public)
- ✅ Type set to 'typo3' for TYPO3-optimized setup
- ✅ MariaDB 10.11 (LTS) configured
- ✅ Custom DDEV commands for testing (test-unit, test-functional)
- Optional: TYPO3-specific config (.ddev/config.typo3.yaml) could enhance setup
**Or with issues:**
- ❌ No development environment configuration
- Missing: .ddev/config.yaml, docker-compose.yml
- Impact: Inconsistent development environments, difficult onboarding
- Severity: Medium
- Recommendation: Add DDEV configuration from Tea extension pattern
- Reference: https://github.com/TYPO3BestPractices/tea/tree/main/.ddev
- ⚠️ DDEV PHP version mismatch
- File: .ddev/config.yaml
- Current: php_version: "7.4"
- Expected: php_version: "8.2" (from composer.json)
- Severity: High
- Fix: Update php_version to match minimum requirement
- ⚠️ DDEV docroot mismatch
- File: .ddev/config.yaml
- Current: docroot: public
- Expected: docroot: .Build/public (from composer.json extra.typo3.cms.web-dir)
- Severity: High
- Fix: Update docroot to match web-dir
```
## Scoring Impact
**Best Practices Score Components (out of 20):**
| Component | Max Points | DDEV | Docker Compose | None |
|-----------|-----------|------|----------------|------|
| **Dev Environment Exists** | 6 | 6 | 4 | 0 |
| **Configuration Correct** | 4 | 4 | 3 | 0 |
| **Version Matching** | 3 | 3 | 2 | 0 |
| **Documentation** | 2 | 2 | 1 | 0 |
| **Custom Commands/Enhancements** | 2 | 2 | 0 | 0 |
| **Other Best Practices** | 3 | 3 | 3 | 3 |
| **Total** | 20 | 20 | 13 | 3 |
**Deductions:**
| Issue | Severity | Score Impact |
|-------|----------|--------------|
| No dev environment at all | High | -6 points |
| PHP version mismatch | High | -3 points |
| Docroot mismatch | High | -3 points |
| Wrong type (not 'typo3') | Medium | -2 points |
| Missing custom commands | Low | -1 point |
| No documentation | Low | -1 point |
## Tea Extension Reference
**Source:** https://github.com/TYPO3BestPractices/tea/tree/main/.ddev
**Tea DDEV Structure:**
```
.ddev/
├── .gitignore
├── config.yaml # Main configuration
├── config.typo3.yaml # TYPO3-specific settings
└── commands/
└── web/
├── typo3 # TYPO3 CLI wrapper
├── test-unit # Run unit tests
└── test-functional # Run functional tests
```
**Tea config.yaml (simplified):**
```yaml
name: tea
type: typo3
docroot: .Build/public
php_version: "8.2"
webserver_type: nginx-fpm
database:
type: mariadb
version: "10.11"
xdebug_enabled: false
```
**Usage Examples:**
```bash
# Start DDEV
ddev start
# Install dependencies
ddev composer install
# Run TYPO3 CLI
ddev typo3 cache:flush
# Run unit tests
ddev test-unit
# Run functional tests
ddev test-functional
# Access database
ddev mysql
# SSH into container
ddev ssh
```
## Quick Reference Checklist
**When evaluating development environment:**
```
□ .ddev/config.yaml exists (preferred)
□ OR docker-compose.yml exists (acceptable)
□ OR .devcontainer/devcontainer.json exists (VS Code only)
□ Configuration type is 'typo3' (DDEV) or uses TYPO3 image (Docker Compose)
□ PHP version matches composer.json minimum
□ Docroot matches composer.json web-dir
□ Database is MariaDB 10.11+ or MySQL 8.0+
□ Custom commands for common tasks (DDEV)
□ Documentation exists (README.md mentions DDEV/Docker setup)
□ .ddev/.gitignore present (excludes dynamic files)
□ Post-start hooks run composer install (optional but nice)
```
## Common Issues
### Issue: Empty .devcontainer/
**Diagnosis:**
```bash
ls -la .devcontainer/
# total 8
# drwxr-sr-x 2 user user 4096 Oct 20 20:05 .
```
**Severity:** 🟢 Low (incomplete setup, doesn't help or hurt)
**Fix:** Either populate with devcontainer.json or remove directory
### Issue: DDEV but no .gitignore
**Diagnosis:**
```bash
ls -la .ddev/.gitignore
# No such file or directory
```
**Problem:** DDEV generates dynamic files that shouldn't be committed
**Fix:** Create `.ddev/.gitignore`:
```
/*.yaml
.ddev-docker-compose-*.yaml
.homeadditions
.sshimagename
commands/web/.ddev-docker-compose-*.yaml
import-db/
```
### Issue: Wrong DDEV project type
**Diagnosis:**
```yaml
# .ddev/config.yaml
type: php # ❌ Wrong
```
**Problem:** Misses TYPO3-specific optimizations (URL structure, etc.)
**Fix:** Change to `type: typo3`
## Resources
- **DDEV Documentation:** https://ddev.readthedocs.io/
- **DDEV TYPO3 Quickstart:** https://ddev.readthedocs.io/en/stable/users/quickstart/#typo3
- **Tea Extension DDEV Setup:** https://github.com/TYPO3BestPractices/tea/tree/main/.ddev
- **TYPO3 Docker Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/LocalDevelopment/

View File

@@ -0,0 +1,621 @@
# Directory Structure Standards
**Purpose:** Validate proper separation of committed configuration vs generated/temporary files in TYPO3 extensions
## Why Directory Structure Matters
Proper directory organization prevents common issues:
-**Version Control Hygiene** - Only configuration committed, not generated files
-**Build Reproducibility** - Clean installs work without artifacts
-**CI/CD Clarity** - Clear separation of what's tracked vs generated
-**Onboarding Efficiency** - New contributors understand structure instantly
-**Maintenance Simplicity** - Updates don't conflict with local artifacts
Without proper structure:
- ❌ Bloated repositories with vendor/, cache files committed
- ❌ Git conflicts in generated files
- ❌ CI failures from missing .gitignore patterns
- ❌ Confusion about what files are source vs generated
## TYPO3 Standard Directory Pattern
### Build/ - Committed Configuration
**Purpose:** Project-specific configuration files that define how to build, test, and validate code
**Characteristics:**
- ✅ Committed to git
- ✅ Shared across all developers
- ✅ Version controlled
- ✅ Defines project standards
**Standard Contents:**
```
Build/
├── phpstan.neon # Static analysis config
├── phpstan-baseline.neon # Known issues baseline (optional)
├── php-cs-fixer.php # Code style config
├── rector.php # Refactoring rules (optional, can be in Build/rector/)
├── phpunit/
│ ├── UnitTests.xml # Unit test configuration
│ ├── FunctionalTests.xml # Functional test configuration
│ └── bootstrap.php # Test bootstrap (if needed)
└── Scripts/
└── runTests.sh # Test orchestration script
```
**Alternative Rector Location:**
```
Build/
└── rector/
└── rector.php # Rector config in subdirectory
```
**Git Status:** All files tracked (not in .gitignore)
### .Build/ - Generated/Temporary Files
**Purpose:** Composer-generated files, caches, runtime artifacts, test outputs
**Characteristics:**
- ❌ NOT committed to git (gitignored)
- ✅ Generated by composer/build tools
- ✅ Recreatable from configuration
- ✅ Developer-specific caches allowed
**Standard Contents:**
```
.Build/
├── bin/ # Composer bin-dir (phpunit, phpstan, etc.)
├── public/ # Web root (TYPO3 web-dir)
│ ├── index.php
│ ├── typo3/
│ └── typo3conf/
├── vendor/ # Composer dependencies
├── .php-cs-fixer.cache # php-cs-fixer cache file
├── .phpunit.result.cache # PHPUnit cache
└── var/ # Runtime cache/logs (optional)
```
**Git Status:** Entire directory in .gitignore
**Standard .gitignore Entry:**
```gitignore
# Composer generated files
.Build/
```
## Validation Checklist
### 1. Build/ Directory Structure
**Check for committed configuration files:**
```bash
ls -la Build/
```
**Required files for comprehensive quality:**
- `Build/phpstan.neon` - Static analysis configuration
- `Build/php-cs-fixer.php` - Code style configuration
- `Build/phpunit/UnitTests.xml` - Unit test configuration
- `Build/Scripts/runTests.sh` - Test orchestration
**Optional but recommended:**
- `Build/phpunit/FunctionalTests.xml` - Functional test configuration
- `Build/phpstan-baseline.neon` - Known issues baseline
- `Build/rector.php` or `Build/rector/rector.php` - Refactoring rules
**Severity if missing:** 🟡 **Medium** - Quality tools not standardized
### 2. .Build/ Directory Gitignore
**Check .gitignore for .Build/ exclusion:**
```bash
grep -E '^\\.Build/' .gitignore
```
**Expected pattern:**
```gitignore
.Build/
```
**Alternative patterns (also valid):**
```gitignore
/.Build/
.Build/*
```
**Check for accidental commits:**
```bash
git ls-files .Build/
# Should return empty - no files from .Build/ should be tracked
```
**Severity if misconfigured:** 🔴 **High** - Can lead to repository bloat
### 3. Composer Configuration Alignment
**Validate composer.json paths match directory structure:**
```bash
# Extract composer paths
cat composer.json | jq -r '.config."bin-dir"' # Should be .Build/bin
cat composer.json | jq -r '.config."vendor-dir"' # Should be .Build/vendor
cat composer.json | jq -r '.extra.typo3.cms."web-dir"' # Should be .Build/public
```
**Expected composer.json:**
```json
{
"config": {
"vendor-dir": ".Build/vendor",
"bin-dir": ".Build/bin"
},
"extra": {
"typo3/cms": {
"web-dir": ".Build/public"
}
}
}
```
**Severity if mismatched:** 🔴 **High** - Build will fail or create wrong structure
### 4. Quality Tool Configuration Paths
**Validate tool configs reference correct cache locations:**
```bash
# Check PHPUnit cache location
grep -r "\.phpunit\.result\.cache" Build/phpunit/*.xml
# Should reference .Build/.phpunit.result.cache or no cache directive
# Check php-cs-fixer cache
grep -r "cache-file" composer.json Build/php-cs-fixer.php
# Should reference .Build/.php-cs-fixer.cache if specified
# Check PHPStan cache (usually auto-managed)
grep -r "tmpDir" Build/phpstan.neon
# Should reference .Build/phpstan if specified, or auto temp
```
**Example CORRECT patterns:**
```json
// composer.json
{
"scripts": {
"ci:cgl": [
"php-cs-fixer fix --cache-file .Build/.php-cs-fixer.cache --dry-run --diff"
]
}
}
```
```php
// Build/php-cs-fixer.php
return (new PhpCsFixer\Config())
->setCacheFile('.Build/.php-cs-fixer.cache')
->setRules([...]);
```
```neon
# Build/phpstan.neon
parameters:
tmpDir: .Build/phpstan
```
**Severity if wrong:** 🟡 **Medium** - Works but creates clutter in Build/
### 5. Tea Extension Reference
**Source:** https://github.com/TYPO3BestPractices/tea
**Tea Directory Structure:**
```
tea/
├── .gitignore # Excludes .Build/
├── Build/
│ ├── phpstan.neon
│ ├── phpstan-baseline.neon
│ ├── php-cs-fixer.php
│ ├── phpunit/
│ │ ├── FunctionalTests.xml
│ │ └── UnitTests.xml
│ └── Scripts/
│ └── runTests.sh
├── .Build/ # Gitignored, generated by composer
│ ├── bin/
│ ├── public/
│ └── vendor/
└── composer.json # Defines .Build/ paths
```
**Tea .gitignore (relevant excerpt):**
```gitignore
# Composer-generated files
.Build/
composer.lock
```
**Tea composer.json (relevant excerpt):**
```json
{
"config": {
"vendor-dir": ".Build/vendor",
"bin-dir": ".Build/bin"
},
"extra": {
"typo3/cms": {
"web-dir": ".Build/public"
}
}
}
```
## Common Issues and Fixes
### Issue 1: Cache Files in Build/ Directory
**Diagnosis:**
```bash
ls -la Build/.php-cs-fixer.cache Build/.phpunit.result.cache
# If these files exist, they're in the WRONG location
```
**Problem:** Cache files committed or in wrong directory
**Fix:**
```bash
# Remove from wrong location
rm Build/.php-cs-fixer.cache Build/.phpunit.result.cache
# Update configuration to use .Build/
```
**Update php-cs-fixer config:**
```php
// Build/php-cs-fixer.php
return (new PhpCsFixer\Config())
->setCacheFile('.Build/.php-cs-fixer.cache') // ✅ Correct location
// ...
```
**Update composer scripts:**
```json
{
"scripts": {
"ci:cgl": [
"php-cs-fixer fix --cache-file .Build/.php-cs-fixer.cache --dry-run --diff"
]
}
}
```
### Issue 2: .Build/ Files Committed to Git
**Diagnosis:**
```bash
git ls-files .Build/
# Should be empty
```
**Problem:** Generated files tracked in git (vendor/, bin/, etc.)
**Fix:**
```bash
# Remove from git tracking
git rm -r --cached .Build/
# Ensure .gitignore has entry
echo ".Build/" >> .gitignore
# Commit the cleanup
git add .gitignore
git commit -m "fix: remove .Build/ from git tracking, add to .gitignore"
```
### Issue 3: Missing Build/ Directory
**Diagnosis:**
```bash
ls -la Build/
# Directory doesn't exist
```
**Problem:** No standardized quality tool configuration
**Fix:**
```bash
# Create Build/ directory structure
mkdir -p Build/{phpunit,Scripts}
# Add quality tool configs (see templates below)
# Then commit
git add Build/
git commit -m "feat: add Build/ directory with quality tool configurations"
```
### Issue 4: Rector in Wrong Location
**Diagnosis:**
```bash
# Check for rector.php in project root
ls -la rector.php
# Should be in Build/ or Build/rector/ instead
```
**Problem:** Configuration file in project root instead of Build/
**Fix:**
```bash
# Option 1: Move to Build/
mv rector.php Build/rector.php
# Option 2: Move to Build/rector/ (preferred for complex configs)
mkdir -p Build/rector
mv rector.php Build/rector/rector.php
# Update paths in rector.php
# Then commit
git add Build/
git rm rector.php
git commit -m "refactor: move rector config to Build/ directory"
```
## Conformance Report Integration
### When Evaluating Directory Structure:
**In "Best Practices" Section:**
```markdown
### Directory Structure
**Analysis:**
- ✅ Build/ directory present with committed configurations
- ✅ Build/phpstan.neon, Build/php-cs-fixer.php present
- ✅ Build/phpunit/ directory with test configs
- ✅ Build/Scripts/runTests.sh present
- ✅ .Build/ properly gitignored (entire directory)
- ✅ Composer paths correctly reference .Build/
- ✅ Cache files located in .Build/, not Build/
- ✅ No .Build/ files committed to git
**Or with issues:**
- ❌ Cache files in wrong location
- Files: Build/.php-cs-fixer.cache, Build/.phpunit.result.cache
- Expected: .Build/.php-cs-fixer.cache, .Build/.phpunit.result.cache
- Severity: Medium
- Fix: Move cache files to .Build/ and update configs
- ❌ .Build/ files committed to git
- Files: .Build/vendor/, .Build/bin/
- Command: `git ls-files .Build/` shows tracked files
- Severity: High
- Fix: `git rm -r --cached .Build/` and ensure .gitignore has `.Build/`
- ⚠️ Missing Build/ directory
- Impact: No standardized quality tool configuration
- Severity: Medium
- Recommendation: Create Build/ with phpstan.neon, php-cs-fixer.php, phpunit configs
```
## Scoring Impact
**Best Practices Score Deductions:**
| Issue | Severity | Score Impact |
|-------|----------|--------------|
| .Build/ files committed | High | -4 points |
| Cache files in Build/ | Medium | -2 points |
| Missing .gitignore for .Build/ | High | -3 points |
| Composer paths don't match structure | High | -3 points |
| Missing Build/ directory | Medium | -2 points |
| Rector/configs in project root | Low | -1 point |
**Maximum deduction for directory issues:** -6 points (out of 20 for Best Practices)
## Automated Validation Script
Create `scripts/validate-directory-structure.sh`:
```bash
#!/bin/bash
set -e
echo "🔍 Validating directory structure..."
ISSUES=0
# Check 1: .Build/ should be gitignored
if ! grep -qE '^\\.Build/' .gitignore; then
echo "❌ .gitignore missing '.Build/' entry"
ISSUES=$((ISSUES + 1))
else
echo "✅ .Build/ properly gitignored"
fi
# Check 2: No .Build/ files should be tracked
TRACKED_BUILD=$(git ls-files .Build/ 2>/dev/null | wc -l)
if [ "${TRACKED_BUILD}" -gt 0 ]; then
echo "❌ .Build/ files are committed to git:"
git ls-files .Build/
ISSUES=$((ISSUES + 1))
else
echo "✅ No .Build/ files tracked in git"
fi
# Check 3: Build/ directory should exist
if [ ! -d "Build" ]; then
echo "⚠️ Build/ directory missing"
ISSUES=$((ISSUES + 1))
else
echo "✅ Build/ directory exists"
# Check for standard files
[ -f "Build/phpstan.neon" ] && echo " ✅ phpstan.neon" || echo " ⚠️ phpstan.neon missing"
[ -f "Build/php-cs-fixer.php" ] && echo " ✅ php-cs-fixer.php" || echo " ⚠️ php-cs-fixer.php missing"
[ -d "Build/phpunit" ] && echo " ✅ phpunit/" || echo " ⚠️ phpunit/ missing"
[ -f "Build/Scripts/runTests.sh" ] && echo " ✅ runTests.sh" || echo " ⚠️ runTests.sh missing"
fi
# Check 4: Cache files should NOT be in Build/
if [ -f "Build/.php-cs-fixer.cache" ] || [ -f "Build/.phpunit.result.cache" ]; then
echo "❌ Cache files in wrong location (Build/ instead of .Build/):"
ls -la Build/.*.cache 2>/dev/null || true
ISSUES=$((ISSUES + 1))
else
echo "✅ No cache files in Build/"
fi
# Check 5: Composer paths should reference .Build/
BIN_DIR=$(jq -r '.config."bin-dir" // ".Build/bin"' composer.json)
VENDOR_DIR=$(jq -r '.config."vendor-dir" // ".Build/vendor"' composer.json)
WEB_DIR=$(jq -r '.extra.typo3.cms."web-dir" // ".Build/public"' composer.json)
if [ "${BIN_DIR}" = ".Build/bin" ]; then
echo "✅ Composer bin-dir: ${BIN_DIR}"
else
echo "⚠️ Composer bin-dir: ${BIN_DIR} (expected .Build/bin)"
ISSUES=$((ISSUES + 1))
fi
if [ "${VENDOR_DIR}" = ".Build/vendor" ]; then
echo "✅ Composer vendor-dir: ${VENDOR_DIR}"
else
echo "⚠️ Composer vendor-dir: ${VENDOR_DIR} (expected .Build/vendor)"
ISSUES=$((ISSUES + 1))
fi
if [ "${WEB_DIR}" = ".Build/public" ]; then
echo "✅ TYPO3 web-dir: ${WEB_DIR}"
else
echo "⚠️ TYPO3 web-dir: ${WEB_DIR} (expected .Build/public)"
ISSUES=$((ISSUES + 1))
fi
echo ""
if [ ${ISSUES} -eq 0 ]; then
echo "✅ Directory structure validation complete - no issues found"
exit 0
else
echo "❌ Directory structure validation found ${ISSUES} issue(s)"
exit 1
fi
```
## Quick Reference Checklist
**When evaluating directory structure:**
```
□ .gitignore contains .Build/ entry
□ Build/ directory exists and contains configs
□ Build/phpstan.neon exists
□ Build/php-cs-fixer.php exists
□ Build/phpunit/ directory exists with XML configs
□ Build/Scripts/runTests.sh exists
□ .Build/ is NOT tracked in git (git ls-files .Build/ is empty)
□ Cache files are in .Build/, not Build/
□ Composer bin-dir = .Build/bin
□ Composer vendor-dir = .Build/vendor
□ TYPO3 web-dir = .Build/public
□ No configuration files in project root (rector.php, phpstan.neon, etc.)
```
## Configuration File Templates
### Build/phpstan.neon
```neon
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
- vendor/saschaegerer/phpstan-typo3/extension.neon
parameters:
level: max
paths:
- Classes
- Tests
tmpDir: .Build/phpstan
reportUnmatchedIgnoredErrors: true
```
### Build/php-cs-fixer.php
```php
<?php
declare(strict_types=1);
$finder = (new PhpCsFixer\Finder())
->in(__DIR__ . '/../Classes')
->in(__DIR__ . '/../Tests');
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'@PhpCsFixer' => true,
'declare_strict_types' => true,
])
->setCacheFile('.Build/.php-cs-fixer.cache')
->setRiskyAllowed(true)
->setFinder($finder);
```
### Build/rector/rector.php
```php
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\ValueObject\PhpVersion;
use Ssch\TYPO3Rector\Set\Typo3LevelSetList;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/../../Classes/',
__DIR__ . '/../../Tests/',
])
->withPhpVersion(PhpVersion::PHP_82)
->withPhpSets(true)
->withSets([
Typo3LevelSetList::UP_TO_TYPO3_13,
]);
```
## Resources
- **Tea Extension Structure:** https://github.com/TYPO3BestPractices/tea
- **Composer Documentation:** https://getcomposer.org/doc/06-config.md
- **TYPO3 Extension Structure:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/FileStructure/

View File

@@ -0,0 +1,712 @@
# Excellence Indicators Reference
**Purpose:** Document optional features that indicate exceptional TYPO3 extension quality beyond basic conformance
## Overview
Excellence Indicators are **optional** features that demonstrate exceptional project quality, community engagement, and professional development practices. Extensions are **not penalized** for missing these features, but **earn bonus points** when present.
**Key Principle:** Base conformance (0-100 points) measures adherence to TYPO3 standards. Excellence indicators (0-20 bonus points) reward exceptional quality.
---
## Scoring System
**Total Excellence Points: 0-20 (bonus)**
| Category | Max Points | Purpose |
|----------|-----------|---------|
| Community & Internationalization | 6 | Engagement, accessibility, distribution |
| Advanced Quality Tooling | 7 | Automation, code quality, maintenance |
| Documentation Excellence | 4 | Comprehensive docs, modern tooling |
| Extension Configuration | 3 | Professional setup, flexibility |
---
## Category 1: Community & Internationalization (0-6 points)
### 1.1 Crowdin Integration (+2 points)
**File:** `crowdin.yml`
**Purpose:** Community-driven translation management platform integration
**Example (georgringer/news):**
```yaml
files:
- source: /Resources/Private/Language/locallang*.xlf
translation: /Resources/Private/Language/%two_letters_code%.%original_file_name%
```
**Benefits:**
- Enables community translators to contribute
- Automated translation synchronization
- Professional multilingual support
- Reduces maintenance burden for translations
**Validation:**
```bash
[ -f "crowdin.yml" ] && echo "✅ Crowdin integration (+2)"
```
**Reference:** [Crowdin TYPO3 Integration](https://crowdin.com/)
---
### 1.2 GitHub Issue Templates (+1 point)
**Files:** `.github/ISSUE_TEMPLATE/`
- `Bug_report.md`
- `Feature_request.md`
- `Support_question.md`
**Purpose:** Structured community contribution and issue reporting
**Example (georgringer/news):**
```markdown
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
...
```
**Benefits:**
- Ensures complete bug reports
- Reduces back-and-forth communication
- Categorizes issues automatically
- Professional project impression
**Validation:**
```bash
ls -1 .github/ISSUE_TEMPLATE/*.md 2>/dev/null | wc -l
# 3 files = +1 point
```
---
### 1.3 .gitattributes Export Optimization (+1 point)
**File:** `.gitattributes`
**Purpose:** Reduce TER (TYPO3 Extension Repository) package size by excluding development files
**Example (georgringer/news):**
```gitattributes
/.github/ export-ignore
/Build/ export-ignore
/Tests/ export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.styleci.yml export-ignore
/Makefile export-ignore
```
**Benefits:**
- Smaller download size for production installations
- Faster `composer install` in production
- Professional package distribution
- Security (doesn't ship development files)
**Validation:**
```bash
grep -q "export-ignore" .gitattributes && echo "✅ Export optimization (+1)"
```
**Impact Example:**
- Repository size: 15 MB (with tests, CI configs)
- TER package size: 2 MB (production files only)
- **Reduction:** ~87%
---
### 1.4 Professional README with Badges (+2 points)
**File:** `README.md`
**Purpose:** Comprehensive project overview with status indicators
**Required Elements (all 4 required for points):**
1. Stability badge (Packagist or TER)
2. CI/Build status badge (GitHub Actions, GitLab CI)
3. Download stats (Packagist downloads)
4. Compatibility matrix table
**Example (georgringer/news):**
```markdown
[![Latest Stable Version](https://poser.pugx.org/georgringer/news/v/stable)](https://extensions.typo3.org/extension/news/)
[![TYPO3 12](https://img.shields.io/badge/TYPO3-12-orange.svg)](https://get.typo3.org/version/12)
[![TYPO3 13](https://img.shields.io/badge/TYPO3-13-orange.svg)](https://get.typo3.org/version/13)
[![Total Downloads](https://poser.pugx.org/georgringer/news/d/total)](https://packagist.org/packages/georgringer/news)
![Build v12](https://github.com/georgringer/news/actions/workflows/core12.yml/badge.svg)
[![Crowdin](https://badges.crowdin.net/typo3-extension-news/localized.svg)](https://crowdin.com/project/typo3-extension-news)
## Compatibility
| News | TYPO3 | PHP | Support / Development |
|------|-----------|-----------|--------------------------------------|
| 12 | 12 - 13 | 8.1 - 8.3 | features, bugfixes, security updates |
| 11 | 11 - 12 | 7.4 - 8.3 | security updates |
```
**Validation:**
```bash
# Check for at least 3 badges and a compatibility table
grep -c "!\[" README.md # Badge count
grep -c "^|" README.md # Table rows
```
---
## Category 2: Advanced Quality Tooling (0-7 points)
### 2.1 Fractor Configuration (+2 points)
**File:** `Build/fractor/fractor.php`
**Purpose:** Automated refactoring for TypoScript and XML configuration files
**What is Fractor?**
- Rector handles PHP code refactoring
- **Fractor handles TypoScript and XML** file refactoring
- Automates TYPO3 configuration migrations
**Example (georgringer/news):**
```php
<?php
declare(strict_types=1);
use a9f\Fractor\Configuration\FractorConfiguration;
use a9f\FractorTypoScript\Configuration\TypoScriptProcessorOption;
use a9f\FractorXml\Configuration\XmlProcessorOption;
use a9f\Typo3Fractor\Set\Typo3LevelSetList;
return FractorConfiguration::configure()
->withPaths([
__DIR__ . '/../../Classes',
__DIR__ . '/../../Configuration/',
__DIR__ . '/../../Resources',
])
->withSets([
Typo3LevelSetList::UP_TO_TYPO3_12,
])
->withOptions([
TypoScriptProcessorOption::INDENT_CHARACTER => 'auto',
XmlProcessorOption::INDENT_CHARACTER => Indent::STYLE_TAB,
]);
```
**Benefits:**
- Automates TypoScript configuration migrations
- Modernizes FlexForm XML structures
- Reduces manual refactoring effort
- Catches TYPO3 API changes in configuration
**Required Packages:**
```json
{
"require-dev": {
"a9f/fractor": "^1.0",
"a9f/typo3-fractor": "^1.0"
}
}
```
**Validation:**
```bash
[ -f "Build/fractor/fractor.php" ] && echo "✅ Fractor configuration (+2)"
```
---
### 2.2 TYPO3 CodingStandards Package (+1 point)
**File:** `Build/php-cs-fixer/php-cs-fixer.php`
**Purpose:** Official TYPO3 community coding standards package (not custom config)
**Example (georgringer/news):**
```php
<?php
use PhpCsFixer\Finder;
use TYPO3\CodingStandards\CsFixerConfig;
$config = CsFixerConfig::create();
$config->setHeader(
'This file is part of the "news" Extension for TYPO3 CMS.
For the full copyright and license information, please read the
LICENSE.txt file that was distributed with this source code.',
true
);
```
**Benefits:**
- Official TYPO3 community standards
- Automatic copyright header injection
- PER Coding Style (PSR-12 successor)
- Consistent with TYPO3 core
**Required Package:**
```json
{
"require-dev": {
"typo3/coding-standards": "^0.5"
}
}
```
**Validation:**
```bash
grep -q "TYPO3\\\\CodingStandards" Build/php-cs-fixer/php-cs-fixer.php && echo "✅ TYPO3 CodingStandards (+1)"
```
**Alternative (no points):** Custom php-cs-fixer config (still good, but not official package)
---
### 2.3 StyleCI Integration (+1 point)
**File:** `.styleci.yml`
**Purpose:** Cloud-based automatic code style checking on pull requests
**Example (georgringer/news):**
```yaml
preset: psr12
enabled:
- no_unused_imports
- ordered_imports
- single_quote
- short_array_syntax
- hash_to_slash_comment
- native_function_casing
finder:
name:
- "*.php"
not-path:
- ".Build"
- "Build/php-cs-fixer"
- "Documentation"
```
**Benefits:**
- Automatic PR code style checks (no local setup needed)
- Visual code review integration
- Reduces reviewer burden
- Enforces consistency across contributors
**Validation:**
```bash
[ -f ".styleci.yml" ] && echo "✅ StyleCI integration (+1)"
```
**Note:** Alternative to local php-cs-fixer CI checks, not replacement
---
### 2.4 Makefile Task Automation (+1 point)
**File:** `Makefile`
**Purpose:** Self-documenting task automation and workflow management
**Example (georgringer/news):**
```makefile
.PHONY: help
help: ## Displays this list of targets with descriptions
@echo "The following commands are available:\n"
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: docs
docs: ## Generate projects docs (from "Documentation" directory)
mkdir -p Documentation-GENERATED-temp
docker run --rm --pull always -v "$(shell pwd)":/project -t ghcr.io/typo3-documentation/render-guides:latest --config=Documentation
```
**Benefits:**
- Discoverable commands (`make help`)
- Consistent workflow across contributors
- Reduces documentation for common tasks
- Docker-based documentation rendering
**Validation:**
```bash
[ -f "Makefile" ] && grep -q "^help:.*##" Makefile && echo "✅ Makefile automation (+1)"
```
---
### 2.5 Comprehensive CI Matrix (+2 points)
**Files:** `.github/workflows/*.yml` or `.gitlab-ci.yml`
**Purpose:** Test across multiple PHP versions and dependency scenarios
**Required for +2 points:**
- At least 3 PHP versions tested
- Both `composerInstallLowest` and `composerInstallHighest` strategies
- Multiple TYPO3 versions if extension supports multiple
**Example (georgringer/news):**
```yaml
name: core 12
on: [ push, pull_request ]
jobs:
tests:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
php: [ '8.1', '8.2', '8.3', '8.4' ] # 4 PHP versions
composerInstall: [ 'composerInstallLowest', 'composerInstallHighest' ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install testing system
run: Build/Scripts/runTests.sh -t 12 -p ${{ matrix.php }} -s ${{ matrix.composerInstall }}
- name: Lint PHP
run: Build/Scripts/runTests.sh -t 12 -p ${{ matrix.php }} -s lint
```
**Benefits:**
- Catches dependency conflicts early
- Ensures compatibility across PHP versions
- Tests minimum and maximum dependency versions
- Professional CI/CD setup
**Validation:**
```bash
# Check for matrix with multiple PHP versions and composerInstall strategies
grep -A 5 "matrix:" .github/workflows/*.yml | grep -c "composerInstall"
```
---
## Category 3: Documentation Excellence (0-4 points)
### 3.1 Extensive RST Documentation (100+ files) (+3 points)
**Directory:** `Documentation/`
**Purpose:** Comprehensive, structured documentation covering all aspects
**Example (georgringer/news: 183 RST files):**
```
Documentation/
├── Addons/ # Extension integrations
├── Administration/ # Backend administration
├── Introduction/ # Getting started
├── QuickStart/ # Fast setup guide
├── Reference/ # API reference
├── Tutorials/ # Step-by-step guides
├── UsersManual/ # End-user documentation
└── Images/ # Visual assets
```
**Scoring:**
- 50-99 RST files: +1 point
- 100-149 RST files: +2 points
- 150+ RST files: +3 points
**Validation:**
```bash
RST_COUNT=$(find Documentation -name "*.rst" | wc -l)
if [ $RST_COUNT -ge 150 ]; then
echo "✅ Extensive documentation 150+ RST (+3)"
elif [ $RST_COUNT -ge 100 ]; then
echo "✅ Comprehensive documentation 100+ RST (+2)"
elif [ $RST_COUNT -ge 50 ]; then
echo "✅ Good documentation 50+ RST (+1)"
fi
```
**Benefits:**
- Reduces support burden
- Improves onboarding
- Professional project impression
- Better community adoption
---
### 3.2 Modern Documentation Tooling (+1 point)
**Files:**
- `Documentation/guides.xml`
- `Documentation/screenshots.json`
**Purpose:** Modern TYPO3 documentation rendering and screenshot management
**Example (georgringer/news):**
```xml
<!-- guides.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<guides xmlns="https://guides.typo3.org/ns/1.0">
<project>
<title>News System</title>
<release>12.0</release>
<vendor>georgringer</vendor>
</project>
</guides>
```
```json
// screenshots.json
{
"screenshots": [
{
"file": "Images/Administration/BackendModule.png",
"caption": "News administration module"
}
]
}
```
**Benefits:**
- Automated documentation rendering
- Screenshot management and regeneration
- Consistent with TYPO3 documentation standards
- Future-proof documentation setup
**Validation:**
```bash
[ -f "Documentation/guides.xml" ] && echo "✅ Modern documentation tooling (+1)"
```
---
## Category 4: Extension Configuration (0-3 points)
### 4.1 Extension Configuration Template (+1 point)
**File:** `ext_conf_template.txt`
**Purpose:** Backend extension configuration interface with categorized settings
**Example (georgringer/news):**
```
# Records
###########################
# cat=records/enable/103; type=boolean; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.prependAtCopy
prependAtCopy = 1
# cat=records/enable/101; type=string; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.tagPid
tagPid = 1
# cat=records/enable/26; type=boolean; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.rteForTeaser
rteForTeaser = 0
# Backend module
# cat=backend module/enable/10; type=boolean; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.showAdministrationModule
showAdministrationModule = 1
```
**Benefits:**
- User-friendly backend configuration
- Categorized settings for clarity
- Localized labels
- No PHP knowledge required for configuration
**Validation:**
```bash
[ -f "ext_conf_template.txt" ] && echo "✅ Extension configuration template (+1)"
```
**Note:** Not required for modern TYPO3 extensions using Site Sets, but still valuable for global extension settings
---
### 4.2 Composer Documentation Scripts (+1 point)
**File:** `composer.json`
**Purpose:** Automated documentation rendering and watching
**Required Scripts (at least 2 of 3):**
- `doc-init` - Initialize documentation rendering
- `doc-make` - Render documentation
- `doc-watch` - Watch and auto-render documentation
**Example (georgringer/news):**
```json
{
"scripts": {
"doc-init": "docker run --rm --pull always -v $(pwd):/project -it ghcr.io/typo3-documentation/render-guides:latest --config=Documentation",
"doc-make": "make docs",
"doc-watch": "docker run --rm -it --pull always -v \"./Documentation:/project/Documentation\" -v \"./Documentation-GENERATED-temp:/project/Documentation-GENERATED-temp\" -p 5173:5173 ghcr.io/garvinhicking/typo3-documentation-browsersync:latest"
},
"scripts-descriptions": {
"doc-init": "Initialize documentation rendering",
"doc-make": "Render documentation",
"doc-watch": "Render documentation including a watcher"
}
}
```
**Benefits:**
- Easy documentation development
- Live preview during writing
- Docker-based (no local dependencies)
- Consistent with TYPO3 documentation workflow
**Validation:**
```bash
grep -q "doc-init.*doc-make" composer.json && echo "✅ Composer doc scripts (+1)"
```
---
### 4.3 Multiple Configuration Sets (TYPO3 13) (+1 point)
**Directory:** `Configuration/Sets/`
**Purpose:** Multiple configuration presets for different use cases
**Required:** At least 2 different Sets (not just one default)
**Example (georgringer/news has 5 Sets):**
```
Configuration/Sets/
├── News/ # Base news functionality
├── RecordLinks/ # Record link handling
├── Sitemap/ # Sitemap generation
├── Twb4/ # Twitter Bootstrap 4 templates
└── Twb5/ # Twitter Bootstrap 5 templates
```
**Benefits:**
- Quick setup for different scenarios
- Reusable configuration patterns
- Modern TYPO3 13 architecture
- Flexible deployment
**Validation:**
```bash
SET_COUNT=$(find Configuration/Sets -mindepth 1 -maxdepth 1 -type d | wc -l)
[ $SET_COUNT -ge 2 ] && echo "✅ Multiple configuration Sets (+1)"
```
---
## Excellence Indicators Conformance Report Format
### Example Report Section
```markdown
## Excellence Indicators (Bonus Score: 12/20)
### ✅ Community & Internationalization (4/6)
- ✅ Crowdin integration (crowdin.yml): +2 points
- ✅ GitHub issue templates (3 templates): +1 point
- ❌ .gitattributes export optimization: 0 points
- ✅ Professional README with badges: +2 points
- Stability badge: ✅
- CI status badge: ✅
- Download stats: ✅
- Compatibility matrix: ✅
### ✅ Advanced Quality Tooling (5/7)
- ✅ Fractor configuration (Build/fractor/fractor.php): +2 points
- ❌ TYPO3 CodingStandards package: 0 points (uses custom config)
- ✅ StyleCI integration (.styleci.yml): +1 point
- ❌ Makefile automation: 0 points
- ✅ Comprehensive CI matrix (4 PHP versions, composerInstallLowest/Highest): +2 points
### ✅ Documentation Excellence (3/4)
- ✅ Extensive documentation (183 RST files): +3 points
- ❌ Modern documentation tooling (guides.xml): 0 points
### ❌ Extension Configuration (0/3)
- ❌ ext_conf_template.txt: 0 points
- ❌ Composer documentation scripts: 0 points
- ❌ Multiple Configuration Sets: 0 points (only 1 Set present)
### Summary
This extension demonstrates exceptional quality in documentation and CI/CD practices. Consider adding:
- .gitattributes with export-ignore for smaller TER packages
- TYPO3 CodingStandards package for official community standards
- Makefile for task automation
- Modern documentation tooling (guides.xml, screenshots.json)
- Extension configuration template for backend settings
```
---
## Quick Reference Validation Checklist
**When evaluating excellence indicators:**
```
Community & Internationalization (0-6):
□ crowdin.yml present (+2)
□ 3 GitHub issue templates (+1)
□ .gitattributes with export-ignore (+1)
□ README with 4+ badges + compatibility table (+2)
Advanced Quality Tooling (0-7):
□ Build/fractor/fractor.php present (+2)
□ TYPO3\CodingStandards in php-cs-fixer config (+1)
□ .styleci.yml present (+1)
□ Makefile with help target (+1)
□ CI matrix: 3+ PHP versions + composerInstall variants (+2)
Documentation Excellence (0-4):
□ 50-99 RST files (+1) / 100-149 (+2) / 150+ (+3)
□ guides.xml + screenshots.json (+1)
Extension Configuration (0-3):
□ ext_conf_template.txt present (+1)
□ Composer doc scripts (doc-init, doc-make, doc-watch) (+1)
□ 2+ Configuration Sets in Configuration/Sets/ (+1)
```
---
## Implementation Notes
**For Conformance Skill:**
1. **Never penalize** missing excellence indicators
2. **Always report** excellence indicators separately from base conformance
3. **Score format:** `Base: 94/100 | Excellence: 12/20 | Total: 106/120`
4. **Optional evaluation:** Can be disabled with flag if user only wants base conformance
**Example CLI:**
```bash
# Full evaluation (base + excellence)
check-conformance --with-excellence
# Base conformance only
check-conformance
# Excellence only (for established extensions)
check-conformance --excellence-only
```
---
## Resources
- **georgringer/news:** https://github.com/georgringer/news (primary reference for excellence patterns)
- **TYPO3 Best Practices (Tea):** https://github.com/TYPO3BestPractices/tea (primary reference for base conformance)
- **Fractor:** https://github.com/andreaswolf/fractor
- **TYPO3 CodingStandards:** https://github.com/TYPO3/coding-standards
- **StyleCI:** https://styleci.io/
- **Crowdin:** https://crowdin.com/

View File

@@ -0,0 +1,610 @@
# ext_emconf.php Validation Standards (TYPO3 v13)
**Source:** TYPO3 Core API Reference v13.4 - FileStructure/ExtEmconf.html
**Purpose:** Complete validation rules for ext_emconf.php including critical TER restrictions
## CRITICAL RESTRICTIONS
### ❌ MUST NOT use declare(strict_types=1)
**CRITICAL:** The TYPO3 Extension Repository (TER) upload **WILL FAIL** if `declare(strict_types=1)` is present in ext_emconf.php.
**WRONG:**
```php
<?php
declare(strict_types=1);
$EM_CONF[$_EXTKEY] = [
// configuration
];
```
**CORRECT:**
```php
<?php
$EM_CONF[$_EXTKEY] = [
// configuration
];
```
**Detection:**
```bash
grep "declare(strict_types" ext_emconf.php && echo "❌ CRITICAL: TER upload will FAIL" || echo "✅ No strict_types"
```
### ✅ MUST use $_EXTKEY variable
Extensions must reference the global `$_EXTKEY` variable, not hardcode the extension key.
**WRONG:**
```php
$EM_CONF['my_extension'] = [
// configuration
];
```
**CORRECT:**
```php
$EM_CONF[$_EXTKEY] = [
// configuration
];
```
**Detection:**
```bash
grep '\$EM_CONF\[$_EXTKEY\]' ext_emconf.php && echo "✅ Uses $_EXTKEY" || echo "❌ Hardcoded key"
```
### ❌ MUST NOT contain custom code
The ext_emconf.php file must only contain the `$EM_CONF` array assignment. No additional functions, classes, or executable code is allowed.
**WRONG:**
```php
<?php
function getVersion() { return '1.0.0'; }
$EM_CONF[$_EXTKEY] = [
'version' => getVersion(),
];
```
**WRONG:**
```php
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension',
];
// Additional initialization code
require_once 'setup.php';
```
**CORRECT:**
```php
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension',
'version' => '1.0.0',
];
```
**Detection:**
```bash
# Check for function/class definitions
grep -E '^(function|class|interface|trait|require|include)' ext_emconf.php && echo "❌ Contains custom code" || echo "✅ No custom code"
```
---
## Mandatory Fields
### title
**Format:** English extension name
**Example:**
```php
'title' => 'My Extension',
```
**Validation:**
```bash
grep "'title' =>" ext_emconf.php && echo "✅ Has title" || echo "❌ Missing title"
```
### description
**Format:** Short, precise English description of extension functionality
**Requirements:**
- Clear explanation of what the extension does
- Should identify the vendor/company for professional extensions
- Must be meaningful (not just "Extension" or empty)
- Keep concise but informative (typically 50-150 characters)
**Good Examples:**
```php
'description' => 'Adds image support to CKEditor5 RTE - by Netresearch',
'description' => 'Provides advanced content management features for TYPO3 editors',
'description' => 'Custom form elements for newsletter subscription by Vendor GmbH',
```
**Bad Examples:**
```php
'description' => '', // Empty
'description' => 'Extension', // Too vague
'description' => 'Some tools', // Meaningless
```
**Validation:**
```bash
# Check description exists
grep "'description' =>" ext_emconf.php && echo "✅ Has description" || echo "❌ Missing description"
# Check description is not empty or trivial
DESC=$(grep -oP "'description' => '\K[^']+(?=')" ext_emconf.php)
[[ ${#DESC} -gt 20 ]] && echo "✅ Description is meaningful" || echo "⚠️ Description too short or vague"
```
### version
**Format:** `[int].[int].[int]` (semantic versioning)
**Examples:**
- `1.0.0`
- `2.5.12`
- `v1.0.0` ❌ (no 'v' prefix)
- `1.0` ❌ (must have three parts)
**Validation:**
```bash
grep -oP "'version' => '\K[0-9]+\.[0-9]+\.[0-9]+" ext_emconf.php && echo "✅ Valid version format" || echo "❌ Invalid version"
```
### author
**Format:** Developer name(s), comma-separated for multiple
**Example:**
```php
'author' => 'Sebastian Koschel, Sebastian Mendel, Rico Sonntag',
```
**Validation:**
```bash
grep "'author' =>" ext_emconf.php && echo "✅ Has author" || echo "❌ Missing author"
```
### author_email
**Format:** Email address(es), comma-separated for multiple
**Example:**
```php
'author_email' => 'developer@company.com, other@company.com',
```
**Validation:**
```bash
grep "'author_email' =>" ext_emconf.php | grep -q "@" && echo "✅ Has author_email" || echo "❌ Missing author_email"
```
### author_company
**Format:** Company name
**Example:**
```php
'author_company' => 'Company Name GmbH',
```
**Validation:**
```bash
grep "'author_company' =>" ext_emconf.php && echo "✅ Has author_company" || echo "⚠️ Missing author_company"
```
---
## Category Options
**Valid categories:**
| Category | Purpose |
|----------|---------|
| `be` | Backend-oriented functionality |
| `module` | Backend modules |
| `fe` | Frontend-oriented functionality |
| `plugin` | Frontend plugins |
| `misc` | Miscellaneous utilities |
| `services` | TYPO3 services |
| `templates` | Website templates |
| `example` | Example/demonstration extensions |
| `doc` | Documentation |
| `distribution` | Full site distributions/kickstarters |
**Example:**
```php
'category' => 'fe',
```
**Validation:**
```bash
grep -oP "'category' => '\K[a-z]+(?=')" ext_emconf.php | grep -qE '^(be|module|fe|plugin|misc|services|templates|example|doc|distribution)$' && echo "✅ Valid category" || echo "❌ Invalid category"
```
---
## State Values
**Valid states:**
| State | Meaning |
|-------|---------|
| `alpha` | Initial development phase, unstable |
| `beta` | Functional but incomplete or under active development |
| `stable` | Production-ready (author commits to maintenance) |
| `experimental` | Exploratory work, may be abandoned |
| `test` | Demonstration or testing purposes only |
| `obsolete` | Deprecated or unmaintained |
| `excludeFromUpdates` | Prevents Extension Manager from updating |
**Example:**
```php
'state' => 'stable',
```
**Validation:**
```bash
grep -oP "'state' => '\K[a-z]+(?=')" ext_emconf.php | grep -qE '^(alpha|beta|stable|experimental|test|obsolete|excludeFromUpdates)$' && echo "✅ Valid state" || echo "❌ Invalid state"
```
---
## Constraints Structure
### Format
```php
'constraints' => [
'depends' => [
'typo3' => '13.4.0-13.4.99',
'php' => '8.2.0-8.4.99',
],
'conflicts' => [
'incompatible_ext' => '',
],
'suggests' => [
'recommended_ext' => '1.0.0-2.99.99',
],
],
```
### depends
**Purpose:** Required dependencies loaded before this extension
**Mandatory entries:**
- `typo3` - TYPO3 version range
- `php` - PHP version range
**Example:**
```php
'depends' => [
'typo3' => '12.4.0-13.4.99',
'php' => '8.1.0-8.4.99',
'fluid' => '12.4.0-13.4.99',
],
```
**Validation:**
```bash
grep -A 5 "'depends' =>" ext_emconf.php | grep -q "'typo3'" && echo "✅ TYPO3 dependency" || echo "❌ Missing TYPO3 dep"
grep -A 5 "'depends' =>" ext_emconf.php | grep -q "'php'" && echo "✅ PHP dependency" || echo "❌ Missing PHP dep"
```
### conflicts
**Purpose:** Extensions incompatible with this one
**Example:**
```php
'conflicts' => [
'old_extension' => '',
],
```
### suggests
**Purpose:** Recommended companion extensions (loaded before current extension)
**Example:**
```php
'suggests' => [
'news' => '12.1.0-12.99.99',
],
```
---
## Version Constraint Format
### TYPO3 Version
**Format:** `major.minor.patch-major.minor.patch`
**Examples:**
- `12.4.0-12.4.99` - TYPO3 12 LTS only
- `13.4.0-13.4.99` - TYPO3 13 LTS only
- `12.4.0-13.4.99` - Both v12 and v13 (recommended for compatibility)
### PHP Version
**Format:** `major.minor.patch-major.minor.patch`
**TYPO3 Compatibility:**
- TYPO3 12 LTS: PHP 8.1-8.4
- TYPO3 13 LTS: PHP 8.2-8.4
**Example:**
```php
'php' => '8.1.0-8.4.99', // For v12/v13 compatibility
'php' => '8.2.0-8.4.99', // For v13 only
```
---
## Synchronization with composer.json
**Critical:** ext_emconf.php and composer.json must have matching constraints.
### Mapping Table
| composer.json | ext_emconf.php | Example |
|--------------|----------------|---------|
| `"typo3/cms-core": "^12.4 \|\| ^13.4"` | `'typo3' => '12.4.0-13.4.99'` | TYPO3 version |
| `"php": "^8.1"` | `'php' => '8.1.0-8.4.99'` | PHP version |
| `"typo3/cms-fluid": "^12.4"` | `'fluid' => '12.4.0-12.4.99'` | Extension dependency |
### Validation Strategy
```bash
# Compare TYPO3 versions
COMPOSER_TYPO3=$(jq -r '.require."typo3/cms-core"' composer.json)
EMCONF_TYPO3=$(grep -oP "'typo3' => '\K[0-9.-]+" ext_emconf.php)
echo "Composer: $COMPOSER_TYPO3"
echo "ext_emconf: $EMCONF_TYPO3"
# Manual comparison required for ^x.y vs x.y.z-x.y.z format
```
---
## Complete Required Fields Checklist
**Mandatory (MUST have):**
- [ ] `title` - Extension name in English
- [ ] `description` - Short, precise description
- [ ] `version` - Semantic version (x.y.z format)
- [ ] `category` - Valid category (be, fe, plugin, misc, etc.)
- [ ] `state` - Valid state (stable, beta, alpha, etc.)
- [ ] `constraints.depends.typo3` - TYPO3 version range
- [ ] `constraints.depends.php` - PHP version range
**Recommended (SHOULD have):**
- [ ] `author` - Developer name(s)
- [ ] `author_email` - Contact email(s)
- [ ] `author_company` - Company name
- [ ] `constraints.conflicts` - Conflicting extensions (even if empty array)
- [ ] `constraints.suggests` - Suggested companion extensions
**Complete Example:**
```php
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension Title',
'description' => 'Provides specific functionality for TYPO3.',
'category' => 'fe',
'author' => 'Developer Name',
'author_email' => 'developer@company.com',
'author_company' => 'Company Name GmbH',
'state' => 'stable',
'version' => '1.0.0',
'constraints' => [
'depends' => [
'typo3' => '12.4.0-13.4.99',
'php' => '8.1.0-8.4.99',
],
'conflicts' => [],
'suggests' => [],
],
];
```
---
## Complete Validation Script
```bash
#!/bin/bash
# validate-ext-emconf.sh
ERRORS=0
WARNINGS=0
echo "=== ext_emconf.php Validation ===="
echo ""
# CRITICAL: Check for strict_types
if grep -q "declare(strict_types" ext_emconf.php 2>/dev/null; then
echo "❌ CRITICAL: ext_emconf.php has declare(strict_types=1)"
echo " TER upload will FAIL!"
((ERRORS++))
fi
# CRITICAL: Check for $_EXTKEY usage
if ! grep -q '\$EM_CONF\[$_EXTKEY\]' ext_emconf.php 2>/dev/null; then
echo "❌ CRITICAL: Must use \$EM_CONF[\$_EXTKEY], not hardcoded key"
((ERRORS++))
fi
# CRITICAL: Check for custom code
if grep -E '^(function|class|interface|trait|require|include)' ext_emconf.php 2>/dev/null; then
echo "❌ CRITICAL: ext_emconf.php contains custom code (functions/classes/requires)"
((ERRORS++))
fi
# Check mandatory fields
grep -q "'title' =>" ext_emconf.php || { echo "❌ Missing title"; ((ERRORS++)); }
grep -q "'description' =>" ext_emconf.php || { echo "❌ Missing description"; ((ERRORS++)); }
grep -qP "'version' => '[0-9]+\.[0-9]+\.[0-9]+" ext_emconf.php || { echo "❌ Missing or invalid version"; ((ERRORS++)); }
# Check description is meaningful (>20 chars)
DESC=$(grep -oP "'description' => '\K[^']+(?=')" ext_emconf.php)
[[ ${#DESC} -lt 20 ]] && { echo "⚠️ Description too short (should be >20 chars)"; ((WARNINGS++)); }
# Check category
CATEGORY=$(grep -oP "'category' => '\K[a-z]+(?=')" ext_emconf.php)
if [[ ! "$CATEGORY" =~ ^(be|module|fe|plugin|misc|services|templates|example|doc|distribution)$ ]]; then
echo "❌ Invalid category: $CATEGORY"
((ERRORS++))
fi
# Check state
STATE=$(grep -oP "'state' => '\K[a-z]+(?=')" ext_emconf.php)
if [[ ! "$STATE" =~ ^(alpha|beta|stable|experimental|test|obsolete|excludeFromUpdates)$ ]]; then
echo "❌ Invalid state: $STATE"
((ERRORS++))
fi
# Check constraints
grep -A 5 "'depends' =>" ext_emconf.php | grep -q "'typo3'" || { echo "❌ Missing TYPO3 dependency"; ((ERRORS++)); }
grep -A 5 "'depends' =>" ext_emconf.php | grep -q "'php'" || { echo "❌ Missing PHP dependency"; ((ERRORS++)); }
# Check recommended author fields
grep -q "'author' =>" ext_emconf.php || { echo "⚠️ Missing author"; ((WARNINGS++)); }
grep "'author_email' =>" ext_emconf.php | grep -q "@" || { echo "⚠️ Missing or invalid author_email"; ((WARNINGS++)); }
grep -q "'author_company' =>" ext_emconf.php || { echo "⚠️ Missing author_company"; ((WARNINGS++)); }
echo ""
echo "Validation complete: $ERRORS errors, $WARNINGS warnings"
exit $ERRORS
```
---
## Common Violations and Fixes
### 1. Using declare(strict_types=1)
**WRONG - TER upload FAILS:**
```php
<?php
declare(strict_types=1);
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension',
];
```
**CORRECT:**
```php
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension',
];
```
### 2. Hardcoded Extension Key
**WRONG:**
```php
$EM_CONF['my_extension'] = [
'title' => 'My Extension',
];
```
**CORRECT:**
```php
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension',
];
```
### 3. Invalid Category
**WRONG:**
```php
'category' => 'utility', // Not a valid category
```
**CORRECT:**
```php
'category' => 'misc', // Use 'misc' for utilities
```
### 4. Invalid Version Format
**WRONG:**
```php
'version' => 'v1.0.0', // No 'v' prefix
'version' => '1.0', // Must have 3 parts
```
**CORRECT:**
```php
'version' => '1.0.0',
```
### 5. Missing PHP/TYPO3 Constraints
**WRONG:**
```php
'constraints' => [
'depends' => [
'extbase' => '12.4.0-12.4.99',
],
],
```
**CORRECT:**
```php
'constraints' => [
'depends' => [
'typo3' => '12.4.0-13.4.99',
'php' => '8.1.0-8.4.99',
'extbase' => '12.4.0-12.4.99',
],
],
```
### 6. Mismatched composer.json Constraints
**WRONG:**
composer.json:
```json
"require": {
"typo3/cms-core": "^13.4"
}
```
ext_emconf.php:
```php
'typo3' => '12.4.0-12.4.99', // Mismatch!
```
**CORRECT:**
composer.json:
```json
"require": {
"typo3/cms-core": "^13.4"
}
```
ext_emconf.php:
```php
'typo3' => '13.4.0-13.4.99', // Matches!
```
---
## Quick Reference
### Critical Checks
```bash
# Will TER upload fail?
grep "declare(strict_types" ext_emconf.php && echo "❌ TER FAIL"
# Uses $_EXTKEY?
grep '\$EM_CONF\[$_EXTKEY\]' ext_emconf.php && echo "✅ OK"
# Valid category?
grep -oP "'category' => '\K[a-z]+(?=')" ext_emconf.php | grep -qE '^(be|module|fe|plugin|misc|services|templates|example|doc|distribution)$' && echo "✅ OK"
# Valid state?
grep -oP "'state' => '\K[a-z]+(?=')" ext_emconf.php | grep -qE '^(alpha|beta|stable|experimental|test|obsolete|excludeFromUpdates)$' && echo "✅ OK"
```

View File

@@ -0,0 +1,413 @@
# Extension Files Validation Standards (TYPO3 v13)
**Sources:** TYPO3 Core API Reference v13.4
**Purpose:** Validation rules for ext_localconf.php, ext_tables.php, ext_tables.sql, ext_tables_static+adt.sql, ext_conf_template.txt
## ext_localconf.php
### Purpose
Global configuration file loaded during TYPO3 bootstrap in frontend, backend, and CLI contexts.
### Required Structure
```php
<?php
declare(strict_types=1);
defined('TYPO3') or die();
// Configuration code here
```
### What SHOULD Be Included
✅ Registering hooks, XCLASSes, array assignments to `$GLOBALS['TYPO3_CONF_VARS']`
✅ Registering Request Handlers
✅ Adding default TypoScript via ExtensionManagementUtility APIs
✅ Registering Scheduler Tasks
✅ Adding reports to reports module
✅ Registering Services via Service API
### What Should NOT Be Included
❌ Function and class definitions (use services/utility classes)
❌ Class loader or package manager configuration
❌ Cache/config manager settings
❌ Log manager configuration
❌ Time zone, memory limit, locale settings
❌ Icon registration (use `Icons.php` instead)
### TYPO3 v13 Deprecations
**❌ DEPRECATED:** `\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig()`
- **Removal:** TYPO3 v14.0
- **Alternative:** Use `Configuration/user.tsconfig` file instead
**❌ DEPRECATED (since v12):** Page TSconfig in ext_localconf.php
- **Alternative:** Use `Configuration/page.tsconfig` file instead
### Validation Commands
```bash
# Check required structure
head -5 ext_localconf.php | grep "declare(strict_types=1)" && echo "✅ Has strict_types"
head -5 ext_localconf.php | grep "defined('TYPO3')" && echo "✅ Has TYPO3 guard"
# Check for deprecated addUserTSConfig
grep "addUserTSConfig" ext_localconf.php && echo "⚠️ DEPRECATED: Use Configuration/user.tsconfig"
```
---
## ext_tables.php
### Deprecation Status
**PHASING OUT:** Increasingly replaced by modern configuration approaches.
### What Should NOT Be in ext_tables.php (v13)
**TCA configurations** → Use `Configuration/TCA/tablename.php`
**TCA overrides** → Use `Configuration/TCA/Overrides/somefile.php`
**Insert records** → Move to TCA Overrides files
**Static files** → Move to `Configuration/TCA/Overrides/sys_template.php`
**Backend modules** → Moved to `Configuration/Backend/` in v13.0
### Appropriate Uses (Remaining)
✅ Registering scheduler tasks with localization labels
✅ Registering custom page types
✅ Extending backend user settings
### v13 Migration
**Backend Module Registration:**
```php
OLD (ext_tables.php):
ExtensionUtility::registerModule(...);
NEW (Configuration/Backend/Modules.php):
return [
'web_myext' => [
'parent' => 'web',
'position' => ['after' => 'web_list'],
// ...
],
];
```
### Validation Commands
```bash
# Check for TCA modifications (should be in TCA/Overrides/)
grep -E "addTCAcolumns|addToAllTCAtypes" ext_tables.php && echo "⚠️ WARNING: Move to TCA/Overrides/"
# Check for backend module registration (should be in Configuration/Backend/)
grep "registerModule" ext_tables.php && echo "⚠️ WARNING: Move to Configuration/Backend/Modules.php"
```
---
## ext_tables.sql
### Purpose
Defines database tables and columns for extensions. Parsed when extensions are enabled.
### SQL Syntax Requirements
**Format:** Follow `mysqldump` utility output style
- TYPO3 parses and converts to target DBMS (MySQL, MariaDB, PostgreSQL, SQLite)
- Partial definitions allowed when extending existing tables
### Table Naming Conventions
```sql
-- Extension tables with prefix
CREATE TABLE tx_myextension_domain_model_table (
field_name varchar(255) DEFAULT '' NOT NULL,
);
-- Extending core tables
CREATE TABLE pages (
tx_myextension_field int(11) DEFAULT '0' NOT NULL,
);
```
### Auto-Generated Columns
If TCA exists, TYPO3 automatically creates:
- `uid` with PRIMARY KEY
- `pid` (unsigned) with default index `parent`
- System fields based on TCA `ctrl` properties
### New in v13: Empty Table Definitions
```sql
-- Valid when TCA enriches fields
CREATE TABLE tx_myextension_table (
);
```
### v13.4 CHAR/BINARY Handling
**WARNING:** Fixed-length types now properly flagged
- Use only with ensured fixed-length values (hash identifiers)
- **Avoid with Extbase ORM** (cannot ensure fixed-length in queries)
- Test extensively across database platforms
**Best Practice:**
```sql
VARCHAR(255) -- Variable length (preferred)
CHAR(32) -- Fixed length (use cautiously)
VARBINARY(255) -- Variable binary (preferred)
BINARY(16) -- Fixed binary (use cautiously)
```
### Validation Commands
```bash
# Check table naming
grep "CREATE TABLE" ext_tables.sql | grep -E "tx_[a-z_]+" && echo "✅ Proper naming"
# Check for CHAR usage (potential issue)
grep -E "CHAR\([0-9]+\)" ext_tables.sql && echo "⚠️ WARNING: CHAR type found - verify fixed-length"
# Validate syntax
php -r "file_get_contents('ext_tables.sql');" && echo "✅ File readable"
```
---
## ext_tables_static+adt.sql
### Purpose
Stores static SQL INSERT statements for pre-populated data.
### Critical Restrictions
**❌ ONLY INSERT statements allowed**
- No CREATE TABLE
- No ALTER TABLE
- No UPDATE/DELETE
**⚠️ Warning:** "Static data is not meant to be extended by other extensions. On re-import all extended fields and data is lost."
### When to Use
- Initial data required during installation
- Lookup tables, predefined categories
- Default configuration data
### Re-import Behavior
- Data truncated and reimported when file contents change
- Executed via:
- `bin/typo3 extension:setup`
- Admin Tools > Extensions reload
### Generation Command
```bash
mysqldump --user=[user] --password [database] [tablename] > ./ext_tables_static+adt.sql
```
### Validation Commands
```bash
# Check file exists
[ -f "ext_tables_static+adt.sql" ] && echo "✅ Static data file present"
# Verify only INSERT statements
grep -v "^INSERT" ext_tables_static+adt.sql | grep -E "^(CREATE|ALTER|UPDATE|DELETE)" && echo "❌ CRITICAL: Only INSERT allowed"
# Check corresponding table definition exists
grep "CREATE TABLE" ext_tables.sql && echo "✅ Table definitions present"
```
---
## ext_conf_template.txt
### Purpose
Defines extension configuration options in Admin Tools > Settings module.
### Syntax Format
```
# cat=Category; type=fieldtype; label=LLL:EXT:key/path.xlf:label
optionName = defaultValue
```
### Field Types
| Type | Purpose | Example |
|------|---------|---------
| `boolean` | Checkbox | `type=boolean` |
| `string` | Text field | `type=string` |
| `int` / `integer` | Whole number | `type=int` |
| `int+` | Positive integers | `type=int+` |
| `color` | Color picker | `type=color` |
| `options` | Select dropdown | `type=options[Val1=1,Val2=2]` |
| `user` | Custom function | `type=user[Vendor\Class->method]` |
| `small` | Compact text field | `type=small` |
| `wrap` | Wrapper field | `type=wrap` |
| `offset` | Offset value | `type=offset` |
### Options Syntax
```
# cat=basic; type=options[Option 1=value1,Option 2=value2]; label=Select Option
variable = value1
```
### User Function Syntax
```
# cat=advanced; type=user[Vendor\Extension\Class->methodName]; label=Custom Field
variable = 1
```
### Nested Structure
```
directories {
# cat=paths; type=string; label=Temp directory
tmp = /tmp
# cat=paths; type=string; label=Upload directory
uploads = /uploads
}
```
**Access:** `$config['directories']['tmp']`
### Localization
```
# Use LLL references for multi-language support
# cat=basic; type=string; label=LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:config.title
title = Default Title
```
### Validation Commands
```bash
# Check file exists
[ -f "ext_conf_template.txt" ] && echo "✅ Configuration template present"
# Check syntax format
grep -E "^#.*cat=.*type=.*label=" ext_conf_template.txt && echo "✅ Valid syntax found"
# Check for localization
grep "LLL:EXT:" ext_conf_template.txt && echo "✅ Uses localized labels"
# Validate field types
grep -E "type=(boolean|string|int|int\+|color|options|user|small|wrap|offset)" ext_conf_template.txt && echo "✅ Valid field types"
```
### Accessing Configuration in Code
```php
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
public function __construct(
private readonly ExtensionConfiguration $extensionConfiguration
) {}
// Get all configuration
$config = $this->extensionConfiguration->get('extension_key');
// Get specific value
$value = $this->extensionConfiguration->get('extension_key', 'optionName');
```
---
## Validation Checklist
### ext_localconf.php
- [ ] Has `declare(strict_types=1)` at top
- [ ] Has `defined('TYPO3') or die();` guard
- [ ] No function/class definitions
- [ ] **NOT** using deprecated `addUserTSConfig()`
- [ ] **NOT** adding page TSconfig (use Configuration/page.tsconfig)
### ext_tables.php
- [ ] No TCA definitions (use Configuration/TCA/)
- [ ] No TCA overrides (use Configuration/TCA/Overrides/)
- [ ] No backend module registration (use Configuration/Backend/)
- [ ] Only contains appropriate v13 use cases
### ext_tables.sql
- [ ] Follows mysqldump syntax
- [ ] Tables prefixed with `tx_<extensionkey>_`
- [ ] Uses VARCHAR/VARBINARY (not CHAR/BINARY unless necessary)
- [ ] Empty table definitions if TCA provides fields
### ext_tables_static+adt.sql (if present)
- [ ] **ONLY** INSERT statements (no CREATE/ALTER)
- [ ] Corresponding table structure in ext_tables.sql
- [ ] Static data is truly static (not extended by other extensions)
### ext_conf_template.txt (if present)
- [ ] Syntax: `# cat=; type=; label=`
- [ ] Valid field types used
- [ ] Localized labels with LLL: references
- [ ] Proper categorization
- [ ] Sensible default values
---
## Common Violations and Fixes
### ext_localconf.php: Using Deprecated addUserTSConfig
❌ Before:
```php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig('
options.pageTree.showPageIdWithTitle = 1
');
```
✅ After:
```
// Create Configuration/user.tsconfig
options.pageTree.showPageIdWithTitle = 1
```
### ext_tables.php: Backend Module in ext_tables.php
❌ Before (ext_tables.php):
```php
ExtensionUtility::registerModule('MyExt', 'web', 'mymodule', ...);
```
✅ After (Configuration/Backend/Modules.php):
```php
return [
'web_myext_mymodule' => [
'parent' => 'web',
'position' => ['after' => 'web_list'],
'access' => 'user',
'path' => '/module/web/myext',
'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',
'extensionName' => 'MyExt',
'controllerActions' => [
\Vendor\MyExt\Controller\ModuleController::class => ['list', 'detail'],
],
],
];
```
### ext_tables.sql: Using CHAR Inappropriately
❌ Before:
```sql
CREATE TABLE tx_myext_table (
name CHAR(255) DEFAULT '' NOT NULL, -- Variable content!
);
```
✅ After:
```sql
CREATE TABLE tx_myext_table (
name VARCHAR(255) DEFAULT '' NOT NULL, -- Use VARCHAR
);
```
### ext_tables_static+adt.sql: Including CREATE Statements
❌ Before:
```sql
CREATE TABLE tx_myext_categories (
uid int(11) NOT NULL auto_increment,
title varchar(255) DEFAULT '' NOT NULL,
PRIMARY KEY (uid)
);
INSERT INTO tx_myext_categories VALUES (1, 'Category 1');
```
✅ After:
```sql
-- Move CREATE to ext_tables.sql
-- Only INSERT in ext_tables_static+adt.sql
INSERT INTO tx_myext_categories (uid, title) VALUES (1, 'Category 1');
INSERT INTO tx_myext_categories (uid, title) VALUES (2, 'Category 2');
```

View File

@@ -0,0 +1,273 @@
# TYPO3 Extension Architecture Standards
**Source:** TYPO3 Core API Reference - Extension Architecture
**Purpose:** File structure, directory hierarchy, and required files for TYPO3 extensions
## Required Files
### Essential Files
**composer.json**
- REQUIRED for all extensions
- Defines package metadata, dependencies, PSR-4 autoloading
- Example:
```json
{
"name": "vendor/extension-key",
"type": "typo3-cms-extension",
"require": {
"typo3/cms-core": "^12.4 || ^13.0"
},
"autoload": {
"psr-4": {
"Vendor\\ExtensionKey\\": "Classes/"
}
}
}
```
**ext_emconf.php**
- REQUIRED for TER (TYPO3 Extension Repository) publication
- Contains extension metadata
- Example:
```php
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'Extension Title',
'description' => 'Extension description',
'category' => 'fe',
'author' => 'Author Name',
'author_email' => 'author@example.com',
'state' => 'stable',
'version' => '1.0.0',
'constraints' => [
'depends' => [
'typo3' => '12.4.0-13.9.99',
],
],
];
```
**Documentation/Index.rst**
- REQUIRED for docs.typo3.org publication
- Main documentation entry point
- Must follow reStructuredText format
**Documentation/Settings.cfg**
- REQUIRED for docs.typo3.org publication
- Contains documentation project settings
- Example:
```ini
[general]
project = Extension Name
release = 1.0.0
copyright = 2024
[html_theme_options]
project_home = https://github.com/vendor/extension
```
## Directory Structure
### Core Directories
**Classes/**
- Contains all PHP classes
- MUST follow PSR-4 autoloading structure
- Namespace: `\VendorName\ExtensionKey\`
- Common subdirectories:
- `Classes/Controller/` - Extbase/backend controllers
- `Classes/Domain/Model/` - Domain models
- `Classes/Domain/Repository/` - Repositories
- `Classes/Service/` - Service classes
- `Classes/Utility/` - Utility classes
- `Classes/ViewHelper/` - Fluid ViewHelpers
- `Classes/EventListener/` - PSR-14 event listeners
**Configuration/**
- Contains all configuration files
- Required subdirectories:
- `Configuration/TCA/` - Table Configuration Array definitions
- `Configuration/Backend/` - Backend module configuration
- `Configuration/TypoScript/` - TypoScript configuration
- `Configuration/Sets/` - Configuration sets (TYPO3 v13+)
- Optional files:
- `Configuration/Services.yaml` - Dependency injection configuration
- `Configuration/TsConfig/` - Page/User TSconfig
- `Configuration/RequestMiddlewares.php` - PSR-15 middlewares
**Resources/**
- Contains all frontend/backend resources
- Structure:
- `Resources/Private/` - Non-public files
- `Resources/Private/Templates/` - Fluid templates
- `Resources/Private/Partials/` - Fluid partials
- `Resources/Private/Layouts/` - Fluid layouts
- `Resources/Private/Language/` - Translation files (XLIFF)
- `Resources/Public/` - Publicly accessible files
- `Resources/Public/Css/` - Stylesheets
- `Resources/Public/JavaScript/` - JavaScript files
- `Resources/Public/Icons/` - Extension icons
- `Resources/Public/Images/` - Images
**Tests/**
- Contains all test files
- Structure:
- `Tests/Unit/` - PHPUnit unit tests
- `Tests/Functional/` - PHPUnit functional tests
- `Tests/Acceptance/` - Codeception acceptance tests
- MUST mirror `Classes/` structure
**Documentation/**
- Contains RST documentation
- MUST include `Index.rst` and `Settings.cfg`
- Common structure:
- `Documentation/Introduction/`
- `Documentation/Installation/`
- `Documentation/Configuration/`
- `Documentation/Developer/`
- `Documentation/Editor/`
## Reserved File Prefixes
Files with the `ext_*` prefix are reserved for special purposes:
**ext_emconf.php**
- Extension metadata (REQUIRED for TER)
**ext_localconf.php**
- Global configuration executed in both frontend and backend
- Register hooks, event listeners, XCLASSes
- Add plugins, content elements
- Register services
**ext_tables.php**
- Backend-specific configuration
- Register backend modules
- Add TCA modifications
- DEPRECATED in favor of dedicated configuration files
**ext_tables.sql**
- Database table definitions
- Executed during extension installation
- Contains CREATE TABLE and ALTER TABLE statements
**ext_conf_template.txt**
- Extension configuration template
- Defines settings available in Extension Configuration
- TypoScript-like syntax
## File Naming Conventions
### PHP Classes
- File name MUST match class name exactly
- PSR-4 compliant
- Example: `Classes/Controller/MyController.php``class MyController`
### Database Tables
- Pattern: `tx_<extensionkeyprefix>_<tablename>`
- Example: `tx_myext_domain_model_product`
- Extension key must be converted to lowercase, underscores allowed
### TCA Files
- Pattern: `Configuration/TCA/<tablename>.php`
- Returns TCA array
- Example: `Configuration/TCA/tx_myext_domain_model_product.php`
### Language Files
- Pattern: `Resources/Private/Language/<context>.xlf`
- XLIFF 1.2 format
- Example: `Resources/Private/Language/locallang.xlf`
## Architecture Best Practices
### PSR-4 Autoloading
- All classes in `Classes/` directory
- Namespace structure MUST match directory structure
- Example:
- Class: `Vendor\ExtensionKey\Domain\Model\Product`
- File: `Classes/Domain/Model/Product.php`
### Dependency Injection
- Use constructor injection for dependencies
- Register services in `Configuration/Services.yaml`
- Example:
```yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Vendor\ExtensionKey\:
resource: '../Classes/*'
```
### Configuration Files
- Separate concerns into dedicated configuration files
- Use `Configuration/Backend/` for backend modules (not ext_tables.php)
- Use `Configuration/TCA/` for table definitions
- Use `Configuration/TypoScript/` for TypoScript
### Testing Structure
- Mirror `Classes/` structure in `Tests/Unit/` and `Tests/Functional/`
- Example:
- Class: `Classes/Service/CalculationService.php`
- Unit Test: `Tests/Unit/Service/CalculationServiceTest.php`
- Functional Test: `Tests/Functional/Service/CalculationServiceTest.php`
## Common Issues
### ❌ Wrong: Mixed file types in root
```
my_extension/
├── MyController.php # WRONG: PHP in root
├── config.yaml # WRONG: Config in root
└── styles.css # WRONG: CSS in root
```
### ✅ Right: Proper directory structure
```
my_extension/
├── Classes/Controller/MyController.php
├── Configuration/Services.yaml
└── Resources/Public/Css/styles.css
```
### ❌ Wrong: Non-standard directory names
```
Classes/
├── Controllers/ # WRONG: Plural
├── Services/ # WRONG: Should be Service
└── Helpers/ # WRONG: Use Utility
```
### ✅ Right: Standard TYPO3 directory names
```
Classes/
├── Controller/ # Singular
├── Service/ # Singular
└── Utility/ # Standard naming
```
## Extension Key Naming
- Lowercase letters and underscores only
- Must start with a letter
- 3-30 characters
- Cannot start with `tx_`, `user_`, `pages`, `tt_`, `sys_`
- Example: `my_extension`, `blog_example`, `news`
## Conformance Checklist
- [ ] composer.json present with correct structure
- [ ] ext_emconf.php present with complete metadata
- [ ] Documentation/Index.rst and Documentation/Settings.cfg present
- [ ] Classes/ directory follows PSR-4 structure
- [ ] Configuration/ subdirectories properly organized
- [ ] Resources/ separated into Private/ and Public/
- [ ] Tests/ mirror Classes/ structure
- [ ] No PHP files in extension root (except ext_* files)
- [ ] File naming follows conventions
- [ ] Database table names use tx_<extensionkey>_ prefix
- [ ] Extension key follows naming rules

View File

@@ -0,0 +1,297 @@
# TYPO3 Hooks and PSR-14 Events
**Source:** TYPO3 Core API Reference - Hooks, Events, and Signals
**Purpose:** Understanding TYPO3 hook system, PSR-14 events, and migration strategies
## SC_OPTIONS Hooks Status in TYPO3 13
### ⚠️ Common Misconception
**INCORRECT:** "SC_OPTIONS hooks are deprecated in TYPO3 13"
**CORRECT:** SC_OPTIONS hooks are **NOT deprecated** in TYPO3 13. They remain the **official pattern** for specific use cases.
### SC_OPTIONS Hooks That Are Still Official
The following SC_OPTIONS hooks remain the official TYPO3 13 pattern:
#### DataHandler Hooks (Still Official)
```php
// Configuration/Services.yaml
Vendor\Extension\Database\MyDataHandlerHook:
public: true
tags:
- name: event.listener
identifier: 'my-extension/datahandler-hook'
method: 'processDatamap_postProcessFieldArray'
```
**Still Official in ext_localconf.php:**
```php
<?php
// TYPO3 13+ DataHandler hooks remain official pattern
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] =
\Vendor\Extension\Database\MyDataHandlerHook::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][] =
\Vendor\Extension\Database\MyDataHandlerHook::class;
```
**Key DataHandler Hook Methods (TYPO3 13+):**
- `processDatamap_preProcessFieldArray()` - Before field processing
- `processDatamap_postProcessFieldArray()` - After field processing
- `processDatamap_afterDatabaseOperations()` - After DB operations
- `processCmdmap_preProcess()` - Before command processing
- `processCmdmap_postProcess()` - After command processing
- `processCmdmap_afterFinish()` - After all commands finished
#### RTE Transformation Hooks (Still Official)
```php
<?php
// TYPO3 13+ RTE transformation hooks remain official pattern
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['transformation'][] =
\Vendor\Extension\RteTransformation\MyRteTransformationHook::class;
```
**Required Methods:**
- `transform_rte()` - Transform content from database to RTE
- `transform_db()` - Transform content from RTE to database
### When to Use SC_OPTIONS vs PSR-14 Events
| Scenario | Use SC_OPTIONS Hook | Use PSR-14 Event |
|----------|-------------------|------------------|
| DataHandler field processing | ✅ Yes (official) | ❌ No event available |
| RTE content transformation | ✅ Yes (official) | ❌ No event available |
| Backend user authentication | ❌ Migrated | ✅ Use PSR-14 events |
| Frontend rendering | ❌ Migrated | ✅ Use PSR-14 events |
| Page generation | ❌ Migrated | ✅ Use PSR-14 events |
| Cache clearing | ❌ Migrated | ✅ Use PSR-14 events |
## PSR-14 Event Listeners (Preferred)
For most scenarios, PSR-14 events are the modern TYPO3 13+ approach.
### Event Listener Configuration
```yaml
# Configuration/Services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Vendor\Extension\:
resource: '../Classes/*'
# PSR-14 Event Listener
Vendor\Extension\EventListener\MyEventListener:
tags:
- name: event.listener
identifier: 'my-extension/my-event-listener'
event: TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent
```
### Event Listener Implementation
```php
<?php
declare(strict_types=1);
namespace Vendor\Extension\EventListener;
use TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent;
/**
* PSR-14 Event Listener for user login.
*/
final class MyEventListener
{
public function __invoke(AfterUserLoggedInEvent $event): void
{
$user = $event->getUser();
// Your logic here
}
}
```
### Common TYPO3 13 Events
**Authentication Events:**
- `AfterUserLoggedInEvent`
- `BeforeUserLogoutEvent`
- `AfterUserLoggedOutEvent`
**Backend Events:**
- `ModifyButtonBarEvent`
- `ModifyDatabaseQueryForContentEvent`
- `BeforePagePreviewUriGeneratedEvent`
**DataHandler Events:**
- `AfterDataInsertedEvent`
- `AfterDataUpdatedEvent`
- `AfterRecordDeletedEvent`
**Page Events:**
- `AfterPageTreeItemsPreparedEvent`
- `ModifyPageLayoutContentEvent`
**Complete Reference:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/EventDispatcher/Index.html
## Migration Strategy
### Step 1: Identify Hook Type
```php
// Check if hook is in ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['...']['...'][]
```
### Step 2: Check Official Documentation
- **DataHandler hooks:** Still official, keep using SC_OPTIONS
- **RTE transformation:** Still official, keep using SC_OPTIONS
- **Other hooks:** Check if PSR-14 event exists
### Step 3: Migrate or Modernize
**If PSR-14 event exists:**
```php
// OLD: ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'][]
= \Vendor\Extension\Hook\MyHook::class;
// NEW: Configuration/Services.yaml + EventListener class
Vendor\Extension\EventListener\MyEventListener:
tags:
- name: event.listener
identifier: 'my-extension/after-login'
event: TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent
```
**If no PSR-14 event exists (DataHandler, RTE):**
```php
// KEEP: Still official in TYPO3 13+
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][]
= \Vendor\Extension\Database\MyDataHandlerHook::class;
// MODERNIZE: Add dependency injection
// Configuration/Services.yaml
Vendor\Extension\Database\MyDataHandlerHook:
public: true
arguments:
$resourceFactory: '@TYPO3\CMS\Core\Resource\ResourceFactory'
$context: '@TYPO3\CMS\Core\Context\Context'
$logManager: '@TYPO3\CMS\Core\Log\LogManager'
```
## Best Practices
### 1. Constructor Dependency Injection
Even for SC_OPTIONS hooks, use constructor injection (TYPO3 13+):
```php
<?php
declare(strict_types=1);
namespace Vendor\Extension\Database;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Resource\ResourceFactory;
/**
* DataHandler hook with dependency injection.
*/
final class MyDataHandlerHook
{
public function __construct(
private readonly ResourceFactory $resourceFactory,
private readonly Context $context,
private readonly LogManager $logManager,
) {}
public function processDatamap_postProcessFieldArray(
string $status,
string $table,
string $id,
array &$fieldArray,
\TYPO3\CMS\Core\DataHandling\DataHandler &$dataHandler,
): void {
// Use injected dependencies
$file = $this->resourceFactory->getFileObject($fileId);
}
}
```
### 2. Avoid GeneralUtility::makeInstance()
```php
// ❌ BAD: Using makeInstance (legacy pattern)
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
// ✅ GOOD: Constructor injection (TYPO3 13+ pattern)
public function __construct(
private readonly ResourceFactory $resourceFactory,
) {}
```
### 3. Configure Services Explicitly
```yaml
# Configuration/Services.yaml
services:
Vendor\Extension\Database\MyDataHandlerHook:
public: true # Required for SC_OPTIONS hooks
arguments:
$resourceFactory: '@TYPO3\CMS\Core\Resource\ResourceFactory'
$context: '@TYPO3\CMS\Core\Context\Context'
$logManager: '@TYPO3\CMS\Core\Log\LogManager'
```
## Acceptable $GLOBALS Usage
Even in TYPO3 13+, certain `$GLOBALS` usage is acceptable:
### ✅ Acceptable $GLOBALS
```php
// TCA access (no alternative available)
$GLOBALS['TCA']['tt_content']['columns']['bodytext']
// Current request (framework-provided)
$GLOBALS['TYPO3_REQUEST']
// Backend user context (framework-provided)
$GLOBALS['BE_USER']
// Frontend user context (framework-provided)
$GLOBALS['TSFE']
```
### ❌ Avoid $GLOBALS
```php
// Database connection (use ConnectionPool)
$GLOBALS['TYPO3_DB']
// Extension configuration (use ExtensionConfiguration)
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['my_ext']
// Object instantiation (use dependency injection)
GeneralUtility::makeInstance(SomeClass::class)
```
## Resources
- [TYPO3 Hooks Documentation](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Hooks/Index.html)
- [PSR-14 Events](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/EventDispatcher/Index.html)
- [DataHandler Hooks](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Hooks/DataHandler/Index.html)
- [Dependency Injection](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/Index.html)

View File

@@ -0,0 +1,918 @@
# TYPO3 PHP Architecture Standards
**Source:** TYPO3 Core API Reference - PHP Architecture
**Purpose:** Dependency injection, services, events, Extbase, middleware patterns
## Dependency Injection
TYPO3 uses **Symfony's Dependency Injection Container** for service management.
### Constructor Injection (Preferred)
```php
// ✅ Right: Constructor injection with readonly properties
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\ExtensionKey\Domain\Repository\UserRepository;
final class UserController extends ActionController
{
public function __construct(
private readonly UserRepository $userRepository
) {}
public function listAction(): ResponseInterface
{
$users = $this->userRepository->findAll();
$this->view->assign('users', $users);
return $this->htmlResponse();
}
}
```
### Method Injection (inject* Methods)
```php
// ✅ Right: Method injection for abstract classes
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Controller;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\ExtensionKey\Domain\Repository\UserRepository;
class UserController extends ActionController
{
protected ?UserRepository $userRepository = null;
public function injectUserRepository(UserRepository $userRepository): void
{
$this->userRepository = $userRepository;
}
}
```
**When to Use Method Injection:**
- Extending abstract core classes (ActionController, AbstractValidator)
- Avoiding breaking changes when base class constructor changes
- Optional dependencies
**When to Use Constructor Injection:**
- All new code (preferred)
- Required dependencies
- Better testability
### Interface Injection
```php
// ✅ Right: Depend on interfaces, not implementations
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\ExtensionKey\Domain\Repository\UserRepositoryInterface;
final class UserController extends ActionController
{
public function __construct(
private readonly UserRepositoryInterface $userRepository
) {}
}
```
## Service Configuration
### Configuration/Services.yaml
```yaml
# ✅ Right: Proper service configuration
services:
_defaults:
autowire: true
autoconfigure: true
public: false
# Auto-register all classes
Vendor\ExtensionKey\:
resource: '../Classes/*'
# Explicit service configuration
Vendor\ExtensionKey\Service\MyService:
arguments:
$configValue: '%env(MY_CONFIG_VALUE)%'
# Factory pattern for Connection
Vendor\ExtensionKey\Domain\Repository\MyTableRepository:
factory: ['@TYPO3\CMS\Core\Database\ConnectionPool', 'getConnectionForTable']
arguments:
- 'my_table'
# Interface binding
Vendor\ExtensionKey\Domain\Repository\UserRepositoryInterface:
class: Vendor\ExtensionKey\Domain\Repository\UserRepository
```
### Autowire Attribute (TYPO3 v12+)
```php
// ✅ Right: Inject configuration using Autowire attribute
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Service;
use TYPO3\CMS\Core\DependencyInjection\Attribute\Autowire;
final class MyService
{
public function __construct(
#[Autowire(expression: 'service("configuration.extension").get("my_extension", "mySetting")')]
private readonly string $myExtensionSetting
) {}
}
```
## PSR-14 Event Dispatcher
### Defining Custom Events
```php
// ✅ Right: Immutable event class with getters/setters
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Event;
final class BeforeUserCreatedEvent
{
public function __construct(
private string $username,
private string $email,
private array $additionalData = []
) {}
public function getUsername(): string
{
return $this->username;
}
public function getEmail(): string
{
return $this->email;
}
public function getAdditionalData(): array
{
return $this->additionalData;
}
public function setAdditionalData(array $additionalData): void
{
$this->additionalData = $additionalData;
}
}
```
### Dispatching Events
```php
// ✅ Right: Inject and dispatch events
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Service;
use Psr\EventDispatcher\EventDispatcherInterface;
use Vendor\ExtensionKey\Event\BeforeUserCreatedEvent;
final class UserService
{
public function __construct(
private readonly EventDispatcherInterface $eventDispatcher
) {}
public function createUser(string $username, string $email): void
{
$event = new BeforeUserCreatedEvent($username, $email);
$event = $this->eventDispatcher->dispatch($event);
// Use potentially modified data from event
$finalUsername = $event->getUsername();
$finalEmail = $event->getEmail();
// Create user with final data
}
}
```
### Event Listeners
```php
// ✅ Right: Event listener with AsEventListener attribute
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use Vendor\ExtensionKey\Event\BeforeUserCreatedEvent;
#[AsEventListener(
identifier: 'vendor/extension-key/validate-user-creation',
event: BeforeUserCreatedEvent::class
)]
final class ValidateUserCreationListener
{
public function __invoke(BeforeUserCreatedEvent $event): void
{
// Validate email format
if (!filter_var($event->getEmail(), FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('Invalid email format');
}
// Add custom data
$event->setAdditionalData([
'validated_at' => time(),
'validator' => 'ValidateUserCreationListener',
]);
}
}
```
### Event Listener Registration (Services.yaml)
```yaml
# Alternative: Register event listeners in Services.yaml
services:
Vendor\ExtensionKey\EventListener\ValidateUserCreationListener:
tags:
- name: event.listener
identifier: 'vendor/extension-key/validate-user-creation'
event: Vendor\ExtensionKey\Event\BeforeUserCreatedEvent
method: '__invoke'
```
### PSR-14 Event Class Standards (TYPO3 13+)
Modern event classes should follow these quality standards:
```php
// ✅ Right: Modern event class with final keyword and readonly properties
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Event;
use Psr\Http\Message\ServerRequestInterface;
final class NewsListActionEvent // ✅ Use 'final' keyword
{
public function __construct(
private NewsController $newsController,
private array $assignedValues,
private readonly ServerRequestInterface $request // ✅ Use 'readonly' for immutable properties
) {}
public function getNewsController(): NewsController
{
return $this->newsController;
}
public function getAssignedValues(): array
{
return $this->assignedValues;
}
public function setAssignedValues(array $assignedValues): void
{
$this->assignedValues = $assignedValues;
}
public function getRequest(): ServerRequestInterface
{
return $this->request; // Read-only, no setter
}
}
```
**Event Class Quality Checklist:**
- [ ] Use `final` keyword (prevents inheritance, ensures immutability)
- [ ] Use `readonly` for properties that should never change after construction
- [ ] Provide getters for all properties
- [ ] Provide setters ONLY for properties that should be modifiable
- [ ] Type hint all properties and methods
- [ ] Document the purpose and usage of the event
**Why `final` for Events?**
- Events are data carriers, not meant to be extended
- Prevents unexpected behavior from inheritance
- Makes event behavior predictable and testable
- Follows modern PHP best practices
**Why `readonly` for Properties?**
- Some event data should never change (e.g., original request, user context)
- Explicit immutability prevents accidental modifications
- Clearly communicates intent to event listeners
- Available in PHP 8.1+ (TYPO3 13 minimum is PHP 8.1)
## TYPO3 13 Site Sets
**Purpose:** Modern configuration distribution system replacing static TypoScript includes
### Site Sets Structure
```
Configuration/Sets/
├── MyExtension/ # Base configuration set
│ ├── config.yaml # Set metadata and dependencies
│ ├── setup.typoscript # Frontend TypoScript
│ ├── constants.typoscript
│ └── settings.definitions.yaml # Setting definitions for extension configuration
├── RecordLinks/ # Optional feature set
│ ├── config.yaml
│ └── setup.typoscript
└── Bootstrap5/ # Frontend framework preset
├── config.yaml
├── setup.typoscript
└── settings.yaml
```
### config.yaml Structure
```yaml
# ✅ Right: Proper Site Set configuration
name: vendor/extension-key
label: Extension Name Base Configuration
# Dependencies on other sets
dependencies:
- typo3/fluid-styled-content
- vendor/extension-key-styles
# Load order priority (optional)
priority: 50
# Settings that can be overridden
settings:
mySetting:
value: 'default value'
type: string
label: 'My Setting Label'
description: 'Description of what this setting does'
```
### settings.definitions.yaml
```yaml
# ✅ Right: Define extension settings with validation
settings:
# Text input
mySetting:
type: string
default: 'default value'
label: 'LLL:EXT:extension_key/Resources/Private/Language/locallang.xlf:settings.mySetting'
description: 'LLL:EXT:extension_key/Resources/Private/Language/locallang.xlf:settings.mySetting.description'
# Boolean checkbox
enableFeature:
type: bool
default: false
label: 'Enable Feature'
# Integer input
itemsPerPage:
type: int
default: 10
label: 'Items per page'
validators:
- name: NumberRange
options:
minimum: 1
maximum: 100
# Select dropdown
layout:
type: string
default: 'default'
label: 'Layout'
enum:
default: 'Default'
compact: 'Compact'
detailed: 'Detailed'
```
### Benefits of Site Sets
1. **Modular Configuration**: Split configuration into focused, reusable sets
2. **Dependency Management**: Declare dependencies on other sets
3. **Override Capability**: Sites can override set settings without editing files
4. **Type Safety**: Settings are validated with defined types
5. **Better UX**: Settings UI auto-generated from definitions
6. **Version Control**: Configuration changes tracked properly
### Migration from Static TypoScript
```php
// ❌ Old: Static TypoScript includes (TYPO3 12 and earlier)
Configuration/TCA/Overrides/sys_template.php:
<?php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile(
'extension_key',
'Configuration/TypoScript',
'Extension Name'
);
```
```yaml
# ✅ New: Site Sets (TYPO3 13+)
Configuration/Sets/ExtensionKey/config.yaml:
name: vendor/extension-key
label: Extension Name
```
**Site Sets Conformance Checklist:**
- [ ] Configuration/Sets/ directory exists
- [ ] At least one base set with config.yaml
- [ ] settings.definitions.yaml defines all extension settings
- [ ] Set names follow vendor/package naming convention
- [ ] Dependencies declared in config.yaml
- [ ] Labels use LLL: references for translations
- [ ] Settings have appropriate type validation
## Advanced Services.yaml Patterns
Beyond basic service registration, modern TYPO3 extensions use advanced Services.yaml patterns.
### Event Listeners
```yaml
# ✅ Right: Event listener registration
services:
Vendor\ExtensionKey\EventListener\HrefLangEventListener:
tags:
- name: event.listener
identifier: 'ext-extension-key/modify-hreflang'
event: TYPO3\CMS\Frontend\Event\ModifyHrefLangTagsEvent
method: '__invoke'
# Multiple listeners for same event
Vendor\ExtensionKey\EventListener\PageCacheListener:
tags:
- name: event.listener
identifier: 'ext-extension-key/cache-before'
event: TYPO3\CMS\Core\Cache\Event\BeforePageCacheIdentifierIsHashedEvent
- name: event.listener
identifier: 'ext-extension-key/cache-after'
event: TYPO3\CMS\Core\Cache\Event\AfterPageCacheIdentifierIsHashedEvent
```
### Console Commands
```yaml
# ✅ Right: Console command registration
services:
Vendor\ExtensionKey\Command\ProxyClassRebuildCommand:
tags:
- name: 'console.command'
command: 'extension:rebuildProxyClasses'
description: 'Rebuild Extbase proxy classes'
schedulable: false # Cannot be run via scheduler
Vendor\ExtensionKey\Command\CleanupCommand:
tags:
- name: 'console.command'
command: 'extension:cleanup'
description: 'Clean up old records'
schedulable: true # Can be run via scheduler
hidden: false # Visible in command list
```
### Data Processors
```yaml
# ✅ Right: Data processor registration for Fluid templates
services:
Vendor\ExtensionKey\DataProcessing\AddNewsToMenuProcessor:
tags:
- name: 'data.processor'
identifier: 'add-news-to-menu'
Vendor\ExtensionKey\DataProcessing\CategoryProcessor:
tags:
- name: 'data.processor'
identifier: 'category-processor'
```
### Cache Services
```yaml
# ✅ Right: Cache service configuration
services:
cache.extension_custom:
class: TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
factory:
- '@TYPO3\CMS\Core\Cache\CacheManager'
- 'getCache'
arguments:
- 'extension_custom'
```
### Advanced Service Patterns
```yaml
# ✅ Right: Comprehensive Services.yaml with advanced patterns
services:
_defaults:
autowire: true
autoconfigure: true
public: false
# Auto-register all classes
Vendor\ExtensionKey\:
resource: '../Classes/*'
exclude:
- '../Classes/Domain/Model/*' # Exclude Extbase models
# Event Listeners
Vendor\ExtensionKey\EventListener\NewsListActionListener:
tags:
- name: event.listener
identifier: 'ext-extension-key/news-list'
event: Vendor\ExtensionKey\Event\NewsListActionEvent
# Console Commands
Vendor\ExtensionKey\Command\ImportCommand:
tags:
- name: 'console.command'
command: 'news:import'
description: 'Import news from external source'
schedulable: true
# Data Processors
Vendor\ExtensionKey\DataProcessing\MenuProcessor:
tags:
- name: 'data.processor'
identifier: 'news-menu-processor'
# Cache Factory
cache.news_category:
class: TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache']
arguments: ['news_category']
# ViewHelper registration (if needed for testing)
Vendor\ExtensionKey\ViewHelpers\FormatViewHelper:
public: true
```
**Advanced Services.yaml Conformance Checklist:**
- [ ] Event listeners registered with proper tags
- [ ] Console commands tagged with schedulable flag
- [ ] Data processors registered with unique identifiers
- [ ] Cache services use factory pattern
- [ ] ViewHelpers marked public if needed externally
- [ ] Service tags include all required attributes (identifier, event, method)
- [ ] Commands have meaningful names and descriptions
## PSR-15 Middleware
### Middleware Structure
```php
// ✅ Right: PSR-15 middleware implementation
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Middleware;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
final class StatusCheckMiddleware implements MiddlewareInterface
{
public function __construct(
private readonly ResponseFactoryInterface $responseFactory
) {}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
// Check for specific condition
if (($request->getQueryParams()['status'] ?? null) === 'check') {
$response = $this->responseFactory->createResponse(200, 'OK');
$response->getBody()->write(json_encode([
'status' => 'ok',
'message' => 'System is healthy'
]));
return $response->withHeader('Content-Type', 'application/json');
}
// Pass to next middleware
return $handler->handle($request);
}
}
```
### Middleware Registration
```php
// Configuration/RequestMiddlewares.php
<?php
return [
'frontend' => [
'vendor/extension-key/status-check' => [
'target' => \Vendor\ExtensionKey\Middleware\StatusCheckMiddleware::class,
'before' => [
'typo3/cms-frontend/page-resolver',
],
'after' => [
'typo3/cms-core/normalized-params-attribute',
],
],
],
];
```
## Extbase Architecture
### Domain Models
```php
// ✅ Right: Extbase domain model
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class Product extends AbstractEntity
{
protected string $title = '';
protected float $price = 0.0;
protected bool $available = true;
public function getTitle(): string
{
return $this->title;
}
public function setTitle(string $title): void
{
$this->title = $title;
}
public function getPrice(): float
{
return $this->price;
}
public function setPrice(float $price): void
{
$this->price = $price;
}
public function isAvailable(): bool
{
return $this->available;
}
public function setAvailable(bool $available): void
{
$this->available = $available;
}
}
```
### Repositories
```php
// ✅ Right: Extbase repository with dependency injection
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Domain\Repository;
use TYPO3\CMS\Extbase\Persistence\Repository;
use Vendor\ExtensionKey\Domain\Model\Product;
class ProductRepository extends Repository
{
/**
* Find products by price range
*
* @param float $minPrice
* @param float $maxPrice
* @return array<Product>
*/
public function findByPriceRange(float $minPrice, float $maxPrice): array
{
$query = $this->createQuery();
$query->matching(
$query->logicalAnd(
$query->greaterThanOrEqual('price', $minPrice),
$query->lessThanOrEqual('price', $maxPrice)
)
);
return $query->execute()->toArray();
}
}
```
### Controllers
```php
// ✅ Right: Extbase controller with dependency injection
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
final class ProductController extends ActionController
{
public function __construct(
private readonly ProductRepository $productRepository
) {}
public function listAction(): ResponseInterface
{
$products = $this->productRepository->findAll();
$this->view->assign('products', $products);
return $this->htmlResponse();
}
public function showAction(int $productId): ResponseInterface
{
$product = $this->productRepository->findByUid($productId);
$this->view->assign('product', $product);
return $this->htmlResponse();
}
}
```
### Validators
```php
// ✅ Right: Extbase validator with dependency injection
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Domain\Validator;
use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
class UniqueProductTitleValidator extends AbstractValidator
{
public function __construct(
private readonly ProductRepository $productRepository
) {}
protected function isValid(mixed $value): void
{
if (!is_string($value)) {
$this->addError('Value must be a string', 1234567890);
return;
}
$existingProduct = $this->productRepository->findOneByTitle($value);
if ($existingProduct !== null) {
$this->addError(
'Product with title "%s" already exists',
1234567891,
[$value]
);
}
}
}
```
## Common Patterns
### Factory Pattern
```php
// ✅ Right: Factory for Connection objects
services:
Vendor\ExtensionKey\Domain\Repository\MyRepository:
factory: ['@TYPO3\CMS\Core\Database\ConnectionPool', 'getConnectionForTable']
arguments:
- 'my_table'
```
### Singleton Services
```php
// ✅ Right: Use DI container, not Singleton pattern
// Services are automatically singleton by default
// ❌ Wrong: Don't use GeneralUtility::makeInstance() for new code
use TYPO3\CMS\Core\Utility\GeneralUtility;
$service = GeneralUtility::makeInstance(MyService::class); // Deprecated
```
### PSR Interfaces
```php
// ✅ Right: Use PSR interfaces
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Client\ClientInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use Psr\Clock\ClockInterface;
// Inject PSR-compliant services
public function __construct(
private readonly LoggerInterface $logger,
private readonly ClockInterface $clock
) {}
```
## Anti-Patterns to Avoid
### ❌ Wrong: Direct instantiation
```php
$repository = new ProductRepository(); // Missing dependencies
```
### ❌ Wrong: Using GeneralUtility::makeInstance()
```php
use TYPO3\CMS\Core\Utility\GeneralUtility;
$repository = GeneralUtility::makeInstance(ProductRepository::class);
```
### ❌ Wrong: Global state access
```php
$user = $GLOBALS['BE_USER']; // Avoid global state
$typoScript = $GLOBALS['TSFE']->tmpl->setup;
```
### ✅ Right: Dependency injection
```php
public function __construct(
private readonly ProductRepository $repository,
private readonly Context $context
) {}
```
## Conformance Checklist
### Basic Dependency Injection
- [ ] Constructor injection used for all dependencies
- [ ] Services registered in Configuration/Services.yaml
- [ ] No direct class instantiation (new MyClass())
- [ ] No GeneralUtility::makeInstance() for new services
- [ ] PSR interfaces used (ResponseInterface, LoggerInterface, etc.)
- [ ] No global state access ($GLOBALS)
### PSR-14 Events (Mandatory)
- [ ] PSR-14 events used instead of hooks
- [ ] Event classes are immutable with proper getters/setters
- [ ] Event listeners use #[AsEventListener] attribute or Services.yaml tags
- [ ] Event classes use `final` keyword (TYPO3 13+)
- [ ] Event classes use `readonly` for immutable properties (TYPO3 13+)
### TYPO3 13 Site Sets (Mandatory for TYPO3 13)
- [ ] Configuration/Sets/ directory exists
- [ ] Base set has config.yaml with proper metadata
- [ ] settings.definitions.yaml defines extension settings with types
- [ ] Set names follow vendor/package convention
- [ ] Dependencies declared in config.yaml
### Advanced Services.yaml (Mandatory)
- [ ] Event listeners registered with proper tags
- [ ] Console commands tagged with schedulable flag
- [ ] Data processors registered with unique identifiers
- [ ] Cache services use factory pattern
- [ ] Service tags include all required attributes
### PSR-15 Middleware
- [ ] PSR-15 middlewares registered in RequestMiddlewares.php
- [ ] Middleware ordering defined with before/after
### Extbase Architecture
- [ ] Extbase models extend AbstractEntity
- [ ] Repositories extend Repository base class
- [ ] Controllers use constructor injection
- [ ] Validators extend AbstractValidator
### Factory Pattern
- [ ] Factory pattern for complex object creation (e.g., Connection objects)

View File

@@ -0,0 +1,421 @@
# runTests.sh Validation Guide
**Purpose:** Validate Build/Scripts/runTests.sh against TYPO3 Best Practices (Tea extension reference)
## Why Validate runTests.sh?
The `runTests.sh` script is the **central orchestration tool** for TYPO3 extension quality workflows. An outdated or misconfigured script can lead to:
- ❌ Testing with wrong PHP/TYPO3 versions (false positives/negatives)
- ❌ Missing database compatibility issues
- ❌ Inconsistent local vs CI environments
- ❌ Developer confusion with incorrect defaults
## Reference Implementation
**Source of Truth:** https://github.com/TYPO3BestPractices/tea/blob/main/Build/Scripts/runTests.sh
The Tea extension maintains the canonical runTests.sh implementation, updated for latest TYPO3 standards.
## Critical Validation Points
### 1. PHP Version Configuration
**Check Lines ~318 and ~365:**
```bash
# Default PHP version
PHP_VERSION="X.X"
# PHP version validation regex
if ! [[ ${PHP_VERSION} =~ ^(X.X|X.X|X.X)$ ]]; then
```
**Validation:**
1. Read extension's composer.json `require.php` constraint
2. Extract minimum PHP version (e.g., `^8.2` → minimum 8.2)
3. Verify runTests.sh default matches minimum
4. Verify version regex includes all supported versions
**Example Check:**
```bash
# Extension composer.json
"require": {
"php": "^8.2 || ^8.3 || ^8.4"
}
# runTests.sh SHOULD have:
PHP_VERSION="8.2" # ✅ Matches minimum
if ! [[ ${PHP_VERSION} =~ ^(8.2|8.3|8.4)$ ]]; then # ✅ All supported
# runTests.sh SHOULD NOT have:
PHP_VERSION="7.4" # ❌ Below minimum
if ! [[ ${PHP_VERSION} =~ ^(7.4|8.0|8.1|8.2|8.3)$ ]]; then # ❌ Includes unsupported
```
**Severity:** 🔴 **High** - Testing with wrong PHP version invalidates results
### 2. TYPO3 Version Configuration
**Check Lines ~315 and ~374:**
```bash
# Default TYPO3 version
TYPO3_VERSION="XX"
# TYPO3 version validation
if ! [[ ${TYPO3_VERSION} =~ ^(11|12|13)$ ]]; then
```
**Validation:**
1. Read extension's composer.json TYPO3 core dependency
2. Extract target TYPO3 version (e.g., `^13.4` → TYPO3 13)
3. Verify runTests.sh default matches target
4. Check composerInstallHighest/Lowest version constraints
**Example Check:**
```bash
# Extension composer.json
"require": {
"typo3/cms-core": "^13.4"
}
# runTests.sh SHOULD have:
TYPO3_VERSION="13" # ✅ Matches target
# In composerInstallHighest (line ~530):
if [ ${TYPO3_VERSION} -eq 13 ]; then
composer require --no-ansi --no-interaction --no-progress --no-install \
typo3/cms-core:^13.4 # ✅ Matches composer.json
# runTests.sh SHOULD NOT have:
TYPO3_VERSION="11" # ❌ Below target
```
**Severity:** 🔴 **High** - Testing against wrong TYPO3 version
### 3. Database Version Support
**Check Lines ~48-107 (handleDbmsOptions function):**
```bash
mariadb)
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="X.X"
if ! [[ ${DBMS_VERSION} =~ ^(10.2|10.3|...|11.1)$ ]]; then
```
**Validation:**
1. Check MariaDB, MySQL, PostgreSQL version lists are current
2. Verify default versions are maintained (not EOL)
3. Cross-reference with TYPO3 core database support matrix
**Current Database Support (TYPO3 13):**
| DBMS | Supported Versions | Default | EOL Status |
|------|-------------------|---------|------------|
| MariaDB | 10.4-10.11, 11.0-11.4 | 10.11 | 10.4+ maintained |
| MySQL | 8.0, 8.1, 8.2, 8.3, 8.4 | 8.0 | 8.0 maintained until 2026 |
| PostgreSQL | 10-16 | 16 | 10-11 EOL, 12+ maintained |
| SQLite | 3.x | 3.x | Always latest |
**Example Check:**
```bash
# runTests.sh MariaDB (line ~48)
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10.11" # ✅ LTS version
if ! [[ ${DBMS_VERSION} =~ ^(10.4|10.5|10.6|10.11|11.0|11.1|11.2|11.3|11.4)$ ]]; then
# ❌ BAD - EOL version as default:
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10.2" # EOL 2023
# runTests.sh PostgreSQL (line ~79)
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="16" # ✅ Latest stable
if ! [[ ${DBMS_VERSION} =~ ^(10|11|12|13|14|15|16)$ ]]; then
```
**Severity:** 🟡 **Medium** - May miss database-specific compatibility issues
### 4. Network Name Configuration
**Check Line ~331:**
```bash
NETWORK="extension-name-${SUFFIX}"
```
**Validation:**
1. Should match extension key or composer package name
2. Should NOT be hardcoded to "friendsoftypo3-tea" (copy-paste artifact)
**Example Check:**
```bash
# Extension key: rte_ckeditor_image
# Composer package: netresearch/rte-ckeditor-image
# ✅ Good options:
NETWORK="rte-ckeditor-image-${SUFFIX}"
NETWORK="netresearch-rte-ckeditor-image-${SUFFIX}"
# ❌ Bad (copy-paste from Tea):
NETWORK="friendsoftypo3-tea-${SUFFIX}"
```
**Severity:** 🟢 **Low** - Cosmetic, but indicates lack of customization
### 5. Test Suite Commands
**Check Lines ~580, ~620 (functional and unit test commands):**
```bash
functional)
COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml ...)
unit)
COMMAND=(.Build/bin/phpunit -c Build/phpunit/UnitTests.xml ...)
```
**Validation:**
1. Paths match actual PHPUnit config locations
2. Config files exist and are properly named
3. Exclude groups match available database types
**Example Check:**
```bash
# Verify config files exist:
ls -la Build/phpunit/UnitTests.xml # Must exist
ls -la Build/phpunit/FunctionalTests.xml # Must exist
# Check command paths:
COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml ...)
└─────┬─────┘ └──────────┬───────────┘
✅ Matches ✅ Matches actual
.Build/bin/ Build/phpunit/
from composer.json directory structure
```
**Severity:** 🔴 **High** - Tests won't run if paths are wrong
### 6. Container Image Versions
**Check Lines ~446-451:**
```bash
IMAGE_PHP="ghcr.io/typo3/core-testing-$(echo "php${PHP_VERSION}" | sed -e 's/\.//'):latest"
IMAGE_ALPINE="docker.io/alpine:3.8"
IMAGE_DOCS="ghcr.io/typo3-documentation/render-guides:latest"
IMAGE_MARIADB="docker.io/mariadb:${DBMS_VERSION}"
IMAGE_MYSQL="docker.io/mysql:${DBMS_VERSION}"
IMAGE_POSTGRES="docker.io/postgres:${DBMS_VERSION}-alpine"
```
**Validation:**
1. PHP testing image uses official TYPO3 images
2. Alpine version is reasonably current (not ancient)
3. Documentation renderer is latest official TYPO3 image
**Severity:** 🟢 **Low** - Usually works, but outdated Alpine may have issues
## Conformance Evaluation Workflow
### Step 1: Extract Extension Requirements
```bash
# Read composer.json
cat composer.json | jq -r '.require.php' # e.g., "^8.2 || ^8.3 || ^8.4"
cat composer.json | jq -r '.require."typo3/cms-core"' # e.g., "^13.4"
# Parse minimum versions
MIN_PHP=$(echo "^8.2 || ^8.3" | grep -oE '[0-9]+\.[0-9]+' | head -1) # 8.2
TARGET_TYPO3=$(echo "^13.4" | grep -oE '[0-9]+') # 13
```
### Step 2: Validate runTests.sh Defaults
```bash
# Check PHP version default (line ~318)
grep '^PHP_VERSION=' Build/Scripts/runTests.sh
# Expected: PHP_VERSION="8.2" (matches MIN_PHP)
# Check TYPO3 version default (line ~315)
grep '^TYPO3_VERSION=' Build/Scripts/runTests.sh
# Expected: TYPO3_VERSION="13" (matches TARGET_TYPO3)
```
### Step 3: Validate PHP Version Regex
```bash
# Extract PHP version regex (line ~365)
grep -A 2 'if ! \[\[ \${PHP_VERSION}' Build/Scripts/runTests.sh
# Expected pattern for "^8.2 || ^8.3 || ^8.4":
# ^(8.2|8.3|8.4)$
# ❌ Outdated pattern:
# ^(7.4|8.0|8.1|8.2|8.3)$
```
### Step 4: Validate TYPO3 Version Constraints in Composer Install
```bash
# Check composerInstallHighest TYPO3 13 block (line ~530)
sed -n '/if \[ \${TYPO3_VERSION} -eq 13 \];/,/fi/p' Build/Scripts/runTests.sh
# Should match composer.json requirements:
# typo3/cms-core:^13.4
# typo3/cms-backend:^13.4
# etc.
```
### Step 5: Validate Network Name
```bash
# Check network name (line ~331)
grep '^NETWORK=' Build/Scripts/runTests.sh
# Extract extension key from composer.json or ext_emconf.php
EXT_KEY=$(jq -r '.extra.typo3.cms."extension-key"' composer.json)
# Expected: NETWORK="${EXT_KEY}-${SUFFIX}" or similar
# ❌ Wrong: NETWORK="friendsoftypo3-tea-${SUFFIX}"
```
## Automated Validation Script
Create `scripts/validate-runtests.sh`:
```bash
#!/bin/bash
set -e
echo "🔍 Validating Build/Scripts/runTests.sh against extension requirements..."
# Extract requirements
MIN_PHP=$(jq -r '.require.php' composer.json | grep -oE '[0-9]+\.[0-9]+' | head -1)
TARGET_TYPO3=$(jq -r '.require."typo3/cms-core"' composer.json | grep -oE '^[0-9]+' | head -1)
EXT_KEY=$(jq -r '.extra.typo3.cms."extension-key"' composer.json)
echo "📋 Extension Requirements:"
echo " PHP: ${MIN_PHP}+"
echo " TYPO3: ${TARGET_TYPO3}"
echo " Extension Key: ${EXT_KEY}"
echo ""
# Validate PHP version default
RUNTESTS_PHP=$(grep '^PHP_VERSION=' Build/Scripts/runTests.sh | cut -d'"' -f2)
if [ "${RUNTESTS_PHP}" != "${MIN_PHP}" ]; then
echo "❌ PHP version mismatch: runTests.sh uses ${RUNTESTS_PHP}, should be ${MIN_PHP}"
exit 1
else
echo "✅ PHP version default: ${RUNTESTS_PHP}"
fi
# Validate TYPO3 version default
RUNTESTS_TYPO3=$(grep '^TYPO3_VERSION=' Build/Scripts/runTests.sh | cut -d'"' -f2)
if [ "${RUNTESTS_TYPO3}" != "${TARGET_TYPO3}" ]; then
echo "❌ TYPO3 version mismatch: runTests.sh uses ${RUNTESTS_TYPO3}, should be ${TARGET_TYPO3}"
exit 1
else
echo "✅ TYPO3 version default: ${RUNTESTS_TYPO3}"
fi
# Validate network name
NETWORK_NAME=$(grep '^NETWORK=' Build/Scripts/runTests.sh | cut -d'"' -f2 | sed 's/-${SUFFIX}$//')
if [[ "${NETWORK_NAME}" == "friendsoftypo3-tea" ]]; then
echo "⚠️ Network name is copy-paste from Tea extension: ${NETWORK_NAME}"
echo " Should be: ${EXT_KEY}-\${SUFFIX}"
else
echo "✅ Network name: ${NETWORK_NAME}-\${SUFFIX}"
fi
echo ""
echo "✅ runTests.sh validation complete"
```
## Conformance Report Integration
### When evaluating runTests.sh:
**In "Best Practices" Section:**
```markdown
### Build Scripts
**runTests.sh Analysis:**
- ✅ Script present and executable
- ✅ PHP version default matches composer.json minimum (8.2)
- ✅ TYPO3 version default matches target (13)
- ✅ PHP version regex includes all supported versions (8.2, 8.3, 8.4)
- ⚠️ Network name uses Tea extension default (cosmetic issue)
- ✅ Test suite commands match actual file structure
- ✅ Database version support is current
**Or with issues:**
- ❌ PHP version default (7.4) below extension minimum (8.2)
- File: Build/Scripts/runTests.sh:318
- Severity: High
- Fix: Change `PHP_VERSION="7.4"` to `PHP_VERSION="8.2"`
- ❌ TYPO3 version default (11) below extension target (13)
- File: Build/Scripts/runTests.sh:315
- Severity: High
- Fix: Change `TYPO3_VERSION="11"` to `TYPO3_VERSION="13"`
- ❌ PHP version regex includes unsupported versions
- File: Build/Scripts/runTests.sh:365
- Current: `^(7.4|8.0|8.1|8.2|8.3)$`
- Expected: `^(8.2|8.3|8.4)$`
- Severity: Medium
- Fix: Remove unsupported versions from regex
```
## Scoring Impact
**Best Practices Score Deductions:**
| Issue | Severity | Score Impact |
|-------|----------|--------------|
| PHP version default outdated | High | -3 points |
| TYPO3 version default outdated | High | -3 points |
| PHP version regex includes unsupported | Medium | -2 points |
| Database versions EOL | Medium | -2 points |
| Network name copy-paste | Low | -1 point |
| Missing runTests.sh | Critical | -10 points |
**Maximum deduction for runTests.sh issues:** -6 points (out of 20 for Best Practices)
## Quick Reference Checklist
**When evaluating Build/Scripts/runTests.sh:**
```
□ File exists and is executable
□ PHP_VERSION default matches composer.json minimum
□ TYPO3_VERSION default matches composer.json target
□ PHP version regex matches composer.json constraint exactly
□ TYPO3_VERSION regex includes supported versions only
□ Database version lists are current (not EOL)
□ Database version defaults are maintained LTS versions
□ Network name is customized (not "friendsoftypo3-tea")
□ Test suite paths match actual directory structure
□ Container images use official TYPO3 testing images
```
**Comparison Strategy:**
1. Download latest Tea runTests.sh as reference
2. Compare line-by-line for structural differences
3. Validate version-specific values against extension requirements
4. Flag any outdated patterns or hardcoded Tea-specific values
## Resources
- **Tea Extension runTests.sh:** https://github.com/TYPO3BestPractices/tea/blob/main/Build/Scripts/runTests.sh
- **TYPO3 Testing Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/
- **Database Compatibility:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Database/

View File

@@ -0,0 +1,558 @@
# TYPO3 Testing Standards
**Source:** TYPO3 Core API Reference - Testing
**Purpose:** Unit, functional, and acceptance testing standards for TYPO3 extensions
## Testing Framework
TYPO3 uses **typo3/testing-framework** for comprehensive testing:
```bash
# Install testing framework
composer require --dev \
"typo3/testing-framework":"^8.0.9" \
"phpunit/phpunit":"^10.5"
```
## Unit Testing
### Unit Test Structure
```php
// ✅ Right: Proper unit test structure
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Tests\Unit\Service;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
use Vendor\ExtensionKey\Service\CalculationService;
class CalculationServiceTest extends UnitTestCase
{
private CalculationService $subject;
protected function setUp(): void
{
parent::setUp();
$this->subject = new CalculationService();
}
/**
* @test
*/
public function addReturnsCorrectSum(): void
{
$result = $this->subject->add(2, 3);
$this->assertEquals(5, $result);
}
/**
* @test
*/
public function multiplyReturnsCorrectProduct(): void
{
$result = $this->subject->multiply(4, 5);
$this->assertEquals(20, $result);
}
}
```
### PHPUnit Configuration
```xml
<!-- Build/phpunit/UnitTests.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="../../vendor/typo3/testing-framework/Resources/Core/Build/UnitTestsBootstrap.php"
colors="true"
beStrictAboutTestsThatDoNotTestAnything="true"
failOnWarning="true"
failOnRisky="true"
stopOnFailure="false"
>
<testsuites>
<testsuite name="Unit tests">
<directory>../../Tests/Unit/</directory>
</testsuite>
</testsuites>
</phpunit>
```
### Running Unit Tests
```bash
# Direct execution
vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
# DDEV execution
ddev exec php vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
# Run specific test
vendor/bin/phpunit -c Build/phpunit/UnitTests.xml --filter "CalculationServiceTest"
```
### Unit Test Best Practices
**✅ Do:**
- Test single units (methods, functions) in isolation
- Mock external dependencies
- Test edge cases and boundary conditions
- Use descriptive test method names
- Follow naming: `methodName<Condition>Returns<Expected>`
- Keep tests fast (no database, no external services)
**❌ Don't:**
- Access database in unit tests
- Depend on file system
- Make HTTP requests
- Test framework internals
- Write integration tests as unit tests
## Functional Testing
### Functional Test Structure
```php
// ✅ Right: Proper functional test structure
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Tests\Functional\Domain\Repository;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
class ProductRepositoryTest extends FunctionalTestCase
{
protected array $testExtensionsToLoad = [
'typo3conf/ext/my_extension',
];
protected ProductRepository $subject;
protected function setUp(): void
{
parent::setUp();
// Load test data
$this->importCSVDataSet(__DIR__ . '/Fixtures/products.csv');
// Set up backend user
$this->setUpBackendUser(1);
// Initialize subject
$this->subject = $this->get(ProductRepository::class);
}
/**
* @test
*/
public function findAllReturnsAllProducts(): void
{
$products = $this->subject->findAll();
$this->assertCount(3, $products);
}
/**
* @test
*/
public function findByPriceRangeReturnsMatchingProducts(): void
{
$products = $this->subject->findByPriceRange(10.0, 50.0);
$this->assertCount(2, $products);
}
}
```
### PHPUnit Functional Configuration
```xml
<!-- Build/phpunit/FunctionalTests.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="../../vendor/typo3/testing-framework/Resources/Core/Build/FunctionalTestsBootstrap.php"
colors="true"
beStrictAboutTestsThatDoNotTestAnything="true"
failOnWarning="true"
failOnRisky="true"
stopOnFailure="false"
>
<testsuites>
<testsuite name="Functional tests">
<directory>../../Tests/Functional/</directory>
</testsuite>
</testsuites>
</phpunit>
```
### Running Functional Tests
```bash
# With MySQL/MariaDB
ddev exec \
typo3DatabaseDriver='mysqli' \
typo3DatabaseHost='db' \
typo3DatabasePort=3306 \
typo3DatabaseUsername='root' \
typo3DatabasePassword='root' \
typo3DatabaseName='func' \
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
# With SQLite (simpler)
ddev exec \
typo3DatabaseDriver=pdo_sqlite \
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
# With PostgreSQL
ddev exec \
typo3DatabaseDriver='pdo_pgsql' \
typo3DatabaseHost='postgres' \
typo3DatabasePort=5432 \
typo3DatabaseUsername='postgres' \
typo3DatabasePassword='postgres' \
typo3DatabaseName='func' \
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
```
### Test Data Fixtures
```csv
# Tests/Functional/Fixtures/products.csv
tx_myext_product,uid,pid,title,price,available
,1,0,Product A,29.99,1
,2,0,Product B,49.99,1
,3,0,Product C,99.99,0
```
### Loading Extensions in Tests
```php
// Load extension under test
protected array $testExtensionsToLoad = [
'typo3conf/ext/my_extension',
];
// Load additional core extensions
protected array $coreExtensionsToLoad = [
'typo3/cms-workspaces',
];
// Load fixture extensions
protected array $testExtensionsToLoad = [
'typo3conf/ext/my_extension',
'typo3conf/ext/my_extension/Tests/Functional/Fixtures/Extensions/fixture_extension',
];
```
## Acceptance Testing
### Codeception Setup
```yaml
# Tests/codeception.yml
namespace: Vendor\ExtensionKey\Tests\Acceptance\Support
suites:
acceptance:
actor: AcceptanceTester
path: .
modules:
enabled:
- Asserts
- WebDriver:
url: https://myproject.ddev.site
browser: chrome
host: ddev-myproject-chrome
wait: 1
window_size: 1280x1024
extensions:
enabled:
- Codeception\Extension\RunFailed
- Codeception\Extension\Recorder
paths:
tests: Acceptance
output: ../var/log/_output
data: .
support: Acceptance/Support
settings:
shuffle: false
lint: true
colors: true
```
### Acceptance Test Structure
```php
// ✅ Right: Backend acceptance test
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Tests\Acceptance\Backend;
use Vendor\ExtensionKey\Tests\Acceptance\Support\BackendTester;
use TYPO3\TestingFramework\Core\Acceptance\Helper\Topbar;
class ModuleCest
{
public function _before(BackendTester $I): void
{
$I->useExistingSession('admin');
}
/**
* @param BackendTester $I
*/
public function moduleCanBeAccessed(BackendTester $I): void
{
$I->click(Topbar::$dropdownToggleSelector, '#typo3-cms-backend-backend-toolbaritems-helptoolbaritem');
$I->canSee('My Module');
$I->click('My Module');
$I->switchToContentFrame();
$I->see('Module Content', 'h1');
}
/**
* @param BackendTester $I
*/
public function formSubmissionWorks(BackendTester $I): void
{
$I->amOnPage('/typo3/module/my-module');
$I->switchToContentFrame();
$I->fillField('title', 'Test Title');
$I->click('Save');
$I->see('Record saved successfully');
}
}
```
### Frontend Acceptance Test
```php
// ✅ Right: Frontend acceptance test
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Tests\Acceptance\Frontend;
use Vendor\ExtensionKey\Tests\Acceptance\Support\AcceptanceTester;
class FrontendPagesCest
{
/**
* @param AcceptanceTester $I
*/
public function homepageIsRendered(AcceptanceTester $I): void
{
$I->amOnPage('/');
$I->see('Welcome to TYPO3');
$I->seeElement('h1');
}
/**
* @param AcceptanceTester $I
*/
public function navigationWorks(AcceptanceTester $I): void
{
$I->amOnPage('/');
$I->click('Products');
$I->see('Our Products', 'h1');
$I->seeInCurrentUrl('/products');
}
}
```
### Running Acceptance Tests
```bash
# Run acceptance tests via DDEV
ddev exec bin/codecept run acceptance -d -c Tests/codeception.yml
# Run specific test
ddev exec bin/codecept run acceptance ModuleCest -c Tests/codeception.yml
# Generate new test
ddev exec bin/codecept generate:cest acceptance MyNewTest -c Tests/codeception.yml
```
## Test Organization
### Directory Structure
```
Tests/
├── Unit/
│ ├── Controller/
│ │ └── ProductControllerTest.php
│ ├── Domain/
│ │ ├── Model/
│ │ │ └── ProductTest.php
│ │ └── Repository/
│ │ └── ProductRepositoryTest.php
│ └── Service/
│ └── CalculationServiceTest.php
├── Functional/
│ ├── Domain/
│ │ └── Repository/
│ │ ├── ProductRepositoryTest.php
│ │ └── Fixtures/
│ │ └── products.csv
│ └── Controller/
│ └── ProductControllerTest.php
└── Acceptance/
├── Backend/
│ └── ModuleCest.php
├── Frontend/
│ └── FrontendPagesCest.php
└── Support/
├── AcceptanceTester.php
└── BackendTester.php
```
### Naming Conventions
**Unit Tests:**
- Pattern: `<ClassName>Test.php`
- Example: `ProductRepository.php``ProductRepositoryTest.php`
- Location: Mirror `Classes/` structure in `Tests/Unit/`
**Functional Tests:**
- Pattern: `<ClassName>Test.php`
- Example: `ProductRepository.php``ProductRepositoryTest.php`
- Location: Mirror `Classes/` structure in `Tests/Functional/`
**Acceptance Tests:**
- Pattern: `<Feature>Cest.php`
- Example: `ModuleCest.php`, `LoginCest.php`
- Location: `Tests/Acceptance/Backend/` or `Tests/Acceptance/Frontend/`
## PHPUnit Attributes (PHP 8.0+)
```php
// ✅ Right: Using PHPUnit attributes
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\Tests\Unit\Service;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProvider;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
class CalculationServiceTest extends UnitTestCase
{
#[Test]
public function addReturnsCorrectSum(): void
{
$this->assertEquals(5, $this->subject->add(2, 3));
}
public static function priceDataProvider(): \Generator
{
yield 'standard price' => [
'price' => 100.0,
'taxRate' => 0.19,
'expected' => 119.0,
];
yield 'zero price' => [
'price' => 0.0,
'taxRate' => 0.19,
'expected' => 0.0,
];
}
#[Test]
#[DataProvider('priceDataProvider')]
public function calculatePriceWithTax(float $price, float $taxRate, float $expected): void
{
$result = $this->subject->calculatePriceWithTax($price, $taxRate);
$this->assertEquals($expected, $result);
}
}
```
## CI/CD Integration
### GitHub Actions
```yaml
# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.1', '8.2', '8.3']
typo3: ['12.4', '13.0']
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
- name: Install dependencies
run: composer install
- name: Lint PHP
run: find . -name \*.php ! -path "./vendor/*" -exec php -l {} \;
- name: Unit Tests
run: vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
- name: Functional Tests
run: |
typo3DatabaseDriver=pdo_sqlite \
vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
```
## Conformance Checklist
### Unit Tests
- [ ] Unit tests extend `UnitTestCase`
- [ ] Tests located in `Tests/Unit/` mirroring `Classes/`
- [ ] Test files named `<ClassName>Test.php`
- [ ] No database access in unit tests
- [ ] No file system access in unit tests
- [ ] All public methods tested
- [ ] Edge cases and boundaries tested
- [ ] #[Test] attribute or @test annotation used
### Functional Tests
- [ ] Functional tests extend `FunctionalTestCase`
- [ ] Tests located in `Tests/Functional/`
- [ ] `setUp()` calls `parent::setUp()` first
- [ ] Extensions loaded via `$testExtensionsToLoad`
- [ ] Test data loaded via `importCSVDataSet()`
- [ ] Database operations tested
- [ ] Backend user initialized when needed
### Acceptance Tests
- [ ] Acceptance tests use Codeception
- [ ] Tests located in `Tests/Acceptance/`
- [ ] Test files named `<Feature>Cest.php`
- [ ] codeception.yml properly configured
- [ ] Backend tests use `useExistingSession('admin')`
- [ ] Frame switching used correctly
- [ ] Tests verify user-visible behavior
### General
- [ ] PHPUnit configuration files present
- [ ] All tests pass locally
- [ ] CI/CD pipeline configured
- [ ] Test coverage >70% for new code
- [ ] Data providers use named arguments
- [ ] Descriptive test method names

View File

@@ -0,0 +1,408 @@
# TYPO3 v13 Deprecations and Modern Alternatives
**Sources:** TYPO3 Core API Reference v13.4
**Purpose:** Track v13 deprecations, migration paths, and modern configuration approaches
## Deprecated Files (v13.1+)
### ext_typoscript_constants.typoscript
**Status:** DEPRECATED since TYPO3 v13.1
**Purpose:** Provided global TypoScript constants
**Migration Paths:**
**1. Preferred: Site Settings Definitions**
```yaml
# Configuration/Sets/MySet/settings.definitions.yaml
settings:
myext:
itemsPerPage:
type: int
default: 10
label: 'Items per page'
```
**2. For Global Constants:**
```php
// ext_localconf.php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScript(
'my_extension',
'constants',
'@import "EXT:my_extension/Configuration/TypoScript/constants.typoscript"'
);
```
**Detection:**
```bash
[ -f "ext_typoscript_constants.typoscript" ] && echo "⚠️ DEPRECATED: Migrate to Site sets"
```
**Impact:** "This file takes no effect in sites that use Site sets."
---
### ext_typoscript_setup.typoscript
**Status:** DEPRECATED since TYPO3 v13.1
**Purpose:** Provided global TypoScript setup
**Migration Paths:**
**1. Preferred: Site Sets**
```yaml
# Configuration/Sets/MySet/config.yaml
name: my-vendor/my-set
label: 'My Extension Set'
imports:
- { resource: 'EXT:fluid_styled_content/Configuration/Sets/FluidStyledContent/config.yaml' }
```
```typoscript
# Configuration/Sets/MySet/setup.typoscript
plugin.tx_myextension {
settings {
itemsPerPage = 10
}
}
```
**2. For Global Loading:**
```php
// ext_localconf.php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScript(
'my_extension',
'setup',
'@import "EXT:my_extension/Configuration/TypoScript/setup.typoscript"'
);
```
**Detection:**
```bash
[ -f "ext_typoscript_setup.typoscript" ] && echo "⚠️ DEPRECATED: Migrate to Site sets"
```
**Impact:** "This file takes no effect in sites that use Site sets. This file works for backward compatibility reasons only in installations that depend on TypoScript records only."
---
## Deprecated Methods (Removal in v14)
### ExtensionManagementUtility::addUserTSConfig()
**Status:** DEPRECATED, will be removed with TYPO3 v14.0
**Old Approach:**
```php
// ext_localconf.php - DEPRECATED
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig('
options.pageTree.showPageIdWithTitle = 1
options.defaultUploadFolder = 1:/user_uploads/
');
```
**Modern Approach:**
```
# Configuration/user.tsconfig
options.pageTree.showPageIdWithTitle = 1
options.defaultUploadFolder = 1:/user_uploads/
```
**Detection:**
```bash
grep "addUserTSConfig" ext_localconf.php && echo "❌ DEPRECATED: Use Configuration/user.tsconfig"
```
---
## Modern Configuration Files (v12+)
### Configuration/user.tsconfig
**Since:** TYPO3 v12
**Purpose:** User TSconfig loaded for all backend users
**Location:** `Configuration/user.tsconfig`
**Example:**
```
# Default user settings
options.pageTree.showPageIdWithTitle = 1
options.defaultUploadFolder = 1:/user_uploads/
# Hide modules
options.hideModules = web_layout, web_info
```
**Validation:**
```bash
[ -f "Configuration/user.tsconfig" ] && echo "✅ Modern user TSconfig" || echo "⚠️ Consider adding user TSconfig"
```
---
### Configuration/page.tsconfig
**Since:** TYPO3 v12
**Purpose:** Page TSconfig loaded globally
**Location:** `Configuration/page.tsconfig`
**Example:**
```
# Default page configuration
TCEFORM.pages.layout.disabled = 1
TCEMAIN.table.pages.disablePrependAtCopy = 1
# Backend layout
mod.web_layout.BackendLayouts {
standard {
title = Standard Layout
icon = EXT:my_ext/Resources/Public/Icons/layout.svg
config {
backend_layout {
colCount = 2
rowCount = 1
rows {
1 {
columns {
1 {
name = Main
colPos = 0
}
2 {
name = Sidebar
colPos = 1
}
}
}
}
}
}
}
}
```
**Validation:**
```bash
[ -f "Configuration/page.tsconfig" ] && echo "✅ Modern page TSconfig" || echo "⚠️ Consider adding page TSconfig"
```
---
## Modern Backend Configuration (v13)
### Configuration/Backend/Modules.php
**Since:** TYPO3 v13.0
**Purpose:** Backend module registration (replaces ext_tables.php)
**Location:** `Configuration/Backend/Modules.php`
**Example:**
```php
<?php
return [
'web_myext' => [
'parent' => 'web',
'position' => ['after' => 'web_list'],
'access' => 'user',
'workspaces' => 'live',
'path' => '/module/web/myext',
'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',
'extensionName' => 'MyExt',
'controllerActions' => [
\Vendor\MyExt\Controller\BackendController::class => [
'list',
'detail',
'update',
],
],
],
];
```
**Old Approach (DEPRECATED):**
```php
// ext_tables.php - DEPRECATED
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule(
'MyExt',
'web',
'mymodule',
'after:list',
[
\Vendor\MyExt\Controller\BackendController::class => 'list,detail,update',
],
[
'access' => 'user,group',
'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',
]
);
```
**Migration Script:** TYPO3 provides "Check TCA in ext_tables.php" upgrade tool
**Validation:**
```bash
[ -f "Configuration/Backend/Modules.php" ] && echo "✅ Modern backend modules" || echo "⚠️ Check for modules in ext_tables.php"
```
---
## Site Sets (v13 Recommended Approach)
### Configuration/Sets Structure
```
Configuration/Sets/
└── MySet/
├── config.yaml (REQUIRED)
├── settings.definitions.yaml
├── setup.typoscript
├── constants.typoscript (optional)
├── page.tsconfig
└── user.tsconfig
```
### config.yaml (Required)
```yaml
name: my-vendor/my-set
label: 'My Extension Configuration Set'
# Dependencies
imports:
- { resource: 'EXT:fluid_styled_content/Configuration/Sets/FluidStyledContent/config.yaml' }
# Settings with defaults
settings:
myext:
itemsPerPage: 10
showImages: true
```
### settings.definitions.yaml
```yaml
settings:
myext:
itemsPerPage:
type: int
default: 10
label: 'Items per page'
description: 'Number of items displayed per page in list view'
showImages:
type: bool
default: true
label: 'Show images'
description: 'Display images in list view'
templateLayout:
type: string
default: 'default'
label: 'Template layout'
enum:
default: 'Default Layout'
grid: 'Grid Layout'
list: 'List Layout'
```
### setup.typoscript
```typoscript
plugin.tx_myextension {
view {
templateRootPaths.0 = EXT:my_extension/Resources/Private/Templates/
partialRootPaths.0 = EXT:my_extension/Resources/Private/Partials/
layoutRootPaths.0 = EXT:my_extension/Resources/Private/Layouts/
}
settings {
itemsPerPage = {$settings.myext.itemsPerPage}
showImages = {$settings.myext.showImages}
}
}
```
### Activation in Site Configuration
```yaml
# config/sites/mysite/config.yaml
base: 'https://example.com/'
rootPageId: 1
sets:
- my-vendor/my-set # Activates the set
```
---
## Migration Checklist
### For v12 → v13 Migration
- [ ] Move backend module registration from ext_tables.php to Configuration/Backend/Modules.php
- [ ] Replace `addUserTSConfig()` with Configuration/user.tsconfig
- [ ] Move page TSconfig from ext_localconf.php to Configuration/page.tsconfig
- [ ] Deprecate ext_typoscript_constants.typoscript (use Site sets)
- [ ] Deprecate ext_typoscript_setup.typoscript (use Site sets)
### For Modern v13 Extensions
- [ ] Use Configuration/Sets/ for TypoScript configuration
- [ ] Use settings.definitions.yaml for extension settings
- [ ] Use Configuration/Backend/Modules.php for backend modules
- [ ] Use Configuration/user.tsconfig for user TSconfig
- [ ] Use Configuration/page.tsconfig for page TSconfig
- [ ] Use Configuration/Icons.php for icon registration
---
## Validation Commands
```bash
#!/bin/bash
# check-v13-deprecations.sh
echo "=== Checking for TYPO3 v13 Deprecations ==="
echo ""
# Check deprecated files
if [ -f "ext_typoscript_constants.typoscript" ]; then
echo "⚠️ DEPRECATED: ext_typoscript_constants.typoscript (v13.1)"
echo " → Migrate to Configuration/Sets/ with settings.definitions.yaml"
fi
if [ -f "ext_typoscript_setup.typoscript" ]; then
echo "⚠️ DEPRECATED: ext_typoscript_setup.typoscript (v13.1)"
echo " → Migrate to Configuration/Sets/ with setup.typoscript"
fi
# Check deprecated methods
if grep -q "addUserTSConfig" ext_localconf.php 2>/dev/null; then
echo "❌ DEPRECATED: addUserTSConfig() - Removal in v14"
echo " → Use Configuration/user.tsconfig"
fi
# Check for backend modules in ext_tables.php
if grep -q "registerModule" ext_tables.php 2>/dev/null; then
echo "⚠️ DEPRECATED: Backend modules in ext_tables.php"
echo " → Migrate to Configuration/Backend/Modules.php"
fi
# Check modern files presence
echo ""
echo "=== Modern Configuration Files ===" [ -d "Configuration/Sets" ] && echo "✅ Configuration/Sets/ present" || echo "⚠️ Consider adding Site sets"
[ -f "Configuration/user.tsconfig" ] && echo "✅ Configuration/user.tsconfig present"
[ -f "Configuration/page.tsconfig" ] && echo "✅ Configuration/page.tsconfig present"
[ -f "Configuration/Backend/Modules.php" ] && echo "✅ Configuration/Backend/Modules.php present"
echo ""
echo "Deprecation check complete"
```
---
## Quick Reference Matrix
| Old Approach | Modern Approach (v13) | Status |
|--------------|----------------------|--------|
| ext_typoscript_constants.typoscript | Configuration/Sets/*/settings.definitions.yaml | Deprecated v13.1 |
| ext_typoscript_setup.typoscript | Configuration/Sets/*/setup.typoscript | Deprecated v13.1 |
| addUserTSConfig() in ext_localconf.php | Configuration/user.tsconfig | Removal in v14 |
| Page TSconfig in ext_localconf.php | Configuration/page.tsconfig | Modern v12+ |
| registerModule() in ext_tables.php | Configuration/Backend/Modules.php | Modern v13+ |
| Static files in ext_tables.php | Configuration/TCA/Overrides/sys_template.php | Modern |
| TCA in ext_tables.php | Configuration/TCA/*.php | Modern |

View File

@@ -0,0 +1,150 @@
# TYPO3 and PHP Version Requirements
**Purpose:** Definitive version compatibility matrix for TYPO3 conformance checking
**Last Updated:** 2025-01-18
## Official Version Support Matrix
### TYPO3 12 LTS
**Release:** April 2022
**End of Life:** October 2026
**PHP Support:** 8.1 - 8.4
| PHP Version | Support Status | Since TYPO3 Version |
|------------|----------------|---------------------|
| 8.1 | ✅ Supported | 12.0.0 |
| 8.2 | ✅ Supported | 12.1.0 |
| 8.3 | ✅ Supported | 12.4.0 |
| 8.4 | ✅ Supported | 12.4.24 (Dec 2024) |
**Minimum Requirements:**
- PHP: 8.1.0
- Database: MariaDB 10.4+ / MySQL 8.0+ / PostgreSQL 10.0+ / SQLite 3.8.3+
### TYPO3 13 LTS
**Release:** October 2024
**End of Life:** April 2028
**PHP Support:** 8.2 - 8.4
| PHP Version | Support Status | Since TYPO3 Version |
|------------|----------------|---------------------|
| 8.1 | ❌ Not Supported | - |
| 8.2 | ✅ Supported | 13.0.0 |
| 8.3 | ✅ Supported | 13.0.0 |
| 8.4 | ✅ Supported | 13.4.0 |
**Minimum Requirements:**
- PHP: 8.2.0
- Database: MariaDB 10.4+ / MySQL 8.0+ / PostgreSQL 10.0+ / SQLite 3.8.3+
## Conformance Checker Standards
The TYPO3 conformance checker validates extensions against:
**Target Versions:**
- TYPO3: 12.4 LTS / 13.x
- PHP: 8.1 / 8.2 / 8.3 / 8.4
- PSR Standards: PSR-11 (DI), PSR-12 (Coding Style), PSR-14 (Events), PSR-15 (Middleware)
**Why This Range:**
- Covers both TYPO3 12 LTS and 13 LTS
- PHP 8.1+ ensures support for all modern PHP features used in TYPO3 extensions
- Extensions can target TYPO3 12 (PHP 8.1+) and/or TYPO3 13 (PHP 8.2+)
## Extension composer.json Examples
### TYPO3 12 LTS Only
```json
{
"require": {
"php": "^8.1 || ^8.2 || ^8.3 || ^8.4",
"typo3/cms-core": "^12.4"
}
}
```
### TYPO3 13 LTS Only
```json
{
"require": {
"php": "^8.2 || ^8.3 || ^8.4",
"typo3/cms-core": "^13.4"
}
}
```
### TYPO3 12 and 13 LTS (Recommended for New Extensions)
```json
{
"require": {
"php": "^8.2 || ^8.3 || ^8.4",
"typo3/cms-core": "^12.4 || ^13.4"
}
}
```
**Note:** When targeting both TYPO3 12 and 13, use PHP 8.2+ as minimum to satisfy TYPO3 13's requirements.
## PHP Feature Availability
### PHP 8.1 Features (TYPO3 12+)
- Enumerations
- Readonly properties
- First-class callable syntax
- New in initializers
- Pure intersection types
- Never return type
- Final class constants
- Fibers
### PHP 8.2 Features (TYPO3 13+)
- Readonly classes
- Disjunctive Normal Form (DNF) types
- Null, false, and true as standalone types
- Constants in traits
- Deprecated dynamic properties
### PHP 8.3 Features (TYPO3 12.4+ / 13+)
- Typed class constants
- Dynamic class constant fetch
- `#[\Override]` attribute
- `json_validate()` function
### PHP 8.4 Features (TYPO3 12.4.24+ / 13.4+)
- Property hooks
- Asymmetric visibility
- New array functions
- HTML5 support in DOM extension
## Migration Paths
### From TYPO3 11 to 12
1. Update PHP to 8.1+ (recommended: 8.2+)
2. Update extension to TYPO3 12 compatibility
3. Test thoroughly on PHP 8.2+ for future TYPO3 13 compatibility
### From TYPO3 12 to 13
1. Ensure PHP 8.2+ is already in use
2. Update TYPO3 dependencies to ^13.4
3. Remove deprecated API usage
4. Update Services.yaml for TYPO3 13 changes (if any)
## Deprecation Timeline
**PHP Versions:**
- PHP 8.0: End of Life - November 2023 (Not supported by TYPO3 12/13)
- PHP 8.1: Security fixes until November 2025
- PHP 8.2: Security fixes until December 2026
- PHP 8.3: Security fixes until December 2027
- PHP 8.4: Security fixes until December 2028
**Recommendation:** Target PHP 8.2+ for new extensions to ensure long-term support alignment with TYPO3 13 LTS lifecycle.
## References
- [TYPO3 12 System Requirements](https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/Installation/Index.html)
- [TYPO3 13 System Requirements](https://docs.typo3.org/m/typo3/reference-coreapi/13.4/en-us/Administration/Installation/SystemRequirements/Index.html)
- [PHP Release Cycles](https://www.php.net/supported-versions.php)
- [TYPO3 Roadmap](https://typo3.org/cms/roadmap)

View File

@@ -0,0 +1,180 @@
#!/usr/bin/env bash
#
# TYPO3 PHP Architecture Conformance Checker
#
# Validates dependency injection, services, events, and architectural patterns
#
set -e
PROJECT_DIR="${1:-.}"
cd "${PROJECT_DIR}"
echo "## 3. PHP Architecture Conformance"
echo ""
has_issues=0
### Check for Services.yaml
echo "### Dependency Injection Configuration"
echo ""
if [ -f "Configuration/Services.yaml" ]; then
echo "- ✅ Configuration/Services.yaml present"
# Check if it has basic DI configuration
if grep -q "autowire: true" Configuration/Services.yaml; then
echo " - ✅ Autowiring enabled"
else
echo " - ⚠️ Autowiring not enabled"
fi
if grep -q "autoconfigure: true" Configuration/Services.yaml; then
echo " - ✅ Autoconfiguration enabled"
else
echo " - ⚠️ Autoconfiguration not enabled"
fi
else
echo "- ❌ Configuration/Services.yaml missing (CRITICAL)"
has_issues=1
fi
### Check for deprecated patterns
echo ""
echo "### Deprecated Pattern Detection"
echo ""
# Check for GeneralUtility::makeInstance
makeinstance_count=$(grep -r "GeneralUtility::makeInstance" Classes/ 2>/dev/null | wc -l)
if [ $makeinstance_count -eq 0 ]; then
echo "- ✅ No GeneralUtility::makeInstance() usage found"
else
echo "- ❌ ${makeinstance_count} instances of GeneralUtility::makeInstance() found"
echo " - Should use constructor injection instead"
has_issues=1
fi
# Check for global state access
globals_count=$(grep -r '\$GLOBALS\[' Classes/ 2>/dev/null | wc -l)
if [ $globals_count -eq 0 ]; then
echo "- ✅ No \$GLOBALS access found"
else
echo "- ❌ ${globals_count} instances of \$GLOBALS access found"
echo " - Should use dependency injection instead"
has_issues=1
fi
### Check for constructor injection
echo ""
echo "### Dependency Injection Patterns"
echo ""
# Check for constructors with dependencies
constructors=$(grep -r "public function __construct" Classes/ 2>/dev/null | wc -l)
if [ $constructors -gt 0 ]; then
echo "- ✅ ${constructors} classes use constructors (potential DI)"
else
echo "- ⚠️ No constructor injection found"
fi
# Check for method injection (inject* methods)
inject_methods=$(grep -r "public function inject[A-Z]" Classes/ 2>/dev/null | wc -l)
if [ $inject_methods -gt 0 ]; then
echo "- ⚠️ ${inject_methods} method injection patterns found (inject*)"
echo " - Consider using constructor injection instead (more modern)"
fi
### Check for PSR-14 events
echo ""
echo "### Event System"
echo ""
# Check for event classes
event_classes=$(find Classes/ -type d -name "Event" 2>/dev/null || echo "")
if [ -n "$event_classes" ]; then
event_count=$(find Classes/ -path "*/Event/*.php" 2>/dev/null | wc -l)
echo "- ✅ ${event_count} event classes found in Classes/Event/"
else
echo "- ⚠️ No Classes/Event/ directory found"
fi
# Check for event listeners
listener_classes=$(find Classes/ -type d -name "EventListener" 2>/dev/null || echo "")
if [ -n "$listener_classes" ]; then
listener_count=$(find Classes/ -path "*/EventListener/*.php" 2>/dev/null | wc -l)
echo "- ✅ ${listener_count} event listeners found in Classes/EventListener/"
else
echo "- ⚠️ No Classes/EventListener/ directory found"
fi
### Check for Extbase patterns
echo ""
echo "### Extbase Architecture"
echo ""
# Check for domain models
if [ -d "Classes/Domain/Model" ]; then
model_count=$(find Classes/Domain/Model/ -name "*.php" 2>/dev/null | wc -l)
echo "- ✅ ${model_count} domain models found"
else
echo "- No Classes/Domain/Model/ (not using Extbase models)"
fi
# Check for repositories
if [ -d "Classes/Domain/Repository" ]; then
repo_count=$(find Classes/Domain/Repository/ -name "*.php" 2>/dev/null | wc -l)
echo "- ✅ ${repo_count} repositories found"
# Check if repositories extend Repository
proper_repos=$(grep -r "extends.*Repository" Classes/Domain/Repository/ 2>/dev/null | wc -l)
if [ $proper_repos -gt 0 ]; then
echo " - ✅ Repositories extend base Repository class"
fi
else
echo "- No Classes/Domain/Repository/ (not using Extbase repositories)"
fi
# Check for controllers
if [ -d "Classes/Controller" ]; then
controller_count=$(find Classes/Controller/ -name "*.php" 2>/dev/null | wc -l)
echo "- ✅ ${controller_count} controllers found"
# Check if controllers extend ActionController
proper_controllers=$(grep -r "extends ActionController" Classes/Controller/ 2>/dev/null | wc -l)
if [ $proper_controllers -gt 0 ]; then
echo " - ✅ Controllers extend ActionController"
fi
fi
### Check for PSR-15 middleware
echo ""
echo "### Middleware"
echo ""
if [ -f "Configuration/RequestMiddlewares.php" ]; then
echo "- ✅ Configuration/RequestMiddlewares.php present"
middleware_count=$(find Classes/ -path "*/Middleware/*.php" 2>/dev/null | wc -l)
if [ $middleware_count -gt 0 ]; then
echo " - ✅ ${middleware_count} middleware classes found"
fi
else
echo "- No Configuration/RequestMiddlewares.php (not using custom middleware)"
fi
echo ""
echo "### Summary"
echo ""
if [ $has_issues -eq 0 ]; then
echo "- ✅ **PHP Architecture: PASSED**"
else
echo "- ⚠️ **PHP Architecture: ISSUES FOUND**"
fi
echo ""
echo "---"
echo ""
exit $has_issues

View File

@@ -0,0 +1,168 @@
#!/usr/bin/env bash
#
# TYPO3 Coding Standards Conformance Checker
#
# Validates PSR-12 compliance and TYPO3-specific code style
#
set -e
PROJECT_DIR="${1:-.}"
cd "${PROJECT_DIR}"
echo "## 2. Coding Standards Conformance"
echo ""
has_issues=0
# Find all PHP files in Classes/
if [ ! -d "Classes" ]; then
echo "- ❌ Classes/ directory not found"
echo ""
echo "---"
echo ""
exit 1
fi
php_files=$(find Classes/ -name "*.php" 2>/dev/null || echo "")
if [ -z "$php_files" ]; then
echo "- ⚠️ No PHP files found in Classes/"
echo ""
echo "---"
echo ""
exit 0
fi
total_files=$(echo "$php_files" | wc -l)
echo "**Total PHP files:** $total_files"
echo ""
### Check for strict types
echo "### Strict Types Declaration"
echo ""
missing_strict=0
for file in $php_files; do
if ! grep -q "declare(strict_types=1)" "$file"; then
missing_strict=$((missing_strict + 1))
fi
done
if [ $missing_strict -eq 0 ]; then
echo "- ✅ All files have declare(strict_types=1)"
else
echo "- ❌ ${missing_strict} files missing declare(strict_types=1)"
has_issues=1
fi
### Check for old array syntax
echo ""
echo "### Array Syntax"
echo ""
old_array_count=$(grep -r "array(" Classes/ 2>/dev/null | wc -l)
if [ $old_array_count -eq 0 ]; then
echo "- ✅ No old array() syntax found"
else
echo "- ❌ ${old_array_count} instances of old array() syntax (should use [])"
has_issues=1
fi
### Check for proper namespace
echo ""
echo "### Namespace Structure"
echo ""
files_without_namespace=0
for file in $php_files; do
if ! grep -q "^namespace " "$file"; then
files_without_namespace=$((files_without_namespace + 1))
fi
done
if [ $files_without_namespace -eq 0 ]; then
echo "- ✅ All files have namespace declaration"
else
echo "- ❌ ${files_without_namespace} files missing namespace declaration"
has_issues=1
fi
### Check for PHPDoc comments on classes
echo ""
echo "### PHPDoc Comments"
echo ""
classes_without_doc=0
for file in $php_files; do
# Simple check: look for /** before class declaration
if grep -q "^class " "$file" || grep -q "^final class " "$file"; then
if ! grep -B 5 "^class \|^final class " "$file" | grep -q "/\*\*"; then
classes_without_doc=$((classes_without_doc + 1))
fi
fi
done
if [ $classes_without_doc -eq 0 ]; then
echo "- ✅ All classes have PHPDoc comments"
else
echo "- ⚠️ ${classes_without_doc} classes missing PHPDoc comments"
fi
### Check naming conventions
echo ""
echo "### Naming Conventions"
echo ""
# Check for snake_case in class names (should be UpperCamelCase)
snake_case_classes=$(grep -rE "^(final )?class [a-z][a-z0-9_]*" Classes/ 2>/dev/null | wc -l)
if [ $snake_case_classes -gt 0 ]; then
echo "- ❌ ${snake_case_classes} classes using incorrect naming (should be UpperCamelCase)"
has_issues=1
else
echo "- ✅ Class naming follows UpperCamelCase convention"
fi
### Check for tabs instead of spaces
echo ""
echo "### Indentation"
echo ""
files_with_tabs=0
for file in $php_files; do
if grep -qP "\t" "$file"; then
files_with_tabs=$((files_with_tabs + 1))
fi
done
if [ $files_with_tabs -eq 0 ]; then
echo "- ✅ No tabs found (using spaces for indentation)"
else
echo "- ❌ ${files_with_tabs} files using tabs instead of spaces"
has_issues=1
fi
### Check for proper use statements
echo ""
echo "### Use Statements"
echo ""
# Check if use statements are present and not duplicated
duplicate_uses=$(grep -rh "^use " Classes/ 2>/dev/null | sort | uniq -d | wc -l)
if [ $duplicate_uses -gt 0 ]; then
echo "- ⚠️ ${duplicate_uses} duplicate use statements found"
else
echo "- ✅ No duplicate use statements"
fi
echo ""
echo "### Summary"
echo ""
if [ $has_issues -eq 0 ]; then
echo "- ✅ **Coding standards: PASSED**"
else
echo "- ⚠️ **Coding standards: ISSUES FOUND**"
fi
echo ""
echo "---"
echo ""
exit $has_issues

View File

@@ -0,0 +1,197 @@
#!/usr/bin/env bash
#
# TYPO3 Extension Conformance Checker
#
# Main script to orchestrate all conformance checks
#
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Configuration
PROJECT_DIR="${1:-.}"
REPORT_DIR="${PROJECT_DIR}/.conformance-reports"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
REPORT_FILE="${REPORT_DIR}/conformance_${TIMESTAMP}.md"
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ TYPO3 Extension Conformance Checker ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${BLUE}Standards Compliance Check:${NC}"
echo -e " • TYPO3 Version: ${YELLOW}12.4 LTS / 13.x${NC}"
echo -e " • PHP Version: ${YELLOW}8.1 / 8.2 / 8.3 / 8.4${NC}"
echo -e " • PSR Standard: ${YELLOW}PSR-12 (Extended Coding Style)${NC}"
echo -e " • Architecture: ${YELLOW}Dependency Injection, PSR-14 Events${NC}"
echo ""
# Create report directory
mkdir -p "${REPORT_DIR}"
# Check if directory exists
if [ ! -d "${PROJECT_DIR}" ]; then
echo -e "${RED}✗ Error: Directory ${PROJECT_DIR} not found${NC}"
exit 1
fi
cd "${PROJECT_DIR}"
# Check if this is a TYPO3 extension
if [ ! -f "composer.json" ] && [ ! -f "ext_emconf.php" ]; then
echo -e "${RED}✗ Error: Not a TYPO3 extension (composer.json or ext_emconf.php not found)${NC}"
exit 1
fi
echo -e "${GREEN}✓ TYPO3 Extension detected${NC}"
echo ""
# Initialize report
cat > "${REPORT_FILE}" <<'EOF'
# TYPO3 Extension Conformance Report
**Generated:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")
**Project:** $(basename "$(pwd)")
## Standards Checked
This conformance check validates your extension against the following standards:
| Standard | Version/Specification |
|----------|----------------------|
| **TYPO3 Core** | 12.4 LTS / 13.x |
| **PHP** | 8.1 / 8.2 / 8.3 / 8.4 |
| **Coding Style** | PSR-12 (Extended Coding Style) |
| **Architecture** | Dependency Injection (PSR-11), PSR-14 Events, PSR-15 Middleware |
| **Testing** | PHPUnit 10+, TYPO3 Testing Framework |
| **Documentation** | reStructuredText (RST), TYPO3 Documentation Standards |
**Reference Documentation:**
- [TYPO3 Extension Architecture](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/)
- [TYPO3 Coding Guidelines](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/)
- [PHP Architecture](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/PhpArchitecture/)
- [Testing Standards](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/)
---
## Summary
| Category | Score | Status |
|----------|-------|--------|
EOF
# Initialize scores
total_score=0
max_score=100
echo -e "${YELLOW}Running conformance checks...${NC}"
echo ""
# 1. File Structure Check
echo -e "${BLUE}[1/5] Checking file structure...${NC}"
if bash "${SCRIPT_DIR}/check-file-structure.sh" "${PROJECT_DIR}" >> "${REPORT_FILE}"; then
echo -e "${GREEN} ✓ File structure check complete${NC}"
structure_score=18
else
echo -e "${YELLOW} ⚠ File structure issues found${NC}"
structure_score=10
fi
echo ""
# 2. Coding Standards Check
echo -e "${BLUE}[2/5] Checking coding standards...${NC}"
if bash "${SCRIPT_DIR}/check-coding-standards.sh" "${PROJECT_DIR}" >> "${REPORT_FILE}"; then
echo -e "${GREEN} ✓ Coding standards check complete${NC}"
coding_score=18
else
echo -e "${YELLOW} ⚠ Coding standards issues found${NC}"
coding_score=12
fi
echo ""
# 3. Architecture Check
echo -e "${BLUE}[3/5] Checking PHP architecture...${NC}"
if bash "${SCRIPT_DIR}/check-architecture.sh" "${PROJECT_DIR}" >> "${REPORT_FILE}"; then
echo -e "${GREEN} ✓ Architecture check complete${NC}"
arch_score=18
else
echo -e "${YELLOW} ⚠ Architecture issues found${NC}"
arch_score=10
fi
echo ""
# 4. Testing Check
echo -e "${BLUE}[4/6] Checking testing infrastructure...${NC}"
if bash "${SCRIPT_DIR}/check-testing.sh" "${PROJECT_DIR}" >> "${REPORT_FILE}"; then
echo -e "${GREEN} ✓ Testing check complete${NC}"
test_score=16
else
echo -e "${YELLOW} ⚠ Testing issues found${NC}"
test_score=8
fi
echo ""
# 5. PHPStan Baseline Check
echo -e "${BLUE}[5/6] Checking PHPStan baseline hygiene...${NC}"
if bash "${SCRIPT_DIR}/check-phpstan-baseline.sh" "${PROJECT_DIR}"; then
echo -e "${GREEN} ✓ PHPStan baseline hygiene check passed${NC}"
baseline_score=10
else
echo -e "${RED} ✗ PHPStan baseline violation detected${NC}"
baseline_score=0
fi
echo ""
# 6. Generate comprehensive report
echo -e "${BLUE}[6/6] Generating final report...${NC}"
bash "${SCRIPT_DIR}/generate-report.sh" "${PROJECT_DIR}" "${REPORT_FILE}" \
"${structure_score}" "${coding_score}" "${arch_score}" "${test_score}"
echo ""
# Calculate total (including baseline hygiene score)
total_score=$((structure_score + coding_score + arch_score + test_score + baseline_score))
# Display summary
echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ Conformance Results ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e " File Structure: ${structure_score}/20"
echo -e " Coding Standards: ${coding_score}/20"
echo -e " PHP Architecture: ${arch_score}/20"
echo -e " Testing Standards: ${test_score}/20"
echo -e " Baseline Hygiene: ${baseline_score}/10"
echo -e " Best Practices: 10/10"
echo ""
echo -e " ${BLUE}Total Score: ${total_score}/100${NC}"
echo ""
if [ ${total_score} -ge 80 ]; then
echo -e "${GREEN}✓ EXCELLENT conformance level${NC}"
elif [ ${total_score} -ge 60 ]; then
echo -e "${YELLOW}⚠ GOOD conformance level (some improvements recommended)${NC}"
elif [ ${total_score} -ge 40 ]; then
echo -e "${YELLOW}⚠ FAIR conformance level (several issues to address)${NC}"
else
echo -e "${RED}✗ POOR conformance level (major improvements needed)${NC}"
fi
echo ""
echo -e "${GREEN}Report saved to: ${REPORT_FILE}${NC}"
echo ""
# Exit with appropriate code
if [ ${total_score} -ge 60 ]; then
exit 0
else
exit 1
fi

View File

@@ -0,0 +1,197 @@
#!/usr/bin/env bash
#
# TYPO3 File Structure Conformance Checker
#
# Validates extension directory structure and required files
#
set -e
PROJECT_DIR="${1:-.}"
cd "${PROJECT_DIR}"
echo "## 1. File Structure Conformance"
echo ""
# Track issues
has_issues=0
echo "### Required Files"
echo ""
# Check required files
if [ -f "composer.json" ]; then
echo "- ✅ composer.json present"
else
echo "- ❌ composer.json missing (CRITICAL)"
has_issues=1
fi
if [ -f "ext_emconf.php" ]; then
echo "- ✅ ext_emconf.php present"
else
echo "- ⚠️ ext_emconf.php missing (required for TER publication)"
fi
if [ -f "Documentation/Index.rst" ]; then
echo "- ✅ Documentation/Index.rst present"
else
echo "- ⚠️ Documentation/Index.rst missing (required for docs.typo3.org)"
fi
if [ -f "Documentation/Settings.cfg" ]; then
echo "- ✅ Documentation/Settings.cfg present"
else
echo "- ⚠️ Documentation/Settings.cfg missing (required for docs.typo3.org)"
fi
echo ""
echo "### Directory Structure"
echo ""
# Check core directories
if [ -d "Classes" ]; then
echo "- ✅ Classes/ directory present"
# Check for common subdirectories
if [ -d "Classes/Controller" ]; then
echo " - ✅ Classes/Controller/ found"
fi
if [ -d "Classes/Domain/Model" ]; then
echo " - ✅ Classes/Domain/Model/ found"
fi
if [ -d "Classes/Domain/Repository" ]; then
echo " - ✅ Classes/Domain/Repository/ found"
fi
else
echo "- ❌ Classes/ directory missing (CRITICAL)"
has_issues=1
fi
if [ -d "Configuration" ]; then
echo "- ✅ Configuration/ directory present"
if [ -d "Configuration/TCA" ]; then
echo " - ✅ Configuration/TCA/ found"
fi
if [ -f "Configuration/Services.yaml" ]; then
echo " - ✅ Configuration/Services.yaml found"
else
echo " - ⚠️ Configuration/Services.yaml missing (recommended)"
fi
if [ -d "Configuration/Backend" ]; then
echo " - ✅ Configuration/Backend/ found"
fi
else
echo "- ⚠️ Configuration/ directory missing"
fi
if [ -d "Resources" ]; then
echo "- ✅ Resources/ directory present"
if [ -d "Resources/Private" ] && [ -d "Resources/Public" ]; then
echo " - ✅ Resources/Private/ and Resources/Public/ properly separated"
else
echo " - ⚠️ Resources/ not properly separated into Private/ and Public/"
fi
else
echo "- ⚠️ Resources/ directory missing"
fi
if [ -d "Tests" ]; then
echo "- ✅ Tests/ directory present"
if [ -d "Tests/Unit" ]; then
echo " - ✅ Tests/Unit/ found"
else
echo " - ⚠️ Tests/Unit/ missing"
fi
if [ -d "Tests/Functional" ]; then
echo " - ✅ Tests/Functional/ found"
else
echo " - ⚠️ Tests/Functional/ missing"
fi
else
echo "- ⚠️ Tests/ directory missing"
fi
echo ""
echo "### Anti-Patterns Check"
echo ""
# Check for PHP files in root (except ext_* files)
# Show all files but distinguish between tracked (issues) and untracked (info)
tracked_files=()
untracked_files=()
all_root_php_files=()
# Find all PHP files in root (except ext_* files)
while IFS= read -r file; do
filename=$(basename "$file")
if [[ "$filename" != ext_*.php ]]; then
all_root_php_files+=("$filename")
fi
done < <(find . -maxdepth 1 -name "*.php" 2>/dev/null || true)
# Check if files are tracked in git (if git repository exists)
if [ -d ".git" ]; then
for file in "${all_root_php_files[@]}"; do
if git ls-files --error-unmatch "$file" >/dev/null 2>&1; then
tracked_files+=("$file")
else
untracked_files+=("$file")
fi
done
else
# No git repository - treat all files as tracked (potential issues)
tracked_files=("${all_root_php_files[@]}")
fi
# Report tracked files (these are issues)
if [ ${#tracked_files[@]} -gt 0 ]; then
if [ -d ".git" ]; then
echo "- ❌ ${#tracked_files[@]} PHP file(s) in root directory committed to repository:"
else
echo "- ❌ ${#tracked_files[@]} PHP file(s) found in root directory:"
fi
for file in "${tracked_files[@]}"; do
echo " - ${file} (ISSUE: should be in Classes/ or Build/)"
done
has_issues=1
fi
# Report untracked files (informational only)
if [ ${#untracked_files[@]} -gt 0 ]; then
echo "- ${#untracked_files[@]} untracked PHP file(s) in root (ignored, not committed):"
for file in "${untracked_files[@]}"; do
echo " - ${file} (local file, not in repository)"
done
fi
# Success message if no files found
if [ ${#all_root_php_files[@]} -eq 0 ]; then
echo "- ✅ No PHP files in root (except ext_* files)"
fi
# Check for deprecated ext_tables.php
if [ -f "ext_tables.php" ]; then
echo "- ⚠️ ext_tables.php present (consider migrating to Configuration/Backend/)"
fi
# Check for wrong directory naming
if [ -d "Classes/Controllers" ]; then
echo "- ❌ Classes/Controllers/ found (should be Controller/ singular)"
has_issues=1
fi
if [ -d "Classes/Helpers" ]; then
echo "- ⚠️ Classes/Helpers/ found (should use Utility/ instead)"
fi
echo ""
echo "---"
echo ""
# Return appropriate exit code
if [ ${has_issues} -eq 0 ]; then
exit 0
else
exit 1
fi

View File

@@ -0,0 +1,110 @@
#!/usr/bin/env bash
# TYPO3 Extension Conformance Checker - PHPStan Baseline Validation
# Verifies that new code does not add errors to phpstan-baseline.neon
#
# Usage:
# ./check-phpstan-baseline.sh [path-to-extension]
#
# Returns:
# 0 = No baseline additions detected
# 1 = New errors added to baseline (violation)
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Parse arguments
PROJECT_ROOT="${1:-.}"
cd "$PROJECT_ROOT" || exit 1
echo "Checking PHPStan baseline hygiene in: $PROJECT_ROOT"
echo
# Check if git repository
if [ ! -d ".git" ]; then
echo -e "${YELLOW}⚠️ Not a git repository - skipping baseline check${NC}"
exit 0
fi
# Find baseline file
BASELINE_FILE=""
for path in "Build/phpstan-baseline.neon" "phpstan-baseline.neon" ".phpstan/baseline.neon"; do
if [ -f "$path" ]; then
BASELINE_FILE="$path"
break
fi
done
if [ -z "$BASELINE_FILE" ]; then
echo -e "${GREEN}✅ No baseline file found - all code passes PHPStan level 10!${NC}"
exit 0
fi
echo "Found baseline file: $BASELINE_FILE"
echo
# Check if baseline is modified in current changes
if ! git diff --quiet "$BASELINE_FILE" 2>/dev/null; then
echo -e "${YELLOW}⚠️ Baseline file has uncommitted changes${NC}"
echo
# Extract error counts from diff
BEFORE_COUNT=$(git show "HEAD:$BASELINE_FILE" 2>/dev/null | grep -E "^\s+count:\s+[0-9]+" | head -1 | grep -oE "[0-9]+" || echo "0")
AFTER_COUNT=$(grep -E "^\s+count:\s+[0-9]+" "$BASELINE_FILE" | head -1 | grep -oE "[0-9]+" || echo "0")
if [ "$AFTER_COUNT" -gt "$BEFORE_COUNT" ]; then
ADDED=$((AFTER_COUNT - BEFORE_COUNT))
echo -e "${RED}❌ BASELINE VIOLATION DETECTED${NC}"
echo
echo "Error count increased: $BEFORE_COUNT$AFTER_COUNT (+$ADDED errors)"
echo
echo "New code added $ADDED errors to the baseline!"
echo
echo "The baseline exists only for legacy code."
echo "All new code MUST pass PHPStan level 10 without baseline suppression."
echo
echo -e "${YELLOW}How to fix:${NC}"
echo "1. Run: composer ci:php:stan"
echo "2. Review the new errors reported"
echo "3. Fix the underlying issues (see coding-guidelines.md for patterns)"
echo "4. Revert baseline changes: git checkout $BASELINE_FILE"
echo "5. Verify: composer ci:php:stan should pass with original baseline"
echo
exit 1
elif [ "$AFTER_COUNT" -lt "$BEFORE_COUNT" ]; then
REMOVED=$((BEFORE_COUNT - AFTER_COUNT))
echo -e "${GREEN}✅ Excellent! Baseline reduced by $REMOVED errors${NC}"
echo
echo "You fixed existing baseline issues - great work!"
echo
else
echo -e "${YELLOW}⚠️ Baseline modified but count unchanged${NC}"
echo
echo "Review the baseline diff to ensure changes are intentional:"
echo " git diff $BASELINE_FILE"
echo
fi
else
echo -e "${GREEN}✅ No changes to baseline file${NC}"
fi
# Check for baseline in staged changes
if git diff --cached --quiet "$BASELINE_FILE" 2>/dev/null; then
echo -e "${GREEN}✅ No baseline changes staged for commit${NC}"
else
echo
echo -e "${YELLOW}⚠️ Warning: Baseline file is staged for commit${NC}"
echo
echo "Review staged baseline changes:"
echo " git diff --cached $BASELINE_FILE"
echo
fi
echo
echo -e "${GREEN}✅ PHPStan baseline hygiene check passed${NC}"
exit 0

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env bash
#
# TYPO3 Testing Standards Conformance Checker
#
# Validates testing infrastructure and test coverage
#
set -e
PROJECT_DIR="${1:-.}"
cd "${PROJECT_DIR}"
echo "## 4. Testing Standards Conformance"
echo ""
has_issues=0
### Check for Tests directory
echo "### Test Infrastructure"
echo ""
if [ ! -d "Tests" ]; then
echo "- ❌ Tests/ directory missing (CRITICAL)"
has_issues=1
echo ""
echo "---"
echo ""
exit 1
fi
echo "- ✅ Tests/ directory present"
### Check for PHPUnit configuration
echo ""
echo "### PHPUnit Configuration"
echo ""
if [ -f "Build/phpunit/UnitTests.xml" ] || [ -f "phpunit.xml" ]; then
echo "- ✅ Unit test configuration found"
else
echo "- ❌ No Unit test configuration (Build/phpunit/UnitTests.xml or phpunit.xml)"
has_issues=1
fi
if [ -f "Build/phpunit/FunctionalTests.xml" ]; then
echo "- ✅ Functional test configuration found"
else
echo "- ⚠️ No Functional test configuration (Build/phpunit/FunctionalTests.xml)"
fi
### Unit Tests
echo ""
echo "### Unit Tests"
echo ""
if [ -d "Tests/Unit" ]; then
unit_test_count=$(find Tests/Unit/ -name "*Test.php" 2>/dev/null | wc -l)
echo "- ✅ Tests/Unit/ directory present"
echo " - **${unit_test_count} unit test files found**"
if [ $unit_test_count -eq 0 ]; then
echo " - ⚠️ No unit tests found"
fi
# Check if tests mirror Classes structure
if [ -d "Classes/Controller" ] && [ ! -d "Tests/Unit/Controller" ]; then
echo " - ⚠️ Tests/Unit/Controller/ missing (Classes/Controller/ exists)"
fi
if [ -d "Classes/Service" ] && [ ! -d "Tests/Unit/Service" ]; then
echo " - ⚠️ Tests/Unit/Service/ missing (Classes/Service/ exists)"
fi
if [ -d "Classes/Domain/Repository" ] && [ ! -d "Tests/Unit/Domain/Repository" ]; then
echo " - ⚠️ Tests/Unit/Domain/Repository/ missing (Classes/Domain/Repository/ exists)"
fi
else
echo "- ❌ Tests/Unit/ directory missing"
has_issues=1
fi
### Functional Tests
echo ""
echo "### Functional Tests"
echo ""
if [ -d "Tests/Functional" ]; then
func_test_count=$(find Tests/Functional/ -name "*Test.php" 2>/dev/null | wc -l)
echo "- ✅ Tests/Functional/ directory present"
echo " - **${func_test_count} functional test files found**"
# Check for fixtures
if [ -d "Tests/Functional/Fixtures" ]; then
fixture_count=$(find Tests/Functional/Fixtures/ -name "*.csv" -o -name "*.xml" 2>/dev/null | wc -l)
echo " - ✅ Tests/Functional/Fixtures/ found (${fixture_count} fixture files)"
else
if [ $func_test_count -gt 0 ]; then
echo " - ⚠️ No Tests/Functional/Fixtures/ (functional tests may need fixtures)"
fi
fi
else
echo "- ⚠️ Tests/Functional/ directory missing"
echo " - Functional tests recommended for repository and database operations"
fi
### Acceptance Tests
echo ""
echo "### Acceptance Tests"
echo ""
if [ -d "Tests/Acceptance" ]; then
accept_test_count=$(find Tests/Acceptance/ -name "*Cest.php" 2>/dev/null | wc -l)
echo "- ✅ Tests/Acceptance/ directory present"
echo " - **${accept_test_count} acceptance test files found**"
if [ -f "Tests/codeception.yml" ]; then
echo " - ✅ codeception.yml configuration found"
else
echo " - ⚠️ codeception.yml configuration missing"
fi
else
echo "- Tests/Acceptance/ not found (optional for most extensions)"
fi
### Test Coverage Estimate
echo ""
echo "### Test Coverage Estimate"
echo ""
if [ -d "Classes" ]; then
class_count=$(find Classes/ -name "*.php" 2>/dev/null | wc -l)
if [ -d "Tests/Unit" ]; then
unit_count=$(find Tests/Unit/ -name "*Test.php" 2>/dev/null | wc -l)
else
unit_count=0
fi
if [ -d "Tests/Functional" ]; then
func_count=$(find Tests/Functional/ -name "*Test.php" 2>/dev/null | wc -l)
else
func_count=0
fi
total_tests=$((unit_count + func_count))
echo "- **Total Classes:** $class_count"
echo "- **Total Tests:** $total_tests"
if [ $class_count -gt 0 ]; then
coverage_ratio=$((total_tests * 100 / class_count))
echo "- **Test Ratio:** ${coverage_ratio}%"
if [ $coverage_ratio -ge 70 ]; then
echo " - ✅ Good test coverage (≥70%)"
elif [ $coverage_ratio -ge 50 ]; then
echo " - ⚠️ Moderate test coverage (50-70%)"
else
echo " - ❌ Low test coverage (<50%)"
has_issues=1
fi
fi
fi
### Check for testing framework dependency
echo ""
echo "### Testing Framework Dependency"
echo ""
if [ -f "composer.json" ]; then
if grep -q "typo3/testing-framework" composer.json; then
echo "- ✅ typo3/testing-framework in composer.json"
else
echo "- ⚠️ typo3/testing-framework not found in composer.json"
fi
if grep -q "phpunit/phpunit" composer.json; then
echo "- ✅ phpunit/phpunit in composer.json"
else
echo "- ⚠️ phpunit/phpunit not found in composer.json"
fi
fi
echo ""
echo "### Summary"
echo ""
if [ $has_issues -eq 0 ]; then
echo "- ✅ **Testing Standards: PASSED**"
else
echo "- ⚠️ **Testing Standards: ISSUES FOUND**"
fi
echo ""
echo "---"
echo ""
exit $has_issues

View File

@@ -0,0 +1,143 @@
#!/usr/bin/env bash
#
# Generate final conformance report with recommendations
#
set -e
PROJECT_DIR="${1}"
REPORT_FILE="${2}"
STRUCTURE_SCORE="${3:-15}"
CODING_SCORE="${4:-15}"
ARCH_SCORE="${5:-15}"
TEST_SCORE="${6:-15}"
cd "${PROJECT_DIR}"
# Calculate total
TOTAL_SCORE=$((STRUCTURE_SCORE + CODING_SCORE + ARCH_SCORE + TEST_SCORE + 10))
# Update summary table
sed -i "s/| Extension Architecture .*/| Extension Architecture | ${STRUCTURE_SCORE}\/20 | $(if [ ${STRUCTURE_SCORE} -ge 15 ]; then echo "✅ Passed"; else echo "⚠️ Issues"; fi) |/" "${REPORT_FILE}"
sed -i "/| Extension Architecture /a | Coding Guidelines | ${CODING_SCORE}/20 | $(if [ ${CODING_SCORE} -ge 15 ]; then echo "✅ Passed"; else echo "⚠️ Issues"; fi) |" "${REPORT_FILE}" 2>/dev/null || true
sed -i "/| Coding Guidelines /a | PHP Architecture | ${ARCH_SCORE}/20 | $(if [ ${ARCH_SCORE} -ge 15 ]; then echo "✅ Passed"; else echo "⚠️ Issues"; fi) |" "${REPORT_FILE}" 2>/dev/null || true
sed -i "/| PHP Architecture /a | Testing Standards | ${TEST_SCORE}/20 | $(if [ ${TEST_SCORE} -ge 15 ]; then echo "✅ Passed"; else echo "⚠️ Issues"; fi) |" "${REPORT_FILE}" 2>/dev/null || true
sed -i "/| Testing Standards /a | Best Practices | 10/20 | Partial |" "${REPORT_FILE}" 2>/dev/null || true
sed -i "/| Best Practices /a | **TOTAL** | **${TOTAL_SCORE}/100** | $(if [ ${TOTAL_SCORE} -ge 80 ]; then echo "✅ Excellent"; elif [ ${TOTAL_SCORE} -ge 60 ]; then echo "✅ Good"; else echo "⚠️ Fair"; fi) |" "${REPORT_FILE}" 2>/dev/null || true
# Add final sections
cat >> "${REPORT_FILE}" <<EOF
---
## 5. Best Practices Assessment
### Project Infrastructure
- **README.md:** $(if [ -f "README.md" ]; then echo "✅ Present"; else echo "❌ Missing"; fi)
- **LICENSE:** $(if [ -f "LICENSE" ]; then echo "✅ Present"; else echo "❌ Missing"; fi)
- **.editorconfig:** $(if [ -f ".editorconfig" ]; then echo "✅ Present"; else echo "⚠️ Missing"; fi)
- **.gitignore:** $(if [ -f ".gitignore" ]; then echo "✅ Present"; else echo "⚠️ Missing"; fi)
### Code Quality Tools
- **php-cs-fixer:** $(if [ -f ".php-cs-fixer.dist.php" ] || [ -f ".php-cs-fixer.php" ] || [ -f "Build/.php-cs-fixer.dist.php" ] || [ -f "Build/.php-cs-fixer.php" ]; then echo "✅ Configured"; else echo "⚠️ Not configured"; fi)
- **phpstan:** $(if [ -f "phpstan.neon" ] || [ -f "phpstan.neon.dist" ] || [ -f "Build/phpstan.neon" ] || [ -f "Build/phpstan.neon.dist" ]; then echo "✅ Configured"; else echo "⚠️ Not configured"; fi)
- **rector:** $(if [ -f "rector.php" ] || [ -f "Build/rector.php" ]; then echo "✅ Configured"; else echo " Not configured"; fi)
### CI/CD Pipeline
- **GitHub Actions:** $(if [ -d ".github/workflows" ]; then echo "✅ Configured"; else echo "⚠️ Not found"; fi)
- **GitLab CI:** $(if [ -f ".gitlab-ci.yml" ]; then echo "✅ Configured"; else echo " Not found"; fi)
---
## Overall Assessment
**Total Score: ${TOTAL_SCORE}/100**
$(if [ ${TOTAL_SCORE} -ge 80 ]; then
cat <<END
### ✅ EXCELLENT Conformance Level
Your TYPO3 extension demonstrates strong adherence to official standards and best practices.
**Strengths:**
- Well-structured architecture following TYPO3 conventions
- Modern PHP patterns with dependency injection
- Good code quality and testing coverage
- Proper documentation and infrastructure
**Minor Improvements:**
- Continue maintaining high standards
- Keep dependencies updated
- Monitor code coverage trends
END
elif [ ${TOTAL_SCORE} -ge 60 ]; then
cat <<END
### ✅ GOOD Conformance Level
Your TYPO3 extension follows most standards with some areas for improvement.
**Next Steps:**
1. Address critical issues identified above
2. Improve test coverage
3. Add missing configuration files
4. Update deprecated patterns
**Timeline:** 2-4 weeks for improvements
END
else
cat <<END
### ⚠️ FAIR Conformance Level
Your TYPO3 extension requires significant improvements to meet TYPO3 standards.
**Priority Actions:**
1. Fix critical file structure issues
2. Migrate deprecated patterns (GeneralUtility::makeInstance, \$GLOBALS)
3. Add comprehensive testing infrastructure
4. Improve code quality (strict types, PHPDoc, PSR-12)
5. Add project infrastructure (CI/CD, quality tools)
**Timeline:** 4-8 weeks for comprehensive improvements
END
fi)
---
## Quick Action Checklist
### High Priority (Fix Now)
$(if [ ${STRUCTURE_SCORE} -lt 15 ]; then echo "- [ ] Fix critical file structure issues (missing required files/directories)"; fi)
$(if grep -q "GeneralUtility::makeInstance" Classes/ 2>/dev/null; then echo "- [ ] Migrate GeneralUtility::makeInstance to constructor injection"; fi)
$(if grep -q '\$GLOBALS\[' Classes/ 2>/dev/null; then echo "- [ ] Remove \$GLOBALS access, use dependency injection"; fi)
$(if [ ! -f "Configuration/Services.yaml" ]; then echo "- [ ] Add Configuration/Services.yaml with DI configuration"; fi)
### Medium Priority (Fix Soon)
$(if [ ${CODING_SCORE} -lt 15 ]; then echo "- [ ] Add declare(strict_types=1) to all PHP files"; fi)
$(if [ ${CODING_SCORE} -lt 15 ]; then echo "- [ ] Replace array() with [] short syntax"; fi)
$(if [ ${TEST_SCORE} -lt 15 ]; then echo "- [ ] Add unit tests for untested classes"; fi)
$(if [ ! -d "Tests/Functional" ]; then echo "- [ ] Add functional tests for repositories"; fi)
### Low Priority (Improve When Possible)
$(if [ ! -f ".php-cs-fixer.dist.php" ] && [ ! -f ".php-cs-fixer.php" ] && [ ! -f "Build/.php-cs-fixer.dist.php" ] && [ ! -f "Build/.php-cs-fixer.php" ]; then echo "- [ ] Configure PHP CS Fixer"; fi)
$(if [ ! -f "phpstan.neon" ] && [ ! -f "phpstan.neon.dist" ] && [ ! -f "Build/phpstan.neon" ] && [ ! -f "Build/phpstan.neon.dist" ]; then echo "- [ ] Configure PHPStan for static analysis"; fi)
$(if [ ! -d ".github/workflows" ]; then echo "- [ ] Set up CI/CD pipeline (GitHub Actions)"; fi)
- [ ] Improve PHPDoc comments coverage
- [ ] Add .editorconfig for consistent formatting
---
## Resources
- **TYPO3 Extension Architecture:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/
- **Coding Guidelines:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/
- **Dependency Injection:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/
- **Testing Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/
- **Tea Extension (Best Practice):** https://github.com/TYPO3BestPractices/tea
---
*Report generated by TYPO3 Extension Conformance Checker*
EOF
echo "Final report generated successfully"

View File

@@ -0,0 +1,22 @@
# OS files
.DS_Store
Thumbs.db
# Editor files
.vscode/
.idea/
*.swp
*.swo
*~
# Temporary files
*.tmp
*.bak
.*.tmp
# Log files
*.log
# Development files
.env
.env.local

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Netresearch DTT GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,273 @@
# TYPO3 Core Contributions Skill
An AI skill for guiding contributions to TYPO3 Core through systematic workflows, automated quality checks, and best practices enforcement.
## Overview
This skill provides comprehensive guidance for contributing to TYPO3 Core, including:
- **Gerrit-based code review workflow**
- **Automated CI/CD debugging**
- **Commit message formatting**
- **WIP (Work in Progress) state management**
- **Testing and quality assurance**
- **Account setup and prerequisites**
## Features
### 🔄 Complete Contribution Workflow
- Step-by-step guidance from setup to patch submission
- Automated detection of common issues
- Best practices enforcement at every stage
### 🤖 CI/CD Integration
- Systematic debugging of failed GitLab CI jobs
- Pattern recognition for common failures
- Automated fix suggestions
### ✅ Quality Gates
- Pre-submission validation
- Code style enforcement (CGL)
- PHPStan static analysis
- Comprehensive test coverage
### 📝 Documentation
- Gerrit workflow reference
- Commit message format guidelines
- Troubleshooting guide with 60+ scenarios
- WIP state management
## Quick Start
### Prerequisites
Ensure you have:
- Git configured with your TYPO3.org email
- SSH key uploaded to review.typo3.org
- Docker (for DDEV) or native PHP 8.2+ environment
### Installation
Use this skill in Claude Code by referencing it from your project's skill configuration.
### Usage
The skill activates automatically when working on TYPO3 Core contributions. It guides you through:
1. **Setup Phase**: Prerequisites verification, environment configuration
2. **Development Phase**: Code changes, testing, validation
3. **Submission Phase**: Gerrit patch submission, CI monitoring, WIP management
4. **Review Phase**: Addressing reviewer feedback, iterating on changes
## Scope
**This skill covers**: TYPO3 Core code contributions (PHP, JavaScript, CSS, tests)
- Submission via Gerrit (review.typo3.org)
- Git commit-msg hooks and validation
- Forge issue tracking
- GitLab CI/CD pipeline
**Not covered**: TYPO3 Documentation contributions
- For documentation work, use: https://github.com/netresearch/typo3-docs-skill
- Documentation uses GitHub Pull Requests, not Gerrit
- Different format (reStructuredText) and workflows
## Directory Structure
```
typo3-core-contributions/
├── SKILL.md # Main skill definition
├── README.md # This file
├── references/
│ ├── account-setup.md # Prerequisites and account configuration
│ ├── commit-message-format.md # Commit message standards
│ ├── ddev-setup-workflow.md # DDEV environment setup
│ ├── gerrit-workflow.md # Complete Gerrit submission workflow
│ └── troubleshooting.md # 60+ troubleshooting scenarios
├── scripts/
│ ├── setup-typo3-coredev.sh # Automated environment setup
│ └── verify-prerequisites.sh # Prerequisites checker
└── assets/
└── images/ # Workflow diagrams and screenshots
```
## Key Workflows
### 1. Initial Setup
```bash
# Verify prerequisites
./scripts/verify-prerequisites.sh
# Setup TYPO3 Core development environment
./scripts/setup-typo3-coredev.sh
```
### 2. Create Patch
```bash
# Create feature branch
git checkout -b feature/issue-number-description
# Make changes, commit with proper format
git commit -m "[BUGFIX] Fix indexed search null handling
Resolves: #105737
Releases: main"
```
### 3. Submit to Gerrit
```bash
# Submit as WIP (Work in Progress)
git push origin HEAD:refs/for/main%wip
# After CI passes, mark as ready
git commit --amend --allow-empty --no-edit
git push origin HEAD:refs/for/main%ready
```
### 4. Handle CI Failures
The skill provides systematic debugging:
1. Check ALL failed job logs
2. Identify failure patterns (cgl, phpstan, unit tests)
3. Fix all issues in ONE patchset
4. Re-submit and verify
## WIP State Management
### Command-Line Approach (Recommended)
```bash
# Set WIP state
git push origin HEAD:refs/for/main%wip
# Remove WIP state
git commit --amend --allow-empty --no-edit
git push origin HEAD:refs/for/main%ready
```
### Web UI Alternative
Open review URL and click "Start Review" button.
**Note**: SSH `gerrit review` command does NOT support WIP flags.
## Commit Message Format
Required structure:
```
[TYPE] Short description (max 52 chars)
Extended description explaining the why and how.
Resolves: #12345
Releases: main, 12.4
```
**Types**: BUGFIX, FEATURE, TASK, DOCS, CLEANUP, SECURITY
**Required**: At least one `Resolves:` line
**Optional**: `Related:` (but cannot be used alone)
## CI/CD Debugging
Common failure patterns:
### CGL (Code Style)
```bash
Build/Scripts/cglFixMyCommit.sh
git commit --amend --no-edit
```
### PHPStan
```bash
Build/Scripts/runTests.sh -s phpstan
# Fix reported issues
```
### Unit Tests
```bash
Build/Scripts/runTests.sh -s unit path/to/test
# Fix test failures
```
## Troubleshooting
The skill includes comprehensive troubleshooting for:
- **Account Issues**: Email mismatch, SSH authentication, commit-msg hook
- **CI Failures**: CGL, PHPStan, unit tests, functional tests
- **Gerrit Issues**: WIP state, patch conflicts, rebase requirements
- **Testing Issues**: Test failures, coverage gaps, fixture setup
- **Code Quality**: Naming conventions, type safety, defensive programming
See `references/troubleshooting.md` for detailed solutions.
## Real-World Testing
This skill was developed and validated through:
- **Forge Issue #105737**: TypeError in indexed search
- **7 patchsets** with iterative CI debugging
- **GitHub PR #397**: Documentation improvements
- **Live Gerrit testing**: WIP workflow validation
All workflows have been tested on actual TYPO3 Core submissions.
## Updates and Enhancements
Recent additions:
### v1.1.0 (2025-10-27)
- ✅ WIP state management (command-line and web UI)
- ✅ CI failure investigation protocol (423 lines)
- ✅ Comprehensive troubleshooting guide (60+ scenarios)
- ✅ PHPStan error guidance
- ✅ Code style enforcement patterns
- ✅ Documentation scope clarification
## Contributing
To improve this skill:
1. Test on real TYPO3 Core contributions
2. Document edge cases in troubleshooting guide
3. Add automation scripts for common tasks
4. Validate against official TYPO3 documentation
## Resources
### Official TYPO3 Documentation
- [Contribution Guide](https://docs.typo3.org/m/typo3/guide-contributionworkflow/main/en-us/)
- [Gerrit Documentation](https://gerrit-review.googlesource.com/Documentation/user-upload.html)
- [TYPO3 Forge](https://forge.typo3.org/)
### Related Skills
- [TYPO3 Docs Skill](https://github.com/netresearch/typo3-docs-skill) - For documentation contributions
## License
MIT License - See LICENSE file for details
## Author
Created for use with Claude Code and TYPO3 Core contributions.
Maintained by: Netresearch DTT GmbH
## Support
For issues or questions:
- Open an issue in this repository
- Reference official TYPO3 documentation
- Test workflows on live Gerrit instance
---
**Version**: 1.1.0
**Last Updated**: 2025-10-27
**Status**: Production-ready, validated on live submissions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,78 @@
[BUGFIX|TASK|FEATURE|DOCS|SECURITY] Subject line (max 52 chars, imperative)
# Detailed description explaining how and why (not what):
# - Lines wrapped at 72 characters
# - Explain context and reasoning
# - Don't repeat Forge issue content
# - Don't describe reproduction steps
#
# Use bullet points for lists:
# * First item
# * Second item
# * Third item
Resolves: #
Releases: main, 13.4, 12.4
# Change-Id will be added automatically by git hook - DO NOT edit manually!
# ============================================================================
# TYPO3 Commit Message Format Guide
# ============================================================================
#
# SUBJECT LINE
# ------------
# Format: [TYPE] Description
#
# Types:
# [BUGFIX] - Bug fixes
# [FEATURE] - New features (main branch only)
# [TASK] - Refactoring, cleanup, miscellaneous
# [DOCS] - Documentation changes
# [SECURITY] - Security vulnerability fixes
#
# Breaking changes: [!!!][TYPE] Description
#
# Rules:
# - Imperative mood (Fix, Add, Remove - not Fixed, Added, Removed)
# - Start with uppercase letter
# - Max 52 characters (recommended), 72 absolute limit
# - No period at end
# - Describe what now works, not what was broken
#
# DESCRIPTION BODY
# ----------------
# - Explain HOW and WHY, not WHAT (code shows what)
# - Leave blank line after subject
# - Wrap at 72 characters (URLs can be longer)
# - Use asterisks (*) for bullet points
#
# FOOTER TAGS
# -----------
# Resolves: #12345 - Closes issue on merge (features/tasks)
# Related: #12345 - Links issue without closing (bugfixes)
# Releases: main, 13.4 - Target versions (comma-separated)
# Change-Id: I... - Auto-generated, DO NOT modify!
#
# EXAMPLES
# --------
# Good:
# [BUGFIX] Fix null pointer in indexed search
# [FEATURE] Add WebP image format support
# [TASK] Refactor cache manager for better performance
# [!!!][TASK] Drop support for PHP 7.4
#
# Bad:
# [BUGFIX] fixed bug (past tense)
# [FEATURE] Adds webp support (wrong tense)
# [TASK] refactoring cache (lowercase)
# [BUGFIX] Fix null pointer exception. (period at end)
#
# VALIDATION
# ----------
# Run: ./scripts/validate-commit-message.py
#
# MORE INFO
# ---------
# See: references/commit-message-format.md
# Docs: https://docs.typo3.org/m/typo3/guide-contributionworkflow/
# ============================================================================

View File

@@ -0,0 +1,52 @@
# SKILL.md Refactoring Summary
**Date:** 2025-11-14
**Version Change:** 1.3.0 → 1.4.0
**Skill:** typo3-core-contributions
## Changes Applied
### Pattern 1: Removed "## Overview" Section
- **Before:** Lines 21-23 contained brief "## Overview" section
- **After:** Removed entire section
- **Rationale:** Content duplicated by YAML description and "When to Use This Skill" section
### Pattern 2: Converted "## Best Practices" to Imperative Form
- **Before:** "## Best Practices" with numbered list and subsection
- **After:** "## Contribution Workflow Standards" with imperative "When X" format
- **Changes:**
#### When managing commits (5 standards)
- Converted from general practices to specific commit management procedures
- Emphasized proactive skill usage (typo3-conformance-skill, typo3-testing-skill)
#### When maintaining patches (5 standards)
- Extracted patch maintenance guidance into dedicated section
- Action-oriented instructions for rebase, review, feedback
#### When writing code (5 standards)
- Focused on code quality and framework patterns
- Clear integration points with complementary skills
#### When handling CI failures (4 standards)
- Separated CI troubleshooting into distinct workflow
- Emphasized local validation and root cause analysis
## Impact Analysis
**Readability:** Improved - organized by workflow context
**Consistency:** Aligned with skill-creator best practices
**Usability:** Enhanced - clear triggers for when to apply each standard
**Workflow Integration:** Better integration with complementary skills
## Files Modified
- `/SKILL.md` (lines 1-1057)
## Verification
- Version number updated in YAML frontmatter: ✓
- Overview section removed: ✓
- Best Practices converted to Contribution Workflow Standards: ✓
- All 19 standards preserved with improved organization: ✓
- Skill integration guidance maintained: ✓

View File

@@ -0,0 +1,275 @@
# TYPO3 Core Contribution Account Setup Guide
Complete guide for setting up all required accounts for TYPO3 Core contributions.
## Overview
Three accounts are required for TYPO3 Core contribution:
1. **TYPO3.org Account** - Central authentication for all TYPO3 services
2. **Gerrit Account with SSH** - Code review and patch submission
3. **Slack Access** - Community communication and support
## 1. TYPO3.org Account
### Registration
1. Visit the signup page: https://my.typo3.org/index.php?id=2
2. Fill in the registration form:
- **Username**: Choose alphanumeric identifier (avoid special characters like `@` or `!`)
- **Email Address**: Use email for Forge and Gerrit notifications
- **Full Name**: Use real name (community values genuine identification)
- **Password**: Create strong password (use password manager recommended)
3. Submit the form
4. Check your email for verification message
5. Click verification link to activate account
### What This Account Provides
- Access to Forge issue tracker
- Authentication for Gerrit code review
- Access to my.typo3.org profile management
- Community member identification
### Important Notes
- Username cannot be changed after registration
- Consider using personal email (not corporate) if contributing independently
- This account will be visible in git commits and Gerrit reviews
## 2. Gerrit Account Setup
### Prerequisites
- Active TYPO3.org account
- SSH key pair (will create if needed)
### Step 1: Sign In to Gerrit
1. Visit https://review.typo3.org
2. Click **Sign In** button (top right)
3. Authenticate with your TYPO3.org credentials
4. You'll be redirected back to Gerrit
### Step 2: Generate SSH Key Pair
SSH keys are required for pushing patches to Gerrit.
#### Linux / macOS
```bash
# Generate SSH key pair
ssh-keygen -t ed25519 -C "your-email@example.org"
# Default location: ~/.ssh/id_ed25519
# Press Enter to accept default location
# Optionally set a passphrase for additional security
# View your public key
cat ~/.ssh/id_ed25519.pub
```
#### Windows
**Option A: Git Bash (Recommended)**
```bash
# Same commands as Linux/macOS above
ssh-keygen -t ed25519 -C "your-email@example.org"
```
**Option B: PuTTYgen**
1. Download and install PuTTY: https://www.putty.org/
2. Run PuTTYgen
3. Click "Generate" and move mouse randomly
4. Save private key (*.ppk file)
5. Copy public key from text area
### Step 3: Add Public Key to Gerrit
1. Click your profile icon (top right in Gerrit)
2. Select **Settings** from dropdown
3. Click **SSH Keys** in left sidebar
4. Paste your **public key** content (entire content of `id_ed25519.pub` or `id_rsa.pub`)
5. Click **Add New SSH Key**
**Important**: Only add the PUBLIC key, never the private key!
### Step 4: Test SSH Connection
```bash
# Test connection to Gerrit
ssh -p 29418 <YOUR_USERNAME>@review.typo3.org
# Expected output:
# **** Welcome to Gerrit Code Review ****
# Hi <Your Name>, you have successfully connected over SSH.
```
If you see the welcome message, SSH is configured correctly!
### Multiple Devices
If you work on multiple computers:
**Option 1: Copy Private Key**
- Copy `~/.ssh/id_ed25519` (private key) to other machines
- Set proper permissions: `chmod 600 ~/.ssh/id_ed25519`
- Not recommended for security reasons
**Option 2: Generate Separate Keys (Recommended)**
- Generate new key pair on each device
- Add all public keys to Gerrit settings
- Gerrit supports multiple SSH keys per account
- More secure: compromised device doesn't affect others
### Troubleshooting
**"Permission denied (publickey)"**
- Verify key is added to Gerrit: Settings → SSH Keys
- Check key permissions: `chmod 600 ~/.ssh/id_ed25519`
- Test with verbose: `ssh -vvv -p 29418 <username>@review.typo3.org`
**"Connection refused"**
- Check firewall settings
- Verify port 29418 is accessible
- Try from different network
**"Host key verification failed"**
- Accept host key: `ssh-keyscan -p 29418 review.typo3.org >> ~/.ssh/known_hosts`
## 3. TYPO3 Slack Workspace
### Joining Slack
1. Visit https://typo3.slack.com
2. Click **Create an account** or **Sign in**
3. Use same email as TYPO3.org account (recommended for consistency)
4. Complete Slack registration
5. You'll receive invitation to TYPO3 workspace
### Required Channels
**#typo3-cms-coredev** (Essential)
- Core development discussions
- Patch review requests
- Technical questions
- Get help from core team
### Recommended Channels
**#typo3-cms**
- General TYPO3 CMS discussions
- User questions
- Extension development
**#random**
- Off-topic chat
- Community social
**#announce**
- Official announcements
- Release notifications
### Using Slack Effectively
**Asking for Reviews**:
```
I've submitted a patch for issue #105737 (indexed search crash).
Would appreciate reviews: https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345
```
**Asking Questions**:
```
Working on #105737, need clarification on preg_replace error handling.
Should I use fallback or throw exception? Context: [brief explanation]
```
**Best Practices**:
- Search before asking (knowledge base exists)
- Provide context and Forge/Gerrit links
- Be patient (volunteers respond when available)
- Use threads for discussions
- Thank people who help!
### Slack Etiquette
- **Don't** @here or @channel unless critical
- **Do** use threads to keep discussions organized
- **Don't** DM core team members without asking first
- **Do** share knowledge when you can help others
- **Don't** expect immediate responses (volunteers have lives!)
## Verification Checklist
Before proceeding with development, verify:
- [ ] TYPO3.org account created and email verified
- [ ] Can sign in to https://forge.typo3.org
- [ ] Can sign in to https://review.typo3.org
- [ ] SSH key added to Gerrit
- [ ] SSH connection to Gerrit successful: `ssh -p 29418 <user>@review.typo3.org`
- [ ] Joined TYPO3 Slack workspace
- [ ] Member of #typo3-cms-coredev channel
Run `scripts/verify-prerequisites.sh` to automatically check most of these!
## Security Best Practices
### SSH Key Security
- **Never share private keys** - TYPO3 team will never ask for them
- **Use strong passphrase** - Protects key if device is compromised
- **Rotate keys periodically** - Generate new keys annually
- **Delete old keys** - Remove unused keys from Gerrit settings
### Account Security
- **Use unique strong password** - Use password manager
- **Enable 2FA if available** - Additional security layer
- **Log out on shared devices** - Don't stay signed in
- **Review SSH keys regularly** - Remove keys from old devices
### Privacy Considerations
- Your name and email will be visible in:
- Git commit history
- Gerrit reviews
- Forge issue comments
- Consider using professional email if contributing as individual
- Company contributions may require corporate email
## Next Steps
After completing account setup:
1. Proceed to **Environment Setup** (Phase 2 in main workflow)
2. Configure Git for TYPO3 contributions
3. Clone TYPO3 repository
4. Install Git hooks
5. Start contributing!
## Support Resources
- **Forge Account Issues**: https://forge.typo3.org/projects/typo3cms-core
- **Gerrit SSH Help**: https://review.typo3.org/Documentation/user-upload.html
- **Slack Support**: Ask in #typo3-cms-coredev
- **Documentation**: https://docs.typo3.org/m/typo3/guide-contributionworkflow/
## Quick Reference
| Service | URL | Purpose |
|---------|-----|---------|
| TYPO3.org Registration | https://my.typo3.org/index.php?id=2 | Create account |
| TYPO3.org Profile | https://my.typo3.org | Manage profile |
| Forge | https://forge.typo3.org | Issue tracking |
| Gerrit | https://review.typo3.org | Code review |
| Gerrit SSH Test | `ssh -p 29418 <user>@review.typo3.org` | Verify connection |
| Slack | https://typo3.slack.com | Community chat |
| Documentation | https://docs.typo3.org/m/typo3/guide-contributionworkflow/ | Full guide |

View File

@@ -0,0 +1,477 @@
# TYPO3 Commit Message Format Specification
Complete specification for TYPO3 Core contribution commit messages with examples and validation rules.
## Structure Overview
```
[TYPE] Subject line (max 52 chars recommended, 72 absolute limit)
Detailed description explaining how and why the changes were made.
Lines wrapped at 72 characters. Explain the context and reasoning
behind the implementation approach.
Multiple paragraphs are allowed. Use bullet points with asterisks (*)
for lists:
* First item with detailed explanation
* Second item
* Third item
Resolves: #12345
Related: #12346
Releases: main, 13.4, 12.4
Change-Id: I1234567890abcdef1234567890abcdef12345678
```
## Subject Line
### Format
`[TYPE] Description starting with uppercase verb in imperative mood`
### Commit Types
| Type | Purpose | Branch Restrictions |
|------|---------|-------------------|
| `[BUGFIX]` | Bug fixes | All branches |
| `[FEATURE]` | New functionality | main branch only |
| `[TASK]` | Refactoring, cleanup, misc | All branches |
| `[DOCS]` | Documentation changes | All branches |
| `[SECURITY]` | Security vulnerability fixes | All branches |
### Breaking Changes
Use `[!!!]` prefix before type for breaking changes:
```
[!!!][FEATURE] Remove deprecated TypoScript syntax
[!!!][TASK] Drop support for PHP 7.4
```
**Important**: Deprecations must NOT use `[!!!]` prefix!
### Length Limits
- **Recommended**: 52 characters
- **Absolute maximum**: 72 characters
- Breaking changes get 5 extra chars: `[!!!]` is not counted against limit
### Imperative Mood
Subject must use imperative present tense (command form):
**Correct**:
- `Fix memory leak in cache manager`
- `Add support for WebP images`
- `Remove deprecated method calls`
- `Update documentation for hooks`
**Wrong**:
- `Fixed memory leak` (past tense)
- `Fixing memory leak` (present continuous)
- `Fixes memory leak` (present tense)
- `Memory leak fix` (noun phrase)
**Test**: "If applied, this commit will _[your subject]_"
- "If applied, this commit will **fix memory leak**" ✅
- "If applied, this commit will **fixed memory leak**" ❌
### Capitalization
- Start description with uppercase letter after `[TYPE]`
- No period at the end
`[BUGFIX] Fix null pointer exception in indexer`
`[BUGFIX] fix null pointer exception in indexer`
`[BUGFIX] Fix null pointer exception in indexer.`
### What to Describe
Describe **what now works**, not what was broken:
`Allow cancelling file exists modal`
`Cancelling the file exists modal works now`
`Limit element browser to default language pages`
`Element Browser should only render default language pages`
## Description Body
### Purpose
Explain the **how** and **why** of changes, not the **what** (code shows what).
### Guidelines
- Wrap lines at 72 characters (URLs can be longer)
- Leave blank line after subject
- Explain context and reasoning
- **Don't** repeat Forge issue content
- **Don't** describe reproduction steps (belong in Forge)
- **Do** explain non-obvious implementation choices
- **Do** mention side effects or impacts
### Bullet Points
Use asterisks (`*`) with hanging indents:
```
This change improves performance by:
* Caching compiled templates in memory
* Reducing database queries from N+1 to 1
* Pre-loading frequently accessed resources
```
### Long URLs
Lines exceeding 72 chars are acceptable for URLs. Use numbered references:
```
This implements the W3C recommendation [1] for accessible forms.
Additional context can be found in the TYPO3 RFC [2].
[1] https://www.w3.org/WAI/WCAG21/Understanding/labels-or-instructions.html
[2] https://wiki.typo3.org/Category:T3DD12/Sessions/Accessibility
```
## Footer Tags
### Required Format
`Tag: value` (colon followed by space)
### Resolves **(REQUIRED)**
Closes Forge issue when patch is merged:
```
Resolves: #12345
```
Multiple issues (one per line):
```
Resolves: #12345
Resolves: #12346
```
**Critical Rule**: Every commit MUST have at least one `Resolves:` line. The commit-msg hook will reject commits without it.
**When to use**:
- Features: MUST use Resolves
- Tasks: MUST use Resolves
- Bugfixes: MUST use Resolves
- All commit types: ALWAYS use Resolves
**Note**: For features and tasks, `Resolves:` closes the issue on merge. For bugfixes, you can use `Related:` in addition to `Resolves:` if needed, but `Resolves:` is still mandatory.
### Related **(OPTIONAL)**
Links issue without closing it:
```
Related: #12345
```
**Critical Rule**: `Related:` CANNOT be used alone - you MUST have at least one `Resolves:` line in addition to any `Related:` lines. The commit-msg hook will reject commits with only `Related:` tags.
**When to use** (in addition to Resolves):
- Bugfixes: Use Related for issues that should stay open
- Partial fixes: Related for multi-step fixes where issue remains open
- Context: Link related discussions or issues
- Cross-references: Link to related work or documentation
### Releases
Target versions (comma-separated):
```
Releases: main, 13.4, 12.4
```
**Format**:
- `main` - Current development branch
- `13.4` - Patch release version
- `12.4` - LTS version
**Rules**:
- Always include target versions
- List from newest to oldest
- Features go to `main` only
- Bugfixes can target multiple releases
### Change-Id
Auto-generated by git commit-msg hook:
```
Change-Id: I1234567890abcdef1234567890abcdef12345678
```
**Critical Rules**:
- **NEVER** manually add Change-Id
- **NEVER** modify existing Change-Id
- **NEVER** remove Change-Id
- Git hook generates this automatically
- Required for Gerrit to track patch updates
### Depends (Documentation Only)
For documentation patches referencing core changes:
```
Depends: I1234567890abcdef1234567890abcdef12345678
```
Only used in typo3/cms-docs repository.
### Reverts
For reverting patches:
```
[TASK] Revert "[FEATURE] Introduce YAML imports"
This reverts commit abc123def456.
Resolves: #12347
Reverts: I1234567890abcdef1234567890abcdef12345678
```
## Complete Examples
### Example 1: Bugfix
```
[BUGFIX] Prevent null pointer in indexed search
The preg_replace function returns null on PCRE errors like
PREG_BAD_UTF8_ERROR. Passing null to mb_strcut triggers TypeError
in PHP 8.2+.
Add null check with fallback to original content, ensuring type
safety while maintaining graceful degradation for malformed input.
Resolves: #105737
Releases: main, 13.4, 12.4
Change-Id: I1234567890abcdef1234567890abcdef12345678
```
### Example 2: Feature
```
[FEATURE] Add WebP image format support
Implement WebP processing in image manipulation stack:
* Add WebP MIME type detection
* Integrate libwebp encoding/decoding
* Update image quality settings for WebP
* Add configuration options for compression
WebP provides 25-30% better compression than JPEG while maintaining
quality, significantly improving page load times.
Resolves: #98765
Releases: main
Change-Id: Iabcdef1234567890abcdef1234567890abcdef12
```
### Example 3: Task with Breaking Change
```
[!!!][TASK] Drop PHP 7.4 support
PHP 7.4 reached end-of-life in November 2022 and no longer receives
security updates. Remove compatibility code and leverage PHP 8.0+
features:
* Remove PHP 7.4 compatibility polyfills
* Update composer.json to require PHP >= 8.0
* Use union types and match expressions
* Enable strict type declarations globally
Resolves: #99888
Releases: main
Change-Id: I9876543210fedcba9876543210fedcba98765432
```
### Example 4: Documentation
```
[DOCS] Update contribution workflow guide
Clarify git setup instructions and add troubleshooting section
for common SSH key issues reported in #typo3-cms-coredev.
Related: #12345
Releases: main
Change-Id: Iaa11bb22cc33dd44ee55ff66gg77hh88ii99jj00
```
## Validation Rules
### Subject Line
- [ ] Starts with valid commit type: `[BUGFIX]`, `[FEATURE]`, `[TASK]`, `[DOCS]`, or `[SECURITY]`
- [ ] Breaking changes use `[!!!]` prefix correctly
- [ ] Description starts with uppercase letter
- [ ] Uses imperative mood
- [ ] No period at end
- [ ] Length ≤ 52 chars (recommended) or ≤ 72 chars (absolute)
- [ ] No extension names (EXT:) in subject
### Body
- [ ] Blank line after subject (if body exists)
- [ ] Lines wrapped at 72 chars (except URLs)
- [ ] Explains how and why, not what
- [ ] No reproduction steps (belong in Forge)
### Footer
- [ ] `Resolves:` present **(REQUIRED for ALL commits)**
**Critical**: The commit-msg hook WILL REJECT commits without at least one `Resolves:` line
- [ ] `Related:` used only in addition to `Resolves:` (optional, cannot be used alone)
- [ ] `Releases:` present with valid versions
- [ ] `Change-Id:` present (added by hook)
- [ ] Proper format: `Tag: value` (colon + space)
- [ ] Issue references use `#` prefix: `#12345`
## Common Mistakes
### ❌ Wrong: Vague Subject
```
[TASK] Improve extension configuration
```
### ✅ Correct: Specific Subject
```
[TASK] Add validation for extension configuration arrays
```
---
### ❌ Wrong: Past Tense
```
[BUGFIX] Fixed cache invalidation in frontend
```
### ✅ Correct: Imperative Mood
```
[BUGFIX] Fix cache invalidation in frontend
```
---
### ❌ Wrong: No Footer Tags
```
[FEATURE] Add dark mode support
Implements dark mode toggle with user preference storage.
```
### ✅ Correct: Complete Footer
```
[FEATURE] Add dark mode support
Implements dark mode toggle with user preference storage.
Resolves: #12345
Releases: main
Change-Id: I1234567890abcdef1234567890abcdef12345678
```
---
### ❌ Wrong: Comma-Separated Issues
```
Resolves: #12345, #12346, #12347
```
### ✅ Correct: One Per Line
```
Resolves: #12345
Resolves: #12346
Resolves: #12347
```
---
### ❌ Wrong: Missing Space After Colon
```
Releases:main, 13.4
```
### ✅ Correct: Space After Colon
```
Releases: main, 13.4
```
## Tools
### Validation
Use `scripts/validate-commit-message.py`:
```bash
# Validate last commit
./scripts/validate-commit-message.py
# Validate specific file
./scripts/validate-commit-message.py --file commit-msg.txt
# Strict mode (warnings as errors)
./scripts/validate-commit-message.py --strict
```
### Generation
Use `scripts/create-commit-message.py`:
```bash
# Interactive generator
./scripts/create-commit-message.py --issue 105737 --type BUGFIX
# With breaking change
./scripts/create-commit-message.py --issue 98765 --type FEATURE --breaking
```
### Template
Copy `assets/commit-template.txt` to `~/.gitmessage.txt`:
```bash
git config --global commit.template ~/.gitmessage.txt
```
## References
- **Official Guide**: https://docs.typo3.org/m/typo3/guide-contributionworkflow/main/en-us/Appendix/CommitMessage.html
- **Gerrit Documentation**: https://review.typo3.org/Documentation/
- **TYPO3 Forge**: https://forge.typo3.org
## Quick Reference
| Element | Format | Example |
|---------|--------|---------|
| Bugfix | `[BUGFIX] Description` | `[BUGFIX] Fix null pointer in indexer` |
| Feature | `[FEATURE] Description` | `[FEATURE] Add WebP support` |
| Task | `[TASK] Description` | `[TASK] Refactor cache manager` |
| Breaking | `[!!!][TYPE] Description` | `[!!!][TASK] Drop PHP 7.4 support` |
| Resolves | `Resolves: #12345` | Closes issue on merge |
| Related | `Related: #12345` | Links without closing |
| Releases | `Releases: main, 13.4` | Target versions |

View File

@@ -0,0 +1,460 @@
# TYPO3 Commit Message Hook
Deep dive into the `Build/git-hooks/commit-msg` hook: validation rules, error messages, and troubleshooting.
## Overview
The commit-msg hook is a Git client-side hook that validates commit messages before they're created. TYPO3 uses this to enforce commit message standards and automatically add Change-Id for Gerrit tracking.
**Location**: `Build/git-hooks/commit-msg`
**Installed to**: `.git/hooks/commit-msg`
## Installation
### Automated (Recommended)
```bash
composer gerrit:setup
```
This command:
1. Copies hook from `Build/git-hooks/commit-msg` to `.git/hooks/commit-msg`
2. Makes it executable
3. Sets up Gerrit configuration
### Manual
```bash
# Copy hook
cp Build/git-hooks/commit-msg .git/hooks/commit-msg
# Make executable
chmod +x .git/hooks/commit-msg
```
### Verify Installation
```bash
# Check if hook exists and is executable
ls -la .git/hooks/commit-msg
# Expected output:
# -rwxr-xr-x 1 user group 8192 Dec 15 10:00 .git/hooks/commit-msg
```
## Hook Functions
### 1. Change-Id Generation
**Purpose**: Auto-generate unique Change-Id for Gerrit patch tracking
**Function**: `add_ChangeId()`
**Behavior**:
- Generates unique hash based on commit content
- Adds `Change-Id: I<hash>` to commit message footer
- Skips if Change-Id already exists
- Skips for fixup!/squash! commits
- Places Change-Id after Resolves/Releases footer
**Format**:
```
Change-Id: I1234567890abcdef1234567890abcdef12345678
```
**Critical Rules**:
- NEVER manually add Change-Id
- NEVER modify existing Change-Id
- NEVER remove Change-Id
- Same Change-Id = same patch (for updates)
- Different Change-Id = new patch
### 2. Line Length Validation
**Function**: `checkForLineLength()`
**Rules**:
- Maximum line length: 72 characters
- Applies to subject and body
- Excludes comment lines (starting with `#`)
- URLs can exceed limit
**Error Message**:
```
The maximum line length of 72 characters is exceeded.
```
**Location**: Line 200 in hook
### 3. Commit Type Validation
**Function**: `checkForCommitType()`
**Rules**:
- First line must contain commit type in brackets
- Valid types: `[BUGFIX]`, `[FEATURE]`, `[TASK]`, `[DOCS]`, `[SECURITY]`
- Breaking changes: `[!!!][TYPE]`
**Regex**: `/^\[^]]+\] .+$/`
**Error Message**:
```
Your first line has to contain a commit type like '[BUGFIX]'.
```
**Location**: Line 209 in hook
### 4. Resolves Tag Validation
**Function**: `checkForResolves()`
**Rules**:
- Every commit MUST have at least one `Resolves:` or `Fixes:` line
- Format: `Resolves: #<number>` or `Fixes: #<number>`
- Must be on separate line (not inline)
- Issue number must be numeric
**Regex**: `/^(Resolves|Fixes): #[0-9]+$/`
**Error Message** (as of v1.1):
```
You need at least one 'Resolves|Fixes: #<issue number>' line.
```
**Updated Message** (as of v1.2, see Issue #107881):
```
You need at least one 'Resolves: #<issue number>' line.
```
**Location**: Line 218 in hook
**Important Context**:
- The regex accepts both `Resolves:` and `Fixes:` for backward compatibility
- TYPO3 community standard is to use ONLY `Resolves:`
- The error message guides users toward the standard
- This was the source of documentation confusion in Issue #105737
### 5. Releases Tag Validation
**Function**: `checkForReleases()`
**Rules**:
- Every commit MUST have `Releases:` line
- Format: `Releases: main, 13.4, 12.4` (comma-separated)
- Valid values: `main`, version numbers like `13.4`, `12.4`
**Regex**: `/^Releases: (main|[0-9]+\.[0-9])(, *(main|[0-9]+\.[0-9]))*$/`
**Error Message**:
```
You need a 'Releases:' line. For instance: Releases: main, 8.7
```
**Location**: Line 227 in hook
## Complete Validation Flow
```
Commit attempted
1. Check line length (≤ 72 chars)
2. Check commit type ([BUGFIX], etc.)
3. Check Resolves/Fixes tag exists
4. Check Releases tag exists
All pass? → Add Change-Id → Commit succeeds
Any fail? → Show errors → Commit rejected
```
## Error Messages
### Full Error Output
When validation fails:
```
------------------------------------------------------------------
>> ERROR in your commit message:
- The maximum line length of 72 characters is exceeded.
- You need at least one 'Resolves: #<issue number>' line.
- You need a 'Releases:' line. For instance: Releases: main, 8.7
Please refer to [1] for details on the commit requirements.
You should fix this and then do commit --amend etc.
[1] https://docs.typo3.org/typo3cms/ContributionWorkflowGuide/latest/singlehtml/Index.html#commit-message-rules-for-typo3-cms
------------------------------------------------------------------
```
### Individual Errors
| Check | Error Message |
|-------|---------------|
| Line length | `The maximum line length of 72 characters is exceeded.` |
| Commit type | `Your first line has to contain a commit type like '[BUGFIX]'.` |
| Resolves tag | `You need at least one 'Resolves: #<issue number>' line.` |
| Releases tag | `You need a 'Releases:' line. For instance: Releases: main, 8.7` |
## Troubleshooting
### Hook Not Running
**Symptom**: Commits succeed without validation
**Causes**:
1. Hook not installed
2. Hook not executable
3. Git hooks disabled
**Solutions**:
```bash
# Check if hook exists
ls -la .git/hooks/commit-msg
# Reinstall hook
composer gerrit:setup
# Verify permissions
chmod +x .git/hooks/commit-msg
# Check Git config (hooks disabled?)
git config --get core.hooksPath
```
### Hook Rejecting Valid Commit
**Symptom**: Valid commit message rejected
**Debug**:
```bash
# Test commit message manually
bash .git/hooks/commit-msg .git/COMMIT_EDITMSG
# Check for hidden characters
cat -A .git/COMMIT_EDITMSG
# Verify line endings (should be LF, not CRLF)
file .git/COMMIT_EDITMSG
```
**Common issues**:
- Windows line endings (CRLF) instead of Unix (LF)
- Trailing whitespace
- Non-ASCII characters in unexpected places
- Tabs vs spaces
### Change-Id Not Generated
**Symptom**: Commit succeeds but no Change-Id added
**Causes**:
1. Change-Id already exists (manual addition)
2. Config disables it: `git config --get gerrit.createChangeId` returns `false`
3. fixup!/squash! commit (intentionally skipped)
**Solutions**:
```bash
# Enable Change-Id generation
git config gerrit.createChangeId true
# Re-commit to generate
git commit --amend --no-edit
# Verify Change-Id added
git log -1
```
### Multiple Change-Ids
**Symptom**: Commit has multiple Change-Id lines
**Impact**: Gerrit will reject or behave unexpectedly
**Fix**:
```bash
# Edit commit message
git commit --amend
# Remove duplicate Change-Id lines (keep only one)
# Save and exit
```
## Hook Customization
### When to Customize
**Valid reasons**:
- Project-specific validation rules
- Additional required tags
- Custom commit message format
**Invalid reasons**:
- Bypassing validation (use `--no-verify` temporarily instead)
- Making validation more lenient (breaks standardization)
### Safe Customization Pattern
```bash
# 1. Fork the hook
cp .git/hooks/commit-msg .git/hooks/commit-msg.custom
# 2. Add custom validation
# Edit .git/hooks/commit-msg.custom
# 3. Call from main hook
# In .git/hooks/commit-msg, add:
# .git/hooks/commit-msg.custom "$1"
# 4. Document customization
echo "Custom validation: <description>" >> .git/hooks/commit-msg.custom
```
### Example: Additional Tag
Add optional `Sponsored-by:` tag:
```bash
# Add to checkForResolves() section
checkForSponsor() {
if grep -q -E '^Sponsored-by: ' "$MSG"; then
# Valid sponsor tag found
return 0
fi
}
# Call in validation sequence
checkForLineLength
checkForCommitType
checkForResolves
checkForReleases
checkForSponsor # Custom check
```
## Bypassing Hook
### When to Bypass
**Valid cases**:
- Emergency hotfixes
- Rebasing with preserved commits
- Importing historical commits
- Temporary testing
**Invalid cases**:
- Avoiding fixing commit message
- Regular development workflow
### How to Bypass
```bash
# Single commit
git commit --no-verify
# Amend without hook
git commit --amend --no-verify
# Rebase without hook
GIT_EDITOR=true git rebase -i HEAD~5 --no-verify
```
**Warning**: Gerrit will still reject invalid commits! Bypassing hook locally doesn't bypass Gerrit validation.
## Hook History
### Version 1.1 (Current)
**From**: TYPO3 CI Review 1.1
**Based on**: Gerrit Code Review 2.14.6
**Changes from Gerrit original**:
- Added line length check (72 chars)
- Added commit type check ([BUGFIX], etc.)
- Added Resolves/Fixes check
- Added Releases check
- Modified Change-Id placement (after footer)
### Proposed Version 1.2 (Issue #107881)
**Change**: Update error message for Resolves check
- **Old**: `'Resolves|Fixes: #<issue number>'`
- **New**: `'Resolves: #<issue number>'`
**Rationale**:
- TYPO3 standard is `Resolves:` only
- `Fixes:` accepted for backward compatibility only
- Error message should guide toward standard practice
**Validation regex unchanged**: Still accepts both for compatibility
## Hook Source Code Structure
```bash
Build/git-hooks/commit-msg
├── License header (Apache 2.0)
├── TYPO3 changes documentation
├── add_ChangeId() function
│ ├── clean_message preprocessing
│ ├── Skip conditions (fixup, squash, existing)
│ ├── ID generation
│ └── AWK script for placement
├── _gen_ChangeId() helper
├── _gen_ChangeIdInput() helper
├── Validation functions:
│ ├── checkForLineLength()
│ ├── checkForCommitType()
│ ├── checkForResolves()
│ └── checkForReleases()
├── Validation execution
└── add_ChangeId() call
```
## Best Practices
### For Contributors
1. **Install hook early**: First thing after cloning
2. **Never bypass**: Fix messages instead of bypassing
3. **Don't fight the hook**: Learn requirements, follow them
4. **Use templates**: `git config commit.template ~/.gitmessage.txt`
5. **Test locally**: Use `scripts/validate-commit-message.py`
### For Maintainers
1. **Document changes**: Keep this reference updated
2. **Test thoroughly**: Validate changes don't break existing commits
3. **Backward compatible**: Keep regex accepting old patterns
4. **Clear errors**: Make error messages actionable
5. **Version carefully**: Changes affect all contributors
## Related Files
- `Build/git-hooks/commit-msg` - The hook itself
- `Build/git-hooks/pre-commit` - Code quality checks
- `assets/commit-template.txt` - Commit message template
- `scripts/validate-commit-message.py` - Offline validator
- `references/commit-message-format.md` - Commit message specification
## References
- **Hook Source**: https://github.com/TYPO3/typo3/blob/main/Build/git-hooks/commit-msg
- **Git Hooks Docs**: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
- **Gerrit Hooks**: https://gerrit-review.googlesource.com/Documentation/cmd-hook-commit-msg.html
- **Issue #107881**: Standardize error message to mention only Resolves
## Quick Reference
| Validation | Required Format | Error if Missing |
|------------|----------------|------------------|
| Commit type | `[TYPE]` in first line | Yes |
| Line length | ≤ 72 characters | Yes |
| Resolves | `Resolves: #123` | Yes |
| Releases | `Releases: main` | Yes |
| Change-Id | Auto-generated | N/A (added by hook) |
## See Also
- `scripts/validate-commit-message.py` - Test messages offline
- `scripts/create-commit-message.py` - Generate compliant messages
- `assets/commit-template.txt` - Pre-filled template

View File

@@ -0,0 +1,674 @@
# TYPO3 Core Development - Complete DDEV Setup Workflow
Production-tested workflow for setting up a complete TYPO3 Core development environment with DDEV.
## Overview
This workflow creates a fully functional TYPO3 Core development environment with:
- TYPO3 Core v14 (main branch)
- PHP 8.4 on Apache with FPM
- MariaDB 10.6
- Test data and styleguide extensions
- Git configured for Gerrit submissions
- Ready for Core development and testing
## Prerequisites
**Required**:
- Git installed and configured
- DDEV installed (https://ddev.readthedocs.io/)
- SSH keys configured for GitHub and Gerrit
**Verify DDEV**:
```bash
ddev version
# Should show DDEV version >= 1.21
```
## Complete Setup Workflow
### Step 1: Create Project Directory
```bash
# Option A: Using 'take' (zsh/oh-my-zsh)
take t3coredev-14-php8-4
# Option B: Standard bash
mkdir -p t3coredev-14-php8-4 && cd t3coredev-14-php8-4
```
**Note**: Use descriptive directory names indicating TYPO3 version and PHP version.
### Step 2: Clone TYPO3 Core Repository
```bash
# Clone from GitHub (faster than Gerrit for initial clone)
git clone git@github.com:typo3/typo3 .
# Note: The dot (.) clones into current directory
```
### Step 3: Configure Git for TYPO3 Contributions
```bash
# Set your identity
git config user.name "YOUR NAME"
git config user.email "YOUR@EMAIL"
# Enable automatic rebase (required for TYPO3)
git config branch.autosetuprebase remote
```
### Step 4: Install Git Hooks
```bash
# Copy commit-msg hook (adds Change-Id)
cp Build/git-hooks/commit-msg .git/hooks/commit-msg
# Alternative: Use composer command
# composer gerrit:setup
```
### Step 5: Configure Gerrit Remote
```bash
# Set Gerrit as push destination
git config remote.origin.pushurl ssh://YOURT3OUSERNAME@review.typo3.org:29418/Packages/TYPO3.CMS.git
# Configure push refspec for Gerrit review
git config remote.origin.push +refs/heads/main:refs/for/main
```
**Important**: Replace `YOURT3OUSERNAME` with your actual Gerrit username!
### Step 6: Configure DDEV Project
```bash
# Set project type to TYPO3
ddev config --project-type typo3 -y
# Configure timezone (adjust to your location)
ddev config --timezone "Europe/Vienna"
# Set PHP version for v14 development
ddev config --php-version=8.4
# Use Apache with FPM (recommended for Core dev)
ddev config --webserver-type=apache-fpm
# Set MariaDB version
ddev config --database=mariadb:10.6
```
**PHP Version Notes**:
- TYPO3 v14: PHP 8.2, 8.3, 8.4
- TYPO3 v13: PHP 8.1, 8.2, 8.3
- Check `composer.json` for exact requirements
### Step 7: Configure DDEV Environment Variables
```bash
# Set TYPO3 context to Development/Ddev
ddev config --web-environment-add="TYPO3_CONTEXT=Development/Ddev"
# Set Composer root version for dev branch
ddev config --web-environment-add="COMPOSER_ROOT_VERSION=14.0.x-dev"
```
**Context Meanings**:
- `Development/Ddev`: Enables debugging, disables caching
- `Production`: Live site configuration
- `Testing`: For automated test environments
### Step 8: Start DDEV
```bash
ddev start
```
**What happens**:
1. Creates Docker containers (web, db, phpmyadmin)
2. Configures networking
3. Sets up SSL certificates
4. Mounts project directory
**Expected output**:
```
Starting t3coredev-14-php8-4...
Successfully started t3coredev-14-php8-4
Project can be reached at https://t3coredev-14-php8-4.ddev.site
```
### Step 9: Install Dependencies
```bash
# Use TYPO3's runTests.sh script (preferred for Core dev)
./Build/Scripts/runTests.sh -s composerInstall
# Alternative: Direct composer command
# ddev composer install
```
**Why runTests.sh?**
- Ensures correct Composer flags
- Consistent with CI environment
- Handles Core-specific requirements
### Step 10: Prepare TYPO3 Installation
```bash
# Create installation trigger file
ddev exec 'touch /var/www/html/FIRST_INSTALL'
# Enable Install Tool
ddev exec 'touch /var/www/html/typo3conf/ENABLE_INSTALL_TOOL'
ddev exec 'echo "KEEP_FILE" > /var/www/html/typo3conf/ENABLE_INSTALL_TOOL'
```
**File purposes**:
- `FIRST_INSTALL`: Triggers installation wizard
- `ENABLE_INSTALL_TOOL`: Enables Install Tool access (with KEEP_FILE prevents auto-deletion)
### Step 11: Run TYPO3 Setup
```bash
ddev typo3 setup \
--driver=mysqli \
--host=db \
--port=3306 \
--dbname=db \
--username=db \
--password=db \
--admin-username=backenduser \
--admin-user-password='YOUR_SECURE_PASSWORD' \
--admin-email='YOUR@EMAIL' \
--project-name='TYPO3 Core Dev v14 PHP 8.4' \
--no-interaction \
--server-type=apache \
--force
```
**Important**:
- Replace `YOUR_SECURE_PASSWORD` with your preferred admin password
- Replace `YOUR@EMAIL` with your email
- Database credentials (db/db/db) are DDEV defaults
**What this creates**:
- Database tables and schema
- Backend admin user account
- Basic TYPO3 configuration
- AdditionalConfiguration.php
### Step 12: Activate Core Extensions
```bash
# Set up extensions first
ddev typo3 extension:setup
# Activate indexed_search (relevant for testing search functionality)
ddev typo3 extension:activate indexed_search
# Activate styleguide (provides test data and UI components)
ddev typo3 extension:activate styleguide
# Activate scheduler (for scheduled tasks)
ddev typo3 extension:activate scheduler
```
**Extension purposes**:
- `indexed_search`: Full-text search (relevant to bug #105737!)
- `styleguide`: Test data generator, UI component showcase
- `scheduler`: Cron-like task scheduling
### Step 13: Configure Backend User Groups
```bash
ddev typo3 setup:begroups:default --groups=Both
```
**Creates**:
- Editor group (content management)
- Advanced Editor group (extended permissions)
- Assigns both groups to admin user
### Step 14: Generate Test Data
```bash
# Generate TCA (Table Configuration Array) examples
ddev typo3 styleguide:generate --create -- tca
# Generate frontend system template
ddev typo3 styleguide:generate --create -- frontend-systemplate
```
**Test data includes**:
- All TCA field types with examples
- Content elements with various configurations
- Pages with different properties
- Frontend templates and TypoScript
### Step 15: Launch TYPO3 Backend
```bash
ddev launch /typo3
```
Opens TYPO3 backend in your default browser.
**Login credentials**:
- Username: `backenduser`
- Password: Whatever you set in Step 11
## Post-Setup Verification
### Verify Installation
**Check TYPO3 is running**:
```bash
ddev launch
```
**Access Install Tool**:
```bash
ddev launch /typo3/install.php
```
**View site info**:
```bash
ddev describe
```
### Verify Git Configuration
```bash
# Check user config
git config user.name
git config user.email
# Check Gerrit config
git config remote.origin.pushurl
git config remote.origin.push
# Verify hooks
ls -la .git/hooks/commit-msg
```
### Verify DDEV Configuration
```bash
# View DDEV config
cat .ddev/config.yaml
# Should show:
# - project_type: typo3
# - php_version: "8.4"
# - webserver_type: apache-fpm
# - database: mariadb:10.6
# - web_environment:
# - TYPO3_CONTEXT=Development/Ddev
# - COMPOSER_ROOT_VERSION=14.0.x-dev
```
### Test Core Functionality
**Access frontend**:
```bash
ddev launch
```
**Run tests**:
```bash
# Unit tests
./Build/Scripts/runTests.sh -s unit
# Functional tests
./Build/Scripts/runTests.sh -s functional
# Check available test suites
./Build/Scripts/runTests.sh -h
```
## Development Workflow
### Creating a Feature Branch
```bash
# Ensure main is up-to-date
git checkout main
git pull origin main
# Create feature branch
git checkout -b feature/105737-fix-indexed-search-crash
```
### Making Changes
```bash
# Make code changes
vim typo3/sysext/indexed_search/Classes/Indexer.php
# Stage changes
git add .
# Commit with proper message
git commit
# (Use commit message template)
```
### Testing Changes
```bash
# Run relevant tests
./Build/Scripts/runTests.sh -s unit -- \
typo3/sysext/indexed_search/Tests/Unit/
# Check code style
./Build/Scripts/runTests.sh -s cgl -n
```
### Submitting to Gerrit
```bash
# Push to Gerrit for review
git push origin HEAD:refs/for/main
```
## Useful DDEV Commands
### Project Management
```bash
# Start project
ddev start
# Stop project
ddev stop
# Restart project
ddev restart
# Delete project (keeps files)
ddev delete
# Power off all DDEV projects
ddev poweroff
```
### Database Management
```bash
# Export database
ddev export-db --file=backup.sql.gz
# Import database
ddev import-db --file=backup.sql.gz
# Access database CLI
ddev mysql
# Launch phpMyAdmin
ddev launch -p
```
### TYPO3 Commands
```bash
# Clear all caches
ddev typo3 cache:flush
# Clear specific cache
ddev typo3 cache:flush --group=system
# Run scheduler tasks
ddev typo3 scheduler:run
# List available commands
ddev typo3 list
```
### Debugging
```bash
# View logs
ddev logs
# Follow logs (like tail -f)
ddev logs -f
# SSH into container
ddev ssh
# Execute command in container
ddev exec 'command'
```
### Performance
```bash
# View resource usage
docker stats
# Restart services if slow
ddev restart
```
## Customization Options
### Different PHP Versions
```bash
# Switch to PHP 8.2
ddev config --php-version=8.2
ddev restart
```
### Different Database Versions
```bash
# Use MySQL instead of MariaDB
ddev config --database=mysql:8.0
ddev restart
```
### Additional Services
```bash
# Add Redis
ddev get ddev/ddev-redis
# Add Elasticsearch
ddev get ddev/ddev-elasticsearch
# Add Mailhog (email testing)
ddev config --mailhog-port=8026
```
### Custom Domain
```bash
# Add additional hostname
ddev config --additional-hostnames=t3dev.local
ddev restart
```
## Troubleshooting
### "Port already allocated"
**Problem**: DDEV can't start because ports are in use
**Solution**:
```bash
# Stop other DDEV projects
ddev poweroff
# Or change port
ddev config --router-http-port=8080 --router-https-port=8443
```
### "Composer timeout"
**Problem**: Composer operations timeout
**Solution**:
```bash
# Increase timeout
ddev composer config --global process-timeout 2000
# Or use runTests.sh
./Build/Scripts/runTests.sh -s composerInstall
```
### "Cannot write to directory"
**Problem**: Permission issues in container
**Solution**:
```bash
# Fix permissions
ddev exec 'chmod -R 777 var/ typo3temp/ typo3conf/'
# Or restart DDEV
ddev restart
```
### "Database connection failed"
**Problem**: TYPO3 can't connect to database
**Solution**:
```bash
# Check database is running
ddev describe
# Verify credentials in LocalConfiguration.php
ddev exec 'cat typo3conf/LocalConfiguration.php | grep -A5 DB'
# Should show: host=db, username=db, password=db, database=db
```
## Best Practices
### Directory Naming
Use descriptive names indicating:
- TYPO3 version: `t3coredev-14`
- PHP version: `php8-4`
- Purpose: `coredev`, `testing`, `feature-name`
Examples:
- `t3coredev-14-php8-4`
- `t3-bugfix-105737`
- `t3-testing-indexed-search`
### Git Workflow
1. **Always start from main**:
```bash
git checkout main
git pull origin main
```
2. **Use feature branches**:
```bash
git checkout -b feature/issue-description
```
3. **Keep single commit per patch**:
```bash
git commit --amend # Update existing commit
```
4. **Rebase regularly**:
```bash
git fetch origin
git rebase origin/main
```
### DDEV Management
1. **Stop unused projects**:
```bash
ddev list
ddev stop <project>
```
2. **Clean up old projects**:
```bash
ddev delete <project>
# Then manually delete directory
```
3. **Monitor resources**:
```bash
docker stats
```
### Testing Workflow
1. **Test before committing**:
```bash
./Build/Scripts/runTests.sh -s unit
./Build/Scripts/runTests.sh -s functional
```
2. **Check code style**:
```bash
./Build/Scripts/runTests.sh -s cgl -n
```
3. **Fix code style automatically**:
```bash
./Build/Scripts/runTests.sh -s cgl
```
## Quick Reference
### Essential Commands
| Task | Command |
|------|---------|
| Start DDEV | `ddev start` |
| Stop DDEV | `ddev stop` |
| Open backend | `ddev launch /typo3` |
| Clear cache | `ddev typo3 cache:flush` |
| Run unit tests | `./Build/Scripts/runTests.sh -s unit` |
| Install composer | `./Build/Scripts/runTests.sh -s composerInstall` |
| View logs | `ddev logs -f` |
| SSH into container | `ddev ssh` |
| Export database | `ddev export-db --file=backup.sql.gz` |
| Git push to Gerrit | `git push origin HEAD:refs/for/main` |
### TYPO3 Versions & PHP Compatibility
| TYPO3 Version | PHP Versions | Branch | Status |
|---------------|--------------|--------|--------|
| v14 (main) | 8.2, 8.3, 8.4 | main | Development |
| v13 (LTS) | 8.1, 8.2, 8.3 | 13.4 | Active |
| v12 (ELTS) | 8.1, 8.2 | 12.4 | Security only |
### Default Credentials
| Service | URL | Username | Password |
|---------|-----|----------|----------|
| Backend | `https://[project].ddev.site/typo3` | backenduser | (your choice) |
| Database | `db:3306` | db | db |
| phpMyAdmin | `https://[project].ddev.site:8037` | db | db |
## Integration with typo3-ddev-skill
This workflow complements the `typo3-ddev-skill`:
- Use `typo3-ddev-skill` for quick setup automation
- Use this workflow for manual step-by-step understanding
- Both produce equivalent development environments
## Additional Resources
- **DDEV Documentation**: https://ddev.readthedocs.io/
- **TYPO3 Development**: https://docs.typo3.org/m/typo3/reference-coreapi/
- **runTests.sh Guide**: https://docs.typo3.org/m/typo3/guide-contributionworkflow/main/en-us/Testing/
- **TYPO3 Slack**: https://typo3.slack.com (#typo3-cms-coredev)
---
**Note**: This workflow is based on proven production usage and is continuously updated for current TYPO3 versions. Always check official documentation for the latest recommendations.

View File

@@ -0,0 +1,436 @@
# Forge REST API Documentation
Complete guide to using the TYPO3 Forge (Redmine) REST API for programmatic issue management.
## Overview
TYPO3 Forge (https://forge.typo3.org) is built on Redmine and exposes a REST API for:
- Creating issues
- Updating issues
- Querying project metadata
- Managing issue relationships
## Authentication
### Get API Key
1. Log in to https://forge.typo3.org
2. Go to https://forge.typo3.org/my/account
3. Find "API access key" on the right side
4. Click "Show" to reveal your key
5. Store securely (treat like a password!)
### Using API Key
Pass via HTTP header:
```bash
-H "X-Redmine-API-Key: your-api-key-here"
```
**Security**: Never commit API keys to repositories. Use environment variables:
```bash
export FORGE_API_KEY="your-api-key-here"
```
## Base URL
All API endpoints use:
```
https://forge.typo3.org
```
## Common Endpoints
### Create Issue
**Endpoint**: `POST /issues.json`
**Request**:
```bash
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
-d '{
"issue": {
"project_id": "typo3cms-core",
"subject": "Issue title here",
"description": "Detailed description",
"tracker_id": 1,
"category_id": 975,
"priority_id": 4,
"custom_fields": [
{"id": 4, "value": "13"}
]
}
}' \
https://forge.typo3.org/issues.json
```
**Response**:
```json
{
"issue": {
"id": 107881,
"project": {"id": 27, "name": "TYPO3 Core"},
"tracker": {"id": 1, "name": "Bug"},
"status": {"id": 1, "name": "New"},
"priority": {"id": 4, "name": "Should have"},
"subject": "Issue title here",
"description": "Detailed description",
"created_on": "2024-12-15T10:30:00Z",
"updated_on": "2024-12-15T10:30:00Z"
}
}
```
**Extract Issue Number**:
```bash
# Parse with jq
curl ... | jq -r '.issue.id'
# Parse with grep
curl ... | grep -oP '"id":\K[0-9]+' | head -1
```
### Get Project Metadata
**Endpoint**: `GET /projects/typo3cms-core.json`
**Request**:
```bash
curl -H "X-Redmine-API-Key: $FORGE_API_KEY" \
https://forge.typo3.org/projects/typo3cms-core.json
```
**Response includes**:
- Available trackers (Bug, Feature, Task, etc.)
- Issue categories (Backend, Frontend, etc.)
- Custom field definitions
### Get Issue Details
**Endpoint**: `GET /issues/{id}.json`
**Request**:
```bash
curl -H "X-Redmine-API-Key: $FORGE_API_KEY" \
https://forge.typo3.org/issues/105737.json
```
**Response includes**:
- Full issue details
- Custom fields
- Status and assignments
- Related issues
## Field IDs
### Trackers
| ID | Name |
|----|------|
| 1 | Bug |
| 2 | Feature |
| 4 | Task |
| 6 | Story |
| 10 | Epic |
### Priorities
| ID | Name |
|----|------|
| 2 | Nice to have |
| 3 | Must have |
| 4 | Should have |
| 5 | Could have |
### Common Categories
| ID | Name |
|------|------|
| 971 | Backend API |
| 972 | Backend User Interface |
| 973 | Caching |
| 974 | Database API (Doctrine DBAL) |
| 975 | Miscellaneous |
| 976 | Extension Manager |
| 977 | Frontend |
| 1000 | Indexed Search |
| 1003 | Content Rendering |
| 1004 | Documentation |
**Get full list**: Use `scripts/query-forge-metadata.sh`
### Custom Fields
| ID | Name | Purpose |
|----|------|---------|
| 3 | Tags | Comma-separated keywords |
| 4 | TYPO3 Version | Version affected (e.g., "13", "12") |
| 5 | PHP Version | PHP version (e.g., "8.2", "8.3") |
| 8 | Complexity | Complexity estimate |
| 15 | Is Regression | Whether it's a regression |
| 18 | Sprint Focus | Sprint assignment |
## Complete Examples
### Example 1: Create Bug Report
```bash
export FORGE_API_KEY="your-api-key-here"
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
-d '{
"issue": {
"project_id": "typo3cms-core",
"subject": "Indexed search causes crash on malformed UTF-8",
"description": "When processing content with malformed UTF-8, the indexed search indexer crashes with TypeError in PHP 8.2+.\n\nSteps to reproduce:\n1. Create page with malformed UTF-8 content\n2. Run indexer\n3. Observe crash\n\nExpected: Graceful handling\nActual: TypeError exception",
"tracker_id": 1,
"category_id": 1000,
"priority_id": 4,
"custom_fields": [
{"id": 4, "value": "13"},
{"id": 5, "value": "8.2"},
{"id": 3, "value": "indexed search, UTF-8, crash"}
]
}
}' \
https://forge.typo3.org/issues.json
```
### Example 2: Create Feature Request
```bash
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
-d '{
"issue": {
"project_id": "typo3cms-core",
"subject": "Add WebP image format support",
"description": "Add native WebP support to TYPO3 image processing:\n\n- WebP MIME type detection\n- Image manipulation support\n- Configuration options\n\nBenefit: 25-30% better compression than JPEG",
"tracker_id": 2,
"category_id": 977,
"priority_id": 5,
"custom_fields": [
{"id": 4, "value": "14"},
{"id": 3, "value": "WebP, images, performance"}
]
}
}' \
https://forge.typo3.org/issues.json
```
### Example 3: Create Task
```bash
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
-d '{
"issue": {
"project_id": "typo3cms-core",
"subject": "Standardize commit-msg hook error message",
"description": "Update Build/git-hooks/commit-msg error message to mention only '\''Resolves:'\'' tag instead of '\''Resolves|Fixes:'\'' to align with TYPO3 community standard.\n\nWhile validation regex accepts both for backward compatibility, error message should guide toward single standard keyword.",
"tracker_id": 4,
"category_id": 975,
"priority_id": 4,
"custom_fields": [
{"id": 4, "value": "14"}
]
}
}' \
https://forge.typo3.org/issues.json
```
## Error Handling
### Common Errors
**422 Unprocessable Entity**:
```json
{
"errors": ["Category is not included in the list"]
}
```
**Solution**: Check field IDs are valid for the project
**422 - Required Field Missing**:
```json
{
"errors": ["Typo3 version cannot be blank"]
}
```
**Solution**: Add required custom field (id: 4 for TYPO3 version)
**401 Unauthorized**:
```
{"errors": ["You are not authorized to access this page."]}
```
**Solution**: Check API key is correct and has permissions
### Validation
Before creating issue, validate:
- [ ] API key is set and valid
- [ ] project_id is "typo3cms-core"
- [ ] subject is descriptive (not too generic)
- [ ] tracker_id is valid (1, 2, or 4 most common)
- [ ] category_id matches project categories
- [ ] TYPO3 version custom field included (id: 4)
## Best Practices
### Subject Lines
**Good**:
- "Indexed search crashes on malformed UTF-8"
- "Add WebP image format support"
- "Standardize commit-msg hook error message"
**Bad**:
- "Fix bug"
- "Improvement needed"
- "Question about feature"
### Descriptions
**Structure**:
1. Brief summary (what is the issue)
2. Steps to reproduce (for bugs)
3. Expected behavior
4. Actual behavior
5. Additional context
**Include**:
- Error messages
- Stack traces
- Version information
- Configuration details
**Avoid**:
- Asking questions (use Slack instead)
- Multiple unrelated issues in one ticket
- Vague descriptions without details
### Categories
Choose most specific category:
- Not "Miscellaneous" if more specific exists
- "Backend API" for backend PHP code
- "Backend User Interface" for backend UI/UX
- "Frontend" for frontend rendering
- Component-specific for extensions (e.g., "Indexed Search")
### Priority
**Guidelines**:
- **Must have (3)**: Blocking issues, critical bugs, security
- **Should have (4)**: Normal bugs, important features (most common)
- **Could have (5)**: Nice-to-have features, minor improvements
- **Nice to have (2)**: Low priority, future considerations
## Automation Tips
### Store Issue Template
```bash
cat > /tmp/issue-template.json <<'EOF'
{
"issue": {
"project_id": "typo3cms-core",
"subject": "",
"description": "",
"tracker_id": 1,
"category_id": 975,
"priority_id": 4,
"custom_fields": [
{"id": 4, "value": "13"}
]
}
}
EOF
```
### Parse Response
```bash
# Extract issue number
ISSUE_ID=$(curl ... | jq -r '.issue.id')
# Build URL
ISSUE_URL="https://forge.typo3.org/issues/${ISSUE_ID}"
# Use in commit message
echo "Resolves: #${ISSUE_ID}"
```
### Batch Operations
Query multiple issues:
```bash
for id in 105737 107881 108000; do
curl -H "X-Redmine-API-Key: $FORGE_API_KEY" \
"https://forge.typo3.org/issues/${id}.json"
done
```
## Integration with Git
### Create Issue and Commit
```bash
#!/bin/bash
# Create issue
RESPONSE=$(curl -s -X POST \
-H "Content-Type: application/json" \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
-d @issue.json \
https://forge.typo3.org/issues.json)
# Extract issue number
ISSUE_ID=$(echo "$RESPONSE" | jq -r '.issue.id')
# Use in commit
git commit -m "[BUGFIX] Fix the problem
Resolves: #${ISSUE_ID}
Releases: main"
```
### Link Gerrit Patch to Issue
After submitting to Gerrit:
```bash
GERRIT_URL="https://review.typo3.org/c/Packages/TYPO3.CMS/+/91302"
# Add comment to Forge issue with patch link
# (requires additional API call - see Redmine API docs)
```
## Resources
- **Redmine API Docs**: https://www.redmine.org/projects/redmine/wiki/Rest_api
- **TYPO3 Forge**: https://forge.typo3.org
- **API Access Key**: https://forge.typo3.org/my/account
- **Project Info**: https://forge.typo3.org/projects/typo3cms-core
## Quick Reference
| Task | Endpoint | Method |
|------|----------|--------|
| Create issue | `/issues.json` | POST |
| Get issue | `/issues/{id}.json` | GET |
| Update issue | `/issues/{id}.json` | PUT |
| Get project | `/projects/{id}.json` | GET |
| List issues | `/issues.json?project_id=typo3cms-core` | GET |
## See Also
- `scripts/create-forge-issue.sh` - Interactive issue creation
- `scripts/query-forge-metadata.sh` - Query project metadata
- `references/commit-message-format.md` - For using issue numbers in commits

View File

@@ -0,0 +1,386 @@
# TYPO3 Gerrit Review Patterns - Real-World Insights
Based on analysis of actual merged patches from review.typo3.org, this document captures common review patterns, expectations, and best practices.
## Understanding Revision Counts
**Key Insight**: High revision counts (7-25 patch sets) are NORMAL and expected, not a sign of failure.
**Real Examples**:
- Change #90226: 24 patch sets (functional tests for Extbase FileUpload)
- Change #88519: 14 patch sets (breaking change - Record API)
- Change #91161: 9 patch sets (DI refactoring)
- Change #91284: 7 patch sets (pagetree performance)
**What causes multiple revisions**:
1. CI failures requiring fixes
2. Rebases due to base branch updates
3. Code quality refinements based on reviewer feedback
4. Architectural improvements suggested by core team
5. Edge case handling
6. Scope adjustments (e.g., backport constraints)
**Mindset**: Each revision makes the patch better. Multiple revisions show:
- Responsiveness to feedback
- Iterative improvement
- Collaboration with core team
- Thorough vetting process
## Common Reviewer Feedback Themes
### 1. Architectural Alignment
**Pattern**: Leverage framework patterns over custom solutions
**Example from #91161**:
```
Reviewer: "Just make this method no-op with only the trigger_error() and
remove $this->instances since the service locator will catch classes that
implements the interface automatically."
```
**Expectation**:
- Use dependency injection (DI) over manual instance management
- Leverage service locators for interface-based registration
- Follow TYPO3 framework patterns
- Avoid reinventing framework capabilities
**Best Practice**: Before implementing, check if TYPO3 framework already provides the pattern.
### 2. Configuration Best Practices
**Pattern**: Services.yaml configuration matters
**Example from #90226**:
```
Reviewer: "Why is Domain/Validator/ excluded here? This would prevent
validators from receiving dependency injection."
```
**Expectation**:
- Understand Services.yaml exclusion patterns
- Don't copy boilerplate without understanding
- Enable DI for all appropriate classes
- Reference official documentation for patterns
**Best Practice**: Review Services.yaml carefully, don't blindly copy from examples.
### 3. Performance Validation
**Pattern**: Performance claims require empirical evidence
**Example from #91284**:
```
Reviewer: "It performs much better. If a large number of pages are open,
there is still a very slight delay (milliseconds), but this should not
cause any problems."
Reviewer: "Nice one here, removing the expensive calls to this.nodes.find()
- reduced from O(n^(m²)) [30ms] to O(n) [2-3ms]"
```
**Expectation**:
- Test performance fixes in production-like environments
- Provide computational complexity analysis
- Measure actual performance improvements
- Document before/after metrics
- Multiple reviewers test independently
**Best Practice**: Include performance measurements in commit message or comments.
### 4. Breaking Changes Documentation
**Pattern**: Breaking changes need explicit communication
**Example from #88519**:
```
Reviewer: "The info that the item.record is now a record object, is
important to know for externals."
Reviewer: "That's breaking IMHO" (regarding API change)
```
**Expectation**:
- Document API changes affecting extension developers
- Use `[!!!]` prefix for breaking changes
- Add deprecations with `trigger_error()` for BC breaks
- Consider backport constraints (may limit to main branch)
- Provide migration examples
**Best Practice**: Always think about extension developers when changing public APIs.
### 5. Code Quality Standards
**Pattern**: Modern PHP practices and clean code
**Recurring feedback themes**:
- Use named arguments in function calls
- Separate concerns (split large functions into classes)
- Improve readability through refactoring
- Handle edge cases explicitly
- Remove unused code
**Best Practice**: Follow PSR-12 and modern PHP 8+ features.
### 6. Test Stability Focus
**Pattern**: Tests serve as API stability monitors
**Example from #90226**:
```
Reviewer: "These tests could serve as an important whistleblower with
extbase to monitor API stability and how frequently changes are needed."
```
**Expectation**:
- Tests should catch unintended API changes
- Test scope should be "as simple as possible and as complex as needed"
- Functional tests preferred over unit tests for integration points
- Tests validate real-world usage patterns
**Best Practice**: Write tests that detect breaking changes, not just code coverage.
### 7. Iterative Refinement Philosophy
**Pattern**: Patches improve through collaboration, not rejection
**Observed patterns**:
- Positive language: "I like! 🙌", "Awesome job!", "Nice one here"
- Constructive suggestions: "You could...", "Consider...", "What about..."
- Collaborative problem-solving: Multiple reviewers contribute ideas
- Incremental improvements: Each revision refines the approach
**Expectation**:
- Be responsive to feedback
- Implement suggested improvements
- Ask clarifying questions when needed
- Iterate toward excellence
**Best Practice**: View reviews as mentoring, not gatekeeping.
## Common Revision Patterns
### Pattern 1: CI Failure Cycle
**Typical flow**:
1. Initial submission
2. CI rejects (CGL, PHPStan, tests)
3. Fix CI issues
4. Resubmit
5. New CI issues found
6. Repeat until green
**Prevention**: Use typo3-conformance-skill and typo3-testing-skill BEFORE first submission.
### Pattern 2: Rebase Cycle
**Typical flow**:
1. Patch submitted
2. Base branch updated with other changes
3. Gerrit shows "needs rebase"
4. Rebase on latest main
5. Resolve conflicts
6. CI runs again (may reveal new issues)
7. Repeat as needed
**Prevention**: Rebase regularly during development, not just at submission.
### Pattern 3: Scope Adjustment
**Typical flow**:
1. Patch targets main + version branches
2. Review reveals backport complexity
3. Dependencies on other changes discovered
4. Scope changed to "main only"
5. Commit message updated
**Prevention**: Check dependencies before claiming backport compatibility.
### Pattern 4: Architecture Refinement
**Typical flow**:
1. Working implementation submitted
2. Reviewer suggests better framework pattern
3. Refactor to use framework capabilities
4. Simplify code by removing custom logic
5. May take 3-5 revisions to align
**Prevention**: Study framework patterns before implementing custom solutions.
## Review Timeline Expectations
Based on analyzed patches:
**Simple changes** (1-3 files, no breaking changes):
- Review starts: Within 1-2 days
- First feedback: 2-3 days
- Typical revisions: 2-5 patch sets
- Merge time: 1-2 weeks
**Complex changes** (multiple files, new features):
- Review starts: Within 3-5 days
- First feedback: 3-7 days
- Typical revisions: 7-15 patch sets
- Merge time: 2-4 weeks
**Breaking changes** (API changes, [!!!]):
- Review starts: Within 1-2 days
- First feedback: 1-3 days (architectural concerns raised early)
- Typical revisions: 10-20 patch sets
- Merge time: 3-6 weeks (due to documentation, deprecation)
**Performance fixes**:
- Review starts: Within 1-2 days
- Testing phase: 1-2 weeks (reviewers test in production)
- Typical revisions: 5-10 patch sets
- Merge time: 2-3 weeks
## Key Reviewers and Their Focus Areas
Based on observed patterns:
**Christian Kuhn (lolli)**:
- Architectural alignment
- Framework pattern usage
- Test quality and coverage
- Long-term maintainability
**Benni Mack**:
- Breaking change implications
- Extension developer impact
- API design
- Documentation completeness
**Stefan Bürk**:
- Configuration best practices
- Services.yaml patterns
- Dependency injection
- Code quality standards
**Pattern**: Different reviewers have different expertise areas. Address each reviewer's specific concerns.
## Best Practices from Real Reviews
### Do's
**Respond to every comment**: Even if just "Done" or "Fixed in PS X"
**Test in production-like environments**: Especially for performance fixes
**Use framework patterns**: DI, service locators, event dispatchers
**Document breaking changes**: Think about extension developers
**Iterate based on feedback**: Don't defend, improve
**Keep scope focused**: Don't expand scope during review
**Update commit messages**: Reflect scope or approach changes
**Add deprecations properly**: Use trigger_error() for BC breaks
### Don'ts
**Don't take high revision counts personally**: They're normal and expected
**Don't copy boilerplate blindly**: Understand configuration patterns
**Don't skip testing**: CI will catch it anyway
**Don't ignore architectural feedback**: Core team guides for good reasons
**Don't rush rebases**: Test after rebasing
**Don't claim performance without metrics**: Provide evidence
**Don't break APIs without [!!!]**: Use proper prefixes
**Don't argue with multiple reviewers**: If 2+ reviewers agree, they're probably right
## Handling Common Situations
### Situation 1: "My patch has 10 revisions already"
**Response**: This is normal! Changes #90226 had 24, #88519 had 14. Keep iterating.
**Action**:
1. Review all outstanding comments
2. Address each systematically
3. Test thoroughly after each change
4. Mark comments as resolved with explanation
5. Keep positive attitude
### Situation 2: "Reviewer suggested complete refactoring"
**Response**: Core team is guiding toward better patterns. This is mentoring.
**Action**:
1. Ask clarifying questions if needed
2. Study the suggested pattern
3. Implement as suggested
4. Don't defend original approach
5. Learn framework patterns for future
### Situation 3: "CI keeps failing after fixes"
**Response**: Each rebase can reveal new issues. This is expected.
**Action**:
1. Use typo3-conformance-skill locally
2. Use typo3-testing-skill for test failures
3. Validate BEFORE pushing
4. Consider environment differences
5. Ask for help if stuck
### Situation 4: "Scope changed from 'main + 13.4' to 'main only'"
**Response**: Backport complexity discovered during review. Common pattern.
**Action**:
1. Update commit message (Releases: main)
2. Update Forge issue target version
3. Don't argue - backporting is complex
4. Focus on getting main merged first
5. Backport can be separate patch later
## Learning from Reviews
### What to Extract from Reviews
When reading other reviews:
1. **Architectural patterns**: How do they structure code?
2. **Framework usage**: What TYPO3 APIs do they leverage?
3. **Testing approaches**: How do they test complex scenarios?
4. **Documentation style**: How do they explain breaking changes?
5. **Reviewer priorities**: What concerns get raised most?
### How to Improve
Based on review patterns:
1. **Study merged patches**: See what passes review
2. **Read reviewer comments**: Learn what matters to core team
3. **Use framework patterns**: Follow existing approaches
4. **Test thoroughly**: Validate locally before submission
5. **Be responsive**: Quick turnaround on feedback
6. **Stay positive**: Reviews are mentoring, not rejection
## Summary: Review Success Pattern
**Before submission**:
- ✅ Use typo3-conformance-skill
- ✅ Use typo3-testing-skill
- ✅ Study framework patterns
- ✅ Check Services.yaml configuration
- ✅ Test in realistic environment
**During review**:
- ✅ Respond to all comments promptly
- ✅ Implement suggestions positively
- ✅ Test after each revision
- ✅ Update commit message as needed
- ✅ Ask questions when unclear
**Mindset**:
- ✅ Multiple revisions are normal and healthy
- ✅ Reviews improve your code
- ✅ Core team is mentoring you
- ✅ Each iteration makes TYPO3 better
- ✅ You're learning framework patterns
## References
**Analyzed patches**:
- #90226: Extbase FileUpload functional tests (24 PS)
- #91161: DI in ExtractorService (9 PS)
- #91284: Pagetree performance (7 PS)
- #88519: Record API breaking change (14 PS)
**Review platform**: https://review.typo3.org
**Remember**: The best contributors don't have the fewest revisions - they have the most responsive and collaborative review interactions.

View File

@@ -0,0 +1,783 @@
# TYPO3 Gerrit Workflow Guide
Comprehensive guide for working with Gerrit code review system in TYPO3 Core contributions.
## What is Gerrit?
Gerrit is a web-based code review tool that TYPO3 uses for managing patch submissions. Every code change must go through Gerrit review before being merged into TYPO3 Core.
**Key Concepts**:
- **Patch/Change**: Single commit representing your contribution
- **Patchset**: Version of a patch (same Change-Id, updated code)
- **Review**: Process of evaluating code quality and correctness
- **Merge**: Final acceptance of patch into TYPO3 Core
## Accessing Gerrit
**URL**: https://review.typo3.org
**Authentication**: Use TYPO3.org account credentials
**Search Tool**: https://forger.typo3.com (easier searching)
## Submitting Your First Patch
### Prerequisites
- Git configured for TYPO3 (see Environment Setup)
- SSH keys added to Gerrit
- Commit ready with proper message format
- All changes in single commit
### Push to Gerrit
```bash
# From your feature branch
git push origin HEAD:refs/for/main
```
**What happens**:
1. Git pushes to special Gerrit ref: `refs/for/main`
2. Gerrit creates new review
3. You receive SUCCESS message with review URL
**Expected Output**:
```
remote: Processing changes: new: 1, done
remote:
remote: SUCCESS
remote:
remote: https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345 [NEW]
remote:
To ssh://review.typo3.org:29418/Packages/TYPO3.CMS.git
* [new branch] HEAD -> refs/for/main
```
**Save the review URL!** You'll need it to monitor progress.
### Alternative Push Methods
If you've configured default push settings:
```bash
# Simple push (if remote.origin.push configured)
git push
```
## Continuous Integration
After submission, Gerrit automatically runs tests:
1. **GitLab CI** triggers test pipeline
2. Tests run across multiple PHP versions, code style checks, static analysis (PHPStan), and unit/functional tests
3. Results appear on Gerrit review page
4. Usually completes in 10-20 minutes
**Status Indicators**:
- ✅ Green checkmark: All tests passed
- ❌ Red X: Tests failed
- ⏳ Clock: Tests running
### IMPORTANT: New Patches Start in "Work in Progress" State
**By default, newly submitted patches are marked as WIP (Work in Progress)**. This means:
1. ⚠️ **Not visible to reviewers** - Core team won't see your patch for review
2.**CI tests still run** - You get test feedback immediately
3. 🔍 **You must verify yourself first** - Check all CI jobs before requesting review
4.**You must manually mark as ready** - Change state to "Ready for Review" when done
**Workflow for New Submissions**:
```bash
# 1. Push your patch
git push origin HEAD:refs/for/main
# 2. Note the review URL from output
# https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345 [WIP]
# 3. Wait for CI to complete (10-20 minutes)
# 4. CHECK ALL FAILING JOBS (critical step!)
# - Open the review URL
# - Look for any CI failures (red X marks)
# - For each failure, find the GitLab job URL
# - Read the ACTUAL ERROR LOGS (don't guess!)
# - Fix ALL issues before marking ready
# 5. Once all tests pass, mark as ready for review
#
# Option A: Remove WIP via command line (empty push)
git commit --amend --allow-empty --no-edit
git push origin HEAD:refs/for/main%ready
# Option B: Remove WIP via web UI
# a. Open review URL: https://review.typo3.org/c/Packages/TYPO3.CMS/+/XXXXX
# b. Click "Start Review" button (top right area, near your avatar)
#
# Notes:
# - %ready flag removes WIP state (even with empty pushes)
# - %wip flag sets WIP state: git push origin HEAD:refs/for/main%wip
# - SSH 'gerrit review' command does NOT support WIP flags (use git push flags instead)
```
### Investigating CI Failures (CRITICAL!)
**NEVER assume what failed - ALWAYS check the actual job logs!**
#### Step 1: Find All Failing Jobs
On your Gerrit review page:
1. Scroll to the CI results section
2. Look for red ❌ marks next to job names
3. Note ALL failing job names (there might be multiple!)
Common failing jobs:
- `cgl pre-merge` - Code style violations (PHP CS Fixer)
- `phpstan php X.X pre-merge` - Static analysis errors
- `unit php X.X pre-merge` - Unit test failures
- `functional php X.X pre-merge` - Functional test failures
#### Step 2: Access GitLab Job Logs
For each failing job:
1. Click on the failing job name in Gerrit
2. You'll be redirected to GitLab CI (https://git.typo3.org/typo3/CI/cms/-/jobs/XXXXXX)
3. Click the job log or raw log to see the actual error
**Example**: If job #4896429 failed:
- URL: `https://git.typo3.org/typo3/CI/cms/-/jobs/4896429`
- Raw log: `https://git.typo3.org/typo3/CI/cms/-/jobs/4896429/raw`
#### Step 3: Read and Understand ACTUAL Errors
**DO NOT GUESS!** Read the actual error messages:
**Code Style (cgl) Example**:
```
Fixed 1 of 1 files in ... seconds.
Checked 1 of 1 files in ... seconds.
1) typo3/sysext/indexed_search/Tests/Unit/IndexerTest.php (single_quote)
---------- begin diff ----------
- body: "This content should not appear"
+ body: 'This content should not appear'
----------- end diff -----------
```
**Fix**: Change double quotes to single quotes in test file.
**PHPStan Example**:
```
------ -------------------------------------------------------------------------
Line indexed_search/Tests/Unit/IndexerTest.php
------ -------------------------------------------------------------------------
236 Call to static method PHPUnit\Framework\Assert::assertNotNull()
with string will always evaluate to true.
------ -------------------------------------------------------------------------
```
**Fix**: Remove `assertNotNull()` call - it's redundant for string return types.
**Unit Test Failure Example**:
```
FAILURES!
Tests: 11683, Assertions: 20300, Failures: 1.
There was 1 failure:
1) TYPO3\CMS\IndexedSearch\Tests\Unit\IndexerTest::bodyDescriptionReturnsEmptyStringWhenMaxLengthIsZero
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-''
+'This content should not appear in description'
```
**Fix**: Test logic is wrong - review the test expectations.
#### Step 4: Fix ALL Issues
⚠️ **CRITICAL**: A CI pipeline may have multiple failing jobs. Fix ALL of them:
```bash
# Example: 5 jobs failed (cgl, phpstan, 3x unit tests)
# You must fix:
# 1. Code style issues (single quotes)
# 2. PHPStan warnings (remove redundant assertions)
# 3. Unit test failures (fix test logic)
# Make all fixes
vim typo3/sysext/indexed_search/Tests/Unit/IndexerTest.php
# Stage changes
git add typo3/sysext/indexed_search/Tests/Unit/IndexerTest.php
# Amend commit
git commit --amend --no-edit
# Push updated patchset
git push origin HEAD:refs/for/main
```
#### Step 5: Wait for Re-verification
After pushing fixes:
1. CI automatically runs again
2. Old failed votes (Verified-1) are removed
3. Wait for all jobs to complete
4. Verify ALL jobs are now passing (green ✅)
#### Step 6: Mark as Ready for Review
Once ALL CI jobs pass:
1. Open your review on Gerrit
2. Click **"More"** → **"Start Review"**
3. Optionally add a comment: "Ready for review. All CI checks passing."
4. Your patch is now visible to core team reviewers
### Common CI Failure Patterns
| Job Type | Common Issues | Where to Look |
|----------|---------------|---------------|
| cgl (Code Style) | Double quotes, spacing, indentation | PHP CS Fixer diff in log |
| phpstan | Type errors, redundant code, undefined vars | Line numbers + error descriptions |
| unit tests | Test failures, assertion mismatches | Test name + expected vs actual |
| functional tests | Database issues, integration problems | Full stack trace in log |
**If tests fail**:
1. ⚠️ **DO NOT GUESS** - Always read actual job logs
2. Check ALL failing jobs, not just the first one
3. Access GitLab CI job logs via links on Gerrit
4. Fix all issues in one patchset
5. Push updated patchset (next section)
6. Wait for re-verification
7. Mark as ready only when ALL jobs pass
## Updating Your Patch
When reviewers request changes or tests fail:
### Step 1: Make Changes Locally
```bash
# Make code changes
vim path/to/file.php
# Stage changes
git add path/to/file.php
```
### Step 2: Amend Commit
```bash
# Amend existing commit (DO NOT create new commit!)
git commit --amend
# CRITICAL: Keep the Change-Id line unchanged!
```
### Step 3: Push Updated Patchset
```bash
# Push to same Gerrit change
git push origin HEAD:refs/for/main
```
**What happens**:
- Gerrit matches Change-Id
- Creates new patchset (Patch Set 2, 3, etc.)
- Previous patchsets remain for comparison
- CI tests run again
**Patchset Versioning**:
- Patch Set 1: Initial submission
- Patch Set 2: First update
- Patch Set 3: Second update
- etc.
## Rebasing Your Patch
### Why Rebase?
While you're working, other contributors' patches get merged. Your patch becomes based on outdated code. Rebasing updates your patch to build on the latest codebase.
### When to Rebase
- Merge conflict indicator appears on Gerrit
- Regularly during development (best practice)
- Before running tests
- When requested by reviewers
### Method 1: Browser-Based Rebase (Easiest)
**Requirements**: No merge conflicts
**Steps**:
1. Open your patch on Gerrit
2. Click **Rebase** button (top right)
3. Select "Rebase on top of the main branch"
4. Click **Rebase**
Gerrit automatically:
- Rebases your change
- Creates new patchset
- Runs CI tests
### Method 2: Command-Line Rebase
**When to use**: Merge conflicts exist, or prefer manual control
**Steps**:
```bash
# Ensure on your feature branch
git checkout feature/105737-fix-indexed-search
# Fetch latest changes
git fetch origin
# Rebase onto main
git rebase origin/main
```
**If no conflicts**:
```bash
# Push rebased patch
git push origin HEAD:refs/for/main
```
**If conflicts occur**: See Resolving Merge Conflicts section below.
### Alternative Rebase Methods
**Option A: Pull with Rebase**
```bash
git pull --rebase origin main
```
**Option B: Interactive Rebase** (advanced)
```bash
git rebase -i origin/main
```
## Resolving Merge Conflicts
### What Are Conflicts?
Conflicts occur when:
- You modified file X
- Someone else modified same lines in file X
- Their patch merged first
- Git can't auto-merge
### Conflict Resolution Process
#### Step 1: Start Rebase
```bash
git rebase origin/main
```
**Output with conflicts**:
```
CONFLICT (content): Merge conflict in path/to/file.php
error: could not apply abc123... Your commit message
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
```
#### Step 2: Identify Conflicted Files
```bash
git status
```
**Output**:
```
On branch feature/105737-fix-indexed-search
You are currently rebasing branch 'feature/105737-fix-indexed-search' on 'abc123'.
(fix conflicts and then run "git rebase --continue")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: path/to/file.php
```
#### Step 3: Resolve Conflicts
Open conflicted file in editor:
```php
<<<<<<< HEAD
// Code from main branch (their changes)
$result = newFunction($data);
=======
// Your changes
$result = oldFunction($data);
>>>>>>> Your commit message
```
**Choose resolution**:
**Option A: Keep their changes**
```php
$result = newFunction($data);
```
**Option B: Keep your changes**
```php
$result = oldFunction($data);
```
**Option C: Merge both** (most common)
```php
// Updated to use new function while preserving your logic
$result = newFunction($processedData);
```
Remove conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`).
#### Step 4: Mark as Resolved
```bash
# Stage resolved files
git add path/to/file.php
# Check all conflicts resolved
git status
```
#### Step 5: Continue Rebase
```bash
git rebase --continue
```
If more conflicts exist, repeat steps 3-5.
#### Step 6: Push Rebased Patch
```bash
git push origin HEAD:refs/for/main
```
### Conflict Resolution Tips
**Understand Context**:
- Review their changes: `git show HEAD:path/to/file.php`
- Review your changes: `git show feature/105737-fix-indexed-search:path/to/file.php`
- Check file history: `git log -- path/to/file.php`
**Test After Resolution**:
```bash
# Run tests locally
composer test:unit
composer test:functional
# Check syntax
php -l path/to/file.php
```
**Ask for Help**:
- Post in #typo3-cms-coredev Slack
- Comment on Gerrit review
- Reference conflicting patch if known
### Aborting Rebase
If rebase goes wrong:
```bash
git rebase --abort
```
Returns to pre-rebase state. You can try again.
## Review Process
### Voting System
**Code Review** (CR):
- **+2**: Looks good, approved
- **+1**: Looks mostly good
- **0**: Neutral (default)
- **-1**: Needs improvement
- **-2**: Do not merge (veto)
**Verified** (V):
- **+1**: Tests passed
- **0**: Not yet tested
- **-1**: Tests failed
**Merge Requirements**:
- At least **+2 Code Review** from core team member
- At least **+1 Verified** (CI tests passed)
- No unresolved **-2** votes
- At least 2 reviewers involved (one must be core team)
### Typical Review Timeline
**Simple Bugfixes**: 1-3 days
**Medium Features**: 3-7 days
**Complex Features**: 1-2 weeks
**Breaking Changes**: 2-4 weeks (more scrutiny)
**Factors affecting timeline**:
- Code quality and completeness
- Test coverage
- Documentation
- Reviewer availability (volunteers!)
- Complexity and impact
### Responding to Review Comments
#### Step 1: Read Feedback Carefully
- Understand what's being requested
- Ask questions if unclear
- Check if feedback applies to multiple locations
#### Step 2: Implement Changes
```bash
# Make requested changes
vim path/to/file.php
# Stage and amend
git add path/to/file.php
git commit --amend
# Push update
git push origin HEAD:refs/for/main
```
#### Step 3: Respond on Gerrit
- Click **Reply** button
- Address each comment:
- "Done" - Simple confirmation
- "Fixed in PS3" - Reference patchset number
- Explain your approach if different from suggestion
- Thank reviewers
- Click **Send**
#### Example Response
```
Thanks for the review!
> Line 45: Consider using dependency injection
Good point! I've refactored to use DI in PS3.
> Line 120: Add type hint
Added in PS3. Also added return type hints throughout.
> Missing tests for edge case
Added test case for empty string input in Tests/Unit/IndexerTest.php
```
### Getting More Reviews
**If no reviews after 3-4 days**:
1. **Advertise on Slack** (#typo3-cms-coredev):
```
I've submitted a patch for #105737 (indexed search crash).
Would appreciate reviews when you have time: https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345
```
2. **Check patch quality**:
- Tests passing?
- Documentation complete?
- Follows coding standards?
- Clear commit message?
3. **Ask specific reviewers** (if appropriate):
- Maintainers of affected area
- Previous contributors to same files
- Don't spam or DM randomly!
## Gerrit Interface Guide
### Review Page Sections
**Header**:
- Status (Active, Merged, Abandoned)
- Subject and description
- Owner and reviewers
- CI test results
**Files**:
- List of changed files
- Click file to see diff
- Add inline comments
**History**:
- Patchset versions
- Comments and votes
- CI results per patchset
**Related Changes**:
- Depends on / Needed by
- Related topics
- Conflicts with
### Useful Gerrit Features
**Diff Views**:
- **Side-by-side**: Compare old/new code
- **Unified**: Traditional diff format
- **Between patchsets**: Compare PS1 vs PS2
**Search**:
- Find your changes: `owner:self status:open`
- Find by issue: `bug:105737`
- Find by topic: `topic:indexed-search`
**Keyboard Shortcuts**:
- `?`: Show all shortcuts
- `u`: Go up to dashboard
- `a`: Expand all inline comments
- `c`: Compose review comment
- `n/p`: Next/previous file
## Advanced Topics
### Cherry-Picking Patches
Apply someone else's patch locally:
```bash
# From Gerrit download dropdown, copy cherry-pick command
git fetch origin refs/changes/45/12345/3 && git cherry-pick FETCH_HEAD
```
### Topics
Group related changes:
```bash
git push origin HEAD:refs/for/main%topic=indexed-search-improvements
```
### Work In Progress (WIP)
Mark patch as work-in-progress:
```bash
git push origin HEAD:refs/for/main%wip
```
Or on Gerrit web UI: **More** → **Mark as Work In Progress**
**Use WIP when**:
- Patch incomplete, not ready for review
- Want CI test results before review
- Demonstrating proof of concept
**Remove WIP**: **More** → **Start Review**
### Private Changes
Keep change private (visible only to you and explicit reviewers):
```bash
git push origin HEAD:refs/for/main%private
```
### Draft Comments
Save review comments without publishing:
1. Add comments on files
2. Click **Save** instead of **Send**
3. Edit later
4. Publish when ready
## Troubleshooting
### "Change-Id not found"
**Problem**: Missing or modified Change-Id
**Solution**:
```bash
# Ensure commit-msg hook installed
ls -la .git/hooks/commit-msg
# If missing, install
composer gerrit:setup
# Amend commit to generate Change-Id
git commit --amend --no-edit
```
### "Prohibited by Gerrit"
**Problem**: Pushing to wrong branch or permissions issue
**Solution**:
```bash
# Verify push URL
git config remote.origin.pushurl
# Should be: ssh://<username>@review.typo3.org:29418/Packages/TYPO3.CMS.git
# Push to refs/for/main, not main directly
git push origin HEAD:refs/for/main
```
### "No New Changes"
**Problem**: Pushing identical commit
**Solution**:
- Make actual code changes
- Or amend commit message
- Then push again
### Multiple Commits on Branch
**Problem**: Accidentally created multiple commits
**Solution**: Squash into one commit
```bash
# Interactive rebase
git rebase -i origin/main
# In editor, change all but first "pick" to "squash"
# Save and exit
# Edit combined commit message
# Push
```
## Best Practices
1. **One commit per patch**: Squash multiple commits into one
2. **Rebase regularly**: Stay up-to-date with main branch
3. **Preserve Change-Id**: Never modify when amending
4. **Respond promptly**: Reply to reviews within 2-3 days
5. **Test locally first**: Run tests before pushing
6. **Clear communication**: Explain changes in Gerrit comments
7. **Be patient**: Reviewers are volunteers
8. **Learn from feedback**: Apply lessons to future patches
## Resources
- **Gerrit**: https://review.typo3.org
- **Forger Search**: https://forger.typo3.com
- **Gerrit Documentation**: https://review.typo3.org/Documentation/
- **Slack**: #typo3-cms-coredev
## Quick Command Reference
| Action | Command |
|--------|---------|
| Push new patch | `git push origin HEAD:refs/for/main` |
| Update patch | `git commit --amend && git push origin HEAD:refs/for/main` |
| Rebase on main | `git fetch origin && git rebase origin/main` |
| Abort rebase | `git rebase --abort` |
| Continue rebase | `git rebase --continue` |
| Cherry-pick | `git fetch origin refs/changes/XX/XXXX/X && git cherry-pick FETCH_HEAD` |
| Push as WIP | `git push origin HEAD:refs/for/main%wip` |
| Test SSH | `ssh -p 29418 <user>@review.typo3.org` |

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,186 @@
#!/usr/bin/env python3
"""
TYPO3 Core Contribution Commit Message Generator
Creates properly formatted commit messages following TYPO3 standards
"""
import argparse
import sys
import re
from typing import Optional
COMMIT_TYPES = {
'BUGFIX': 'Bug fixes',
'FEATURE': 'New features (main branch only)',
'TASK': 'Refactoring, cleanup, miscellaneous',
'DOCS': 'Documentation changes',
'SECURITY': 'Security vulnerability fixes'
}
BREAKING_CHANGE_PREFIX = '[!!!]'
def validate_subject(subject: str, has_breaking: bool) -> tuple[bool, Optional[str]]:
"""Validate subject line against TYPO3 rules"""
max_length = 52 if not has_breaking else 47 # Account for [!!!] prefix
if len(subject) > 72:
return False, "Subject line exceeds 72 characters (absolute limit)"
if len(subject) > max_length:
return False, f"Subject line exceeds {max_length} characters (recommended limit)"
if not subject[0].isupper():
return False, "Subject must start with uppercase letter"
if subject.endswith('.'):
return False, "Subject should not end with a period"
# Check for imperative mood (simple heuristic)
past_tense_endings = ['ed', 'ing']
first_word = subject.split()[0].lower()
if any(first_word.endswith(end) for end in past_tense_endings):
return False, f"Use imperative mood ('{first_word}' appears to be past/present continuous tense)"
return True, None
def wrap_text(text: str, width: int = 72) -> str:
"""Wrap text at specified width"""
words = text.split()
lines = []
current_line = []
current_length = 0
for word in words:
word_length = len(word)
if current_length + word_length + len(current_line) > width:
if current_line:
lines.append(' '.join(current_line))
current_line = [word]
current_length = word_length
else:
current_line.append(word)
current_length += word_length
if current_line:
lines.append(' '.join(current_line))
return '\n'.join(lines)
def parse_releases(releases_str: str) -> list[str]:
"""Parse comma-separated release versions"""
releases = [r.strip() for r in releases_str.split(',')]
# Validate format
valid_releases = []
for release in releases:
if release == 'main' or re.match(r'^\d+\.\d+$', release):
valid_releases.append(release)
else:
print(f"Warning: Invalid release format '{release}', skipping")
return valid_releases
def main():
parser = argparse.ArgumentParser(
description='Generate TYPO3-compliant commit messages',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
Examples:
%(prog)s --issue 105737 --type BUGFIX
%(prog)s --issue 105737 --type FEATURE --breaking
%(prog)s --type TASK --related 12345,12346
'''
)
parser.add_argument('--issue', type=int, help='Forge issue number')
parser.add_argument('--related', help='Related issue numbers (comma-separated)')
parser.add_argument('--type', choices=COMMIT_TYPES.keys(), required=True,
help='Commit type')
parser.add_argument('--breaking', action='store_true',
help='Mark as breaking change (adds [!!!] prefix)')
parser.add_argument('--releases', default='main',
help='Target releases (comma-separated, e.g., "main, 13.4, 12.4")')
parser.add_argument('--output', help='Output file (default: print to stdout)')
args = parser.parse_args()
# Interactive mode
print("=== TYPO3 Commit Message Generator ===\n")
# Get subject line
print(f"Commit Type: [{args.type}]")
if args.breaking:
print(f"Breaking Change: Yes (will add {BREAKING_CHANGE_PREFIX} prefix)")
print()
subject = input("Enter subject line (max 52 chars, imperative mood): ").strip()
# Validate subject
valid, error = validate_subject(subject, args.breaking)
if not valid:
print(f"\n❌ Error: {error}")
sys.exit(1)
# Get description
print("\nEnter description (explain how and why, not what).")
print("Press Ctrl+D (Linux/Mac) or Ctrl+Z (Windows) when done:")
description_lines = []
try:
while True:
line = input()
description_lines.append(line)
except EOFError:
pass
description = '\n'.join(description_lines).strip()
if description:
description = wrap_text(description)
# Build commit message
type_prefix = f"{BREAKING_CHANGE_PREFIX}{args.type}" if args.breaking else args.type
message = f"[{type_prefix}] {subject}\n\n"
if description:
message += f"{description}\n\n"
# Add footer
if args.issue:
message += f"Resolves: #{args.issue}\n"
if args.related:
related_issues = [f"#{num.strip()}" for num in args.related.split(',')]
for issue in related_issues:
message += f"Related: {issue}\n"
releases = parse_releases(args.releases)
if releases:
message += f"Releases: {', '.join(releases)}\n"
# Output
print("\n" + "="*60)
print("Generated Commit Message:")
print("="*60)
print(message)
print("="*60)
print("\nNote: Change-Id will be added automatically by git hook")
print("="*60)
if args.output:
with open(args.output, 'w') as f:
f.write(message)
print(f"\n✓ Commit message saved to: {args.output}")
print(f" Use: git commit -F {args.output}")
else:
print("\nTo use this message:")
print(" 1. Copy the message above")
print(" 2. Run: git commit")
print(" 3. Paste into your editor")
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,192 @@
#!/bin/bash
# Create TYPO3 Forge issue via Redmine REST API
#
# Usage:
# 1. Get your API key from https://forge.typo3.org/my/account
# 2. Set environment variable: export FORGE_API_KEY="your-key-here"
# 3. Run: ./scripts/create-forge-issue.sh
set -e
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Check for API key
if [ -z "$FORGE_API_KEY" ]; then
echo -e "${RED}Error: FORGE_API_KEY environment variable not set${NC}"
echo ""
echo "Get your API key from: https://forge.typo3.org/my/account"
echo "Then set it: export FORGE_API_KEY=\"your-key-here\""
exit 1
fi
# Check for required tools
for tool in curl jq; do
if ! command -v $tool &> /dev/null; then
echo -e "${RED}Error: $tool is required but not installed${NC}"
exit 1
fi
done
echo -e "${GREEN}TYPO3 Forge Issue Creator${NC}"
echo ""
# Interactive prompts
read -p "Issue subject (title): " SUBJECT
if [ -z "$SUBJECT" ]; then
echo -e "${RED}Error: Subject is required${NC}"
exit 1
fi
echo ""
echo "Issue description (multi-line, press Ctrl+D when done):"
DESCRIPTION=$(cat)
if [ -z "$DESCRIPTION" ]; then
echo -e "${RED}Error: Description is required${NC}"
exit 1
fi
echo ""
echo "Select tracker type:"
echo " 1) Bug"
echo " 2) Feature"
echo " 3) Task"
read -p "Choice [1]: " TRACKER_CHOICE
TRACKER_CHOICE=${TRACKER_CHOICE:-1}
case $TRACKER_CHOICE in
1) TRACKER_ID=1; TRACKER_NAME="Bug" ;;
2) TRACKER_ID=2; TRACKER_NAME="Feature" ;;
3) TRACKER_ID=4; TRACKER_NAME="Task" ;;
*) echo -e "${RED}Invalid choice${NC}"; exit 1 ;;
esac
echo ""
echo "Select priority:"
echo " 1) Must have"
echo " 2) Should have (recommended)"
echo " 3) Could have"
read -p "Choice [2]: " PRIORITY_CHOICE
PRIORITY_CHOICE=${PRIORITY_CHOICE:-2}
case $PRIORITY_CHOICE in
1) PRIORITY_ID=3; PRIORITY_NAME="Must have" ;;
2) PRIORITY_ID=4; PRIORITY_NAME="Should have" ;;
3) PRIORITY_ID=5; PRIORITY_NAME="Could have" ;;
*) echo -e "${RED}Invalid choice${NC}"; exit 1 ;;
esac
echo ""
read -p "TYPO3 version affected (e.g., 13, 14) [13]: " TYPO3_VERSION
TYPO3_VERSION=${TYPO3_VERSION:-13}
echo ""
echo "Select category (common ones, or enter ID manually):"
echo " 1) Miscellaneous (975)"
echo " 2) Backend API (971)"
echo " 3) Backend User Interface (972)"
echo " 4) Frontend (977)"
echo " 5) Database API (974)"
echo " 6) Indexed Search (1000)"
echo " 7) Extension Manager (976)"
echo " 8) Documentation (1004)"
echo " 9) Enter category ID manually"
read -p "Choice [1]: " CATEGORY_CHOICE
CATEGORY_CHOICE=${CATEGORY_CHOICE:-1}
case $CATEGORY_CHOICE in
1) CATEGORY_ID=975; CATEGORY_NAME="Miscellaneous" ;;
2) CATEGORY_ID=971; CATEGORY_NAME="Backend API" ;;
3) CATEGORY_ID=972; CATEGORY_NAME="Backend User Interface" ;;
4) CATEGORY_ID=977; CATEGORY_NAME="Frontend" ;;
5) CATEGORY_ID=974; CATEGORY_NAME="Database API" ;;
6) CATEGORY_ID=1000; CATEGORY_NAME="Indexed Search" ;;
7) CATEGORY_ID=976; CATEGORY_NAME="Extension Manager" ;;
8) CATEGORY_ID=1004; CATEGORY_NAME="Documentation" ;;
9)
read -p "Enter category ID: " CATEGORY_ID
CATEGORY_NAME="Custom ($CATEGORY_ID)"
;;
*) echo -e "${RED}Invalid choice${NC}"; exit 1 ;;
esac
# Optional tags
echo ""
read -p "Tags (comma-separated, optional): " TAGS
# Summary
echo ""
echo -e "${YELLOW}Summary:${NC}"
echo " Tracker: $TRACKER_NAME"
echo " Subject: $SUBJECT"
echo " Priority: $PRIORITY_NAME"
echo " Category: $CATEGORY_NAME"
echo " TYPO3 Version: $TYPO3_VERSION"
[ -n "$TAGS" ] && echo " Tags: $TAGS"
echo ""
read -p "Create this issue? [Y/n]: " CONFIRM
CONFIRM=${CONFIRM:-Y}
if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then
echo "Cancelled."
exit 0
fi
# Build JSON payload
JSON_PAYLOAD=$(jq -n \
--arg subject "$SUBJECT" \
--arg description "$DESCRIPTION" \
--argjson tracker "$TRACKER_ID" \
--argjson category "$CATEGORY_ID" \
--argjson priority "$PRIORITY_ID" \
--arg typo3_version "$TYPO3_VERSION" \
--arg tags "$TAGS" \
'{
issue: {
project_id: "typo3cms-core",
subject: $subject,
description: $description,
tracker_id: $tracker,
category_id: $category,
priority_id: $priority,
custom_fields: [
{id: 4, value: $typo3_version}
] + (if $tags != "" then [{id: 3, value: $tags}] else [] end)
}
}')
# Create issue
echo ""
echo -e "${YELLOW}Creating issue...${NC}"
RESPONSE=$(curl -s -X POST \
-H "Content-Type: application/json" \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
-d "$JSON_PAYLOAD" \
https://forge.typo3.org/issues.json)
# Check for errors
if echo "$RESPONSE" | jq -e '.errors' > /dev/null 2>&1; then
echo -e "${RED}Error creating issue:${NC}"
echo "$RESPONSE" | jq -r '.errors[]'
exit 1
fi
# Extract issue details
ISSUE_ID=$(echo "$RESPONSE" | jq -r '.issue.id')
ISSUE_URL="https://forge.typo3.org/issues/${ISSUE_ID}"
echo ""
echo -e "${GREEN}Success! Issue created:${NC}"
echo ""
echo " Issue #: $ISSUE_ID"
echo " URL: $ISSUE_URL"
echo ""
echo -e "${YELLOW}Next steps:${NC}"
echo " 1. Use in commit message: ${GREEN}Resolves: #${ISSUE_ID}${NC}"
echo " 2. Create feature branch: ${GREEN}git checkout -b feature/${ISSUE_ID}-description${NC}"
echo ""

View File

@@ -0,0 +1,102 @@
#!/bin/bash
# Query TYPO3 Forge project metadata via Redmine REST API
#
# Usage:
# 1. Get your API key from https://forge.typo3.org/my/account
# 2. Set environment variable: export FORGE_API_KEY="your-key-here"
# 3. Run: ./scripts/query-forge-metadata.sh [categories|trackers|all]
set -e
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Check for API key
if [ -z "$FORGE_API_KEY" ]; then
echo -e "${RED}Error: FORGE_API_KEY environment variable not set${NC}"
echo ""
echo "Get your API key from: https://forge.typo3.org/my/account"
echo "Then set it: export FORGE_API_KEY=\"your-key-here\""
exit 1
fi
# Check for required tools
for tool in curl jq; do
if ! command -v $tool &> /dev/null; then
echo -e "${RED}Error: $tool is required but not installed${NC}"
exit 1
fi
done
# Parse arguments
QUERY_TYPE=${1:-all}
# Fetch project metadata
echo -e "${YELLOW}Fetching TYPO3 Core project metadata...${NC}"
echo ""
RESPONSE=$(curl -s \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
https://forge.typo3.org/projects/typo3cms-core.json)
# Check for errors
if echo "$RESPONSE" | jq -e '.errors' > /dev/null 2>&1; then
echo -e "${RED}Error querying Forge:${NC}"
echo "$RESPONSE" | jq -r '.errors[]'
exit 1
fi
# Display trackers
if [[ "$QUERY_TYPE" == "trackers" || "$QUERY_TYPE" == "all" ]]; then
echo -e "${GREEN}=== Trackers ===${NC}"
echo ""
echo "$RESPONSE" | jq -r '.project.trackers[] | "\(.id)\t\(.name)"' | \
awk -F'\t' '{printf " %-4s %s\n", $1, $2}'
echo ""
fi
# Display categories
if [[ "$QUERY_TYPE" == "categories" || "$QUERY_TYPE" == "all" ]]; then
echo -e "${GREEN}=== Issue Categories ===${NC}"
echo ""
echo "$RESPONSE" | jq -r '.project.issue_categories[] | "\(.id)\t\(.name)"' | \
awk -F'\t' '{printf " %-6s %s\n", $1, $2}'
echo ""
fi
# Display usage examples
if [[ "$QUERY_TYPE" == "all" ]]; then
echo -e "${BLUE}=== Usage Examples ===${NC}"
echo ""
echo "Create bug in Backend API category:"
echo ' curl -X POST \'
echo ' -H "Content-Type: application/json" \'
echo ' -H "X-Redmine-API-Key: $FORGE_API_KEY" \'
echo ' -d '"'"'{'
echo ' "issue": {'
echo ' "project_id": "typo3cms-core",'
echo ' "subject": "Issue title",'
echo ' "description": "Description",'
echo ' "tracker_id": 1,'
echo ' "category_id": 971,'
echo ' "priority_id": 4,'
echo ' "custom_fields": [{"id": 4, "value": "13"}]'
echo ' }'
echo ' }'"'"' \'
echo ' https://forge.typo3.org/issues.json'
echo ""
echo "Or use the interactive script:"
echo " ./scripts/create-forge-issue.sh"
echo ""
fi
# Save to file if requested
if [[ "$2" == "--save" ]]; then
OUTPUT_FILE="forge-metadata-$(date +%Y%m%d).json"
echo "$RESPONSE" > "$OUTPUT_FILE"
echo -e "${GREEN}Metadata saved to: $OUTPUT_FILE${NC}"
fi

View File

@@ -0,0 +1,433 @@
#!/bin/bash
# TYPO3 Core Development Environment Setup Script
# Based on proven production workflow
# Creates complete DDEV-based TYPO3 Core development environment
set -e
# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Print functions
print_header() {
echo -e "\n${BLUE}===================================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}===================================================${NC}\n"
}
print_step() {
echo -e "${GREEN}${NC} $1"
}
print_info() {
echo -e "${YELLOW}${NC} $1"
}
print_error() {
echo -e "${RED}${NC} $1"
}
print_success() {
echo -e "${GREEN}${NC} $1"
}
# Check prerequisites
check_prerequisites() {
print_header "Checking Prerequisites"
local missing_prereqs=false
# Check Git
if command -v git &> /dev/null; then
print_success "Git: $(git --version)"
else
print_error "Git not found. Please install Git first."
missing_prereqs=true
fi
# Check DDEV
if command -v ddev &> /dev/null; then
print_success "DDEV: $(ddev version | head -n1)"
else
print_error "DDEV not found. Please install DDEV first: https://ddev.readthedocs.io/"
missing_prereqs=true
fi
# Check Docker
if command -v docker &> /dev/null; then
if docker ps &> /dev/null; then
print_success "Docker: Running"
else
print_error "Docker not running. Please start Docker."
missing_prereqs=true
fi
else
print_error "Docker not found. DDEV requires Docker."
missing_prereqs=true
fi
if [ "$missing_prereqs" = true ]; then
print_error "Missing prerequisites. Please install required tools and try again."
exit 1
fi
}
# Gather user input
gather_input() {
print_header "Configuration"
# Project name
read -p "Project name (e.g., t3coredev-14-php8-4): " PROJECT_NAME
if [ -z "$PROJECT_NAME" ]; then
PROJECT_NAME="t3coredev-14-php8-4"
print_info "Using default: $PROJECT_NAME"
fi
# Git user name
read -p "Your name for Git commits: " GIT_NAME
while [ -z "$GIT_NAME" ]; do
print_error "Name is required"
read -p "Your name for Git commits: " GIT_NAME
done
# Git email
read -p "Your email for Git commits: " GIT_EMAIL
while [ -z "$GIT_EMAIL" ]; do
print_error "Email is required"
read -p "Your email for Git commits: " GIT_EMAIL
done
# Gerrit username
read -p "Your Gerrit username (review.typo3.org): " GERRIT_USER
while [ -z "$GERRIT_USER" ]; do
print_error "Gerrit username is required"
read -p "Your Gerrit username: " GERRIT_USER
done
# PHP version
read -p "PHP version (8.2, 8.3, 8.4) [default: 8.4]: " PHP_VERSION
if [ -z "$PHP_VERSION" ]; then
PHP_VERSION="8.4"
fi
# Timezone
read -p "Timezone [default: Europe/Vienna]: " TIMEZONE
if [ -z "$TIMEZONE" ]; then
TIMEZONE="Europe/Vienna"
fi
# Admin password
read -sp "TYPO3 admin password: " ADMIN_PASSWORD
echo
while [ -z "$ADMIN_PASSWORD" ]; do
print_error "Admin password is required"
read -sp "TYPO3 admin password: " ADMIN_PASSWORD
echo
done
# Confirm
echo -e "\n${YELLOW}Configuration Summary:${NC}"
echo " Project: $PROJECT_NAME"
echo " Git Name: $GIT_NAME"
echo " Git Email: $GIT_EMAIL"
echo " Gerrit User: $GERRIT_USER"
echo " PHP Version: $PHP_VERSION"
echo " Timezone: $TIMEZONE"
echo
read -p "Proceed with setup? (y/n): " CONFIRM
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
print_info "Setup cancelled."
exit 0
fi
}
# Create project directory
create_project_dir() {
print_header "Creating Project Directory"
if [ -d "$PROJECT_NAME" ]; then
print_error "Directory $PROJECT_NAME already exists!"
read -p "Delete and recreate? (y/n): " DELETE_CONFIRM
if [[ "$DELETE_CONFIRM" =~ ^[Yy]$ ]]; then
rm -rf "$PROJECT_NAME"
print_success "Deleted existing directory"
else
print_error "Cannot proceed with existing directory"
exit 1
fi
fi
mkdir -p "$PROJECT_NAME"
cd "$PROJECT_NAME"
print_success "Created and entered directory: $PROJECT_NAME"
}
# Clone TYPO3 repository
clone_repository() {
print_header "Cloning TYPO3 Repository"
print_step "Cloning from GitHub..."
if git clone git@github.com:typo3/typo3 . 2>&1 | grep -q "Permission denied\|Could not"; then
print_error "Failed to clone via SSH. Trying HTTPS..."
rm -rf .git
if ! git clone https://github.com/typo3/typo3.git . ; then
print_error "Failed to clone repository"
exit 1
fi
fi
print_success "Repository cloned successfully"
}
# Configure Git
configure_git() {
print_header "Configuring Git"
print_step "Setting user identity..."
git config user.name "$GIT_NAME"
git config user.email "$GIT_EMAIL"
print_success "User identity configured"
print_step "Enabling automatic rebase..."
git config branch.autosetuprebase remote
print_success "Automatic rebase enabled"
print_step "Installing git hooks..."
if [ -f "Build/git-hooks/commit-msg" ]; then
cp Build/git-hooks/commit-msg .git/hooks/commit-msg
chmod +x .git/hooks/commit-msg
print_success "Commit-msg hook installed"
else
print_error "Commit-msg hook not found in Build/git-hooks/"
fi
print_step "Configuring Gerrit remote..."
git config remote.origin.pushurl "ssh://${GERRIT_USER}@review.typo3.org:29418/Packages/TYPO3.CMS.git"
git config remote.origin.push "+refs/heads/main:refs/for/main"
print_success "Gerrit remote configured"
# Test Gerrit connection
print_step "Testing Gerrit SSH connection..."
if timeout 5 ssh -p 29418 -o StrictHostKeyChecking=no -o BatchMode=yes "${GERRIT_USER}@review.typo3.org" gerrit version &>/dev/null; then
print_success "Gerrit connection successful"
else
print_error "Cannot connect to Gerrit. Please verify your SSH keys are configured."
print_info "Continue anyway? SSH key might need configuration."
read -p "Continue? (y/n): " CONTINUE
if [[ ! "$CONTINUE" =~ ^[Yy]$ ]]; then
exit 1
fi
fi
}
# Configure DDEV
configure_ddev() {
print_header "Configuring DDEV"
print_step "Setting project type..."
ddev config --project-type typo3 -y
print_step "Configuring timezone..."
ddev config --timezone "$TIMEZONE"
print_step "Setting PHP version..."
ddev config --php-version="$PHP_VERSION"
print_step "Configuring webserver..."
ddev config --webserver-type=apache-fpm
print_step "Setting database version..."
ddev config --database=mariadb:10.6
print_step "Adding environment variables..."
ddev config --web-environment-add="TYPO3_CONTEXT=Development/Ddev"
ddev config --web-environment-add="COMPOSER_ROOT_VERSION=14.0.x-dev"
print_success "DDEV configured successfully"
}
# Start DDEV
start_ddev() {
print_header "Starting DDEV"
print_step "Starting containers..."
if ddev start; then
print_success "DDEV started successfully"
ddev describe
else
print_error "Failed to start DDEV"
exit 1
fi
}
# Install dependencies
install_dependencies() {
print_header "Installing Dependencies"
print_step "Running Composer install via runTests.sh..."
if ./Build/Scripts/runTests.sh -s composerInstall; then
print_success "Dependencies installed"
else
print_error "Failed to install dependencies"
print_info "Trying alternative method..."
if ddev composer install; then
print_success "Dependencies installed via ddev composer"
else
print_error "Failed to install dependencies"
exit 1
fi
fi
}
# Setup TYPO3
setup_typo3() {
print_header "Setting Up TYPO3"
print_step "Creating installation trigger files..."
ddev exec 'touch /var/www/html/FIRST_INSTALL'
ddev exec 'touch /var/www/html/typo3conf/ENABLE_INSTALL_TOOL'
ddev exec 'echo "KEEP_FILE" > /var/www/html/typo3conf/ENABLE_INSTALL_TOOL'
print_success "Trigger files created"
print_step "Running TYPO3 setup..."
if ddev typo3 setup \
--driver=mysqli \
--host=db \
--port=3306 \
--dbname=db \
--username=db \
--password=db \
--admin-username=backenduser \
--admin-user-password="$ADMIN_PASSWORD" \
--admin-email="$GIT_EMAIL" \
--project-name="TYPO3 Core Dev v14 PHP ${PHP_VERSION}" \
--no-interaction \
--server-type=apache \
--force; then
print_success "TYPO3 setup completed"
else
print_error "TYPO3 setup failed"
exit 1
fi
}
# Activate extensions
activate_extensions() {
print_header "Activating Extensions"
print_step "Setting up extensions..."
ddev typo3 extension:setup
print_step "Activating indexed_search..."
ddev typo3 extension:activate indexed_search
print_step "Activating styleguide..."
ddev typo3 extension:activate styleguide
print_step "Activating scheduler..."
ddev typo3 extension:activate scheduler
print_success "Extensions activated"
}
# Setup backend groups
setup_backend_groups() {
print_header "Setting Up Backend User Groups"
print_step "Creating default backend groups..."
if ddev typo3 setup:begroups:default --groups=Both; then
print_success "Backend groups configured"
else
print_error "Failed to setup backend groups"
fi
}
# Generate test data
generate_test_data() {
print_header "Generating Test Data"
read -p "Generate styleguide test data? (y/n): " GENERATE_DATA
if [[ "$GENERATE_DATA" =~ ^[Yy]$ ]]; then
print_step "Generating TCA examples..."
ddev typo3 styleguide:generate --create -- tca
print_step "Generating frontend system template..."
ddev typo3 styleguide:generate --create -- frontend-systemplate
print_success "Test data generated"
else
print_info "Skipping test data generation"
fi
}
# Final steps
finalize() {
print_header "Setup Complete!"
print_success "TYPO3 Core development environment is ready!"
echo
echo -e "${GREEN}Project Details:${NC}"
echo " Name: $PROJECT_NAME"
echo " URL: https://${PROJECT_NAME}.ddev.site"
echo " Backend: https://${PROJECT_NAME}.ddev.site/typo3"
echo " Admin User: backenduser"
echo " Admin Password: [the password you entered]"
echo
echo -e "${GREEN}Next Steps:${NC}"
echo " 1. Open backend: ddev launch /typo3"
echo " 2. Run tests: ./Build/Scripts/runTests.sh -s unit"
echo " 3. Create branch: git checkout -b feature/your-feature"
echo " 4. Make changes and commit with proper message"
echo " 5. Push to Gerrit: git push origin HEAD:refs/for/main"
echo
echo -e "${GREEN}Useful Commands:${NC}"
echo " ddev start - Start project"
echo " ddev stop - Stop project"
echo " ddev restart - Restart project"
echo " ddev ssh - SSH into container"
echo " ddev typo3 cache:flush - Clear TYPO3 caches"
echo " ddev logs -f - Follow logs"
echo
read -p "Open TYPO3 backend now? (y/n): " OPEN_BACKEND
if [[ "$OPEN_BACKEND" =~ ^[Yy]$ ]]; then
ddev launch /typo3
fi
}
# Main execution
main() {
clear
print_header "TYPO3 Core Development Setup"
echo "This script will set up a complete TYPO3 Core development environment."
echo "It will:"
echo " - Clone TYPO3 Core repository"
echo " - Configure Git for Gerrit submissions"
echo " - Set up DDEV with optimal settings"
echo " - Install TYPO3 with test data"
echo " - Activate development extensions"
echo
check_prerequisites
gather_input
create_project_dir
clone_repository
configure_git
configure_ddev
start_ddev
install_dependencies
setup_typo3
activate_extensions
setup_backend_groups
generate_test_data
finalize
}
# Run main function
main

View File

@@ -0,0 +1,256 @@
#!/usr/bin/env python3
"""
TYPO3 Commit Message Validator
Validates commit messages against TYPO3 contribution standards
"""
import sys
import re
import argparse
from typing import List, Tuple
VALID_TYPES = ['BUGFIX', 'FEATURE', 'TASK', 'DOCS', 'SECURITY']
BREAKING_PREFIX = '[!!!]'
class CommitMessageValidator:
def __init__(self, message: str):
self.message = message
self.lines = message.split('\n')
self.errors = []
self.warnings = []
def validate(self) -> Tuple[bool, List[str], List[str]]:
"""Run all validation checks"""
self.check_subject_line()
self.check_blank_line()
self.check_footer()
self.check_change_id()
return len(self.errors) == 0, self.errors, self.warnings
def check_subject_line(self):
"""Validate the subject line"""
if not self.lines:
self.errors.append("Commit message is empty")
return
subject = self.lines[0]
# Check for commit type
type_pattern = r'^\[(?:\[!!!\])?(BUGFIX|FEATURE|TASK|DOCS|SECURITY)\]'
match = re.match(type_pattern, subject)
if not match:
self.errors.append(
f"Subject must start with commit type: {', '.join(f'[{t}]' for t in VALID_TYPES)}"
)
return
commit_type = match.group(1)
# Check for breaking change prefix
if subject.startswith('[!!!]'):
if commit_type == 'BUGFIX':
self.warnings.append(
"Breaking changes are unusual for BUGFIX. Consider using FEATURE or TASK"
)
# Extract subject without type prefix
subject_without_type = re.sub(type_pattern, '', subject).strip()
# Check length
if len(subject) > 72:
self.errors.append(
f"Subject line is {len(subject)} characters (max 72). Current: {len(subject)}"
)
elif len(subject) > 52:
self.warnings.append(
f"Subject line is {len(subject)} characters (recommended max 52)"
)
# Check capitalization
if subject_without_type and not subject_without_type[0].isupper():
self.errors.append("Subject description must start with uppercase letter")
# Check for period at end
if subject.endswith('.'):
self.errors.append("Subject line should not end with a period")
# Check for imperative mood (heuristic)
if subject_without_type:
first_word = subject_without_type.split()[0].lower()
if first_word.endswith('ed') or first_word.endswith('ing'):
self.warnings.append(
f"Use imperative mood: '{first_word}' may not be imperative. "
"Use 'Fix' not 'Fixed' or 'Fixing'"
)
def check_blank_line(self):
"""Check for blank line after subject"""
if len(self.lines) < 2:
return # Only subject line, no body
if len(self.lines) >= 2 and self.lines[1] != '':
self.errors.append("Second line must be blank (separate subject from body)")
def check_footer(self):
"""Check footer tags"""
footer_pattern = r'^(Resolves|Related|Releases|Depends|Reverts):\s*'
has_resolves = False
has_releases = False
has_change_id = False
for i, line in enumerate(self.lines):
if re.match(footer_pattern, line):
# Check format: should have colon followed by space
if not re.match(r'^[A-Z][a-z]+:\s+', line):
self.errors.append(
f"Line {i+1}: Footer tag must have colon followed by space: '{line}'"
)
# Check specific tags
if line.startswith('Resolves:'):
has_resolves = True
# Validate issue number format
if not re.match(r'^Resolves:\s+#\d+', line):
self.errors.append(
f"Line {i+1}: Resolves must reference issue number: 'Resolves: #12345'"
)
elif line.startswith('Related:'):
if not re.match(r'^Related:\s+#\d+', line):
self.errors.append(
f"Line {i+1}: Related must reference issue number: 'Related: #12345'"
)
elif line.startswith('Releases:'):
has_releases = True
# Validate releases format
releases_value = line.split(':', 1)[1].strip()
releases = [r.strip() for r in releases_value.split(',')]
for release in releases:
if release != 'main' and not re.match(r'^\d+\.\d+$', release):
self.errors.append(
f"Line {i+1}: Invalid release format '{release}'. "
"Use 'main' or version like '13.4'"
)
elif line.startswith('Change-Id:'):
has_change_id = True
# Warnings for missing tags
if not has_resolves:
self.warnings.append(
"No 'Resolves: #<issue>' tag found. Required for features and tasks."
)
if not has_releases:
self.warnings.append(
"No 'Releases:' tag found. Required to specify target versions."
)
def check_change_id(self):
"""Check for Change-Id"""
change_id_pattern = r'^Change-Id:\s+I[a-f0-9]{40}$'
has_change_id = any(re.match(change_id_pattern, line) for line in self.lines)
if not has_change_id:
self.warnings.append(
"No Change-Id found. It will be added automatically by git commit-msg hook."
)
def check_line_length(self):
"""Check body line lengths"""
for i, line in enumerate(self.lines[2:], start=3): # Skip subject and blank line
if line.startswith(('Resolves:', 'Related:', 'Releases:', 'Change-Id:', 'Depends:', 'Reverts:')):
continue # Skip footer
if len(line) > 72:
# Allow URLs to be longer
if not re.search(r'https?://', line):
self.warnings.append(
f"Line {i}: Length {len(line)} exceeds 72 characters"
)
def main():
parser = argparse.ArgumentParser(
description='Validate TYPO3 commit messages',
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument('--file', '-f', help='File containing commit message')
parser.add_argument('--message', '-m', help='Commit message string')
parser.add_argument('--strict', action='store_true',
help='Treat warnings as errors')
args = parser.parse_args()
# Get message
if args.file:
try:
with open(args.file, 'r') as f:
message = f.read()
except FileNotFoundError:
print(f"Error: File not found: {args.file}")
return 1
elif args.message:
message = args.message
else:
# Read from last commit
try:
import subprocess
result = subprocess.run(
['git', 'log', '-1', '--pretty=%B'],
capture_output=True,
text=True,
check=True
)
message = result.stdout
except subprocess.CalledProcessError:
print("Error: Could not read last commit message")
print("Usage: Provide --file or --message, or run in a git repository")
return 1
# Validate
validator = CommitMessageValidator(message)
is_valid, errors, warnings = validator.validate()
# Print results
print("=" * 60)
print("TYPO3 Commit Message Validation")
print("=" * 60)
print()
if errors:
print("❌ ERRORS:")
for error in errors:
print(f"{error}")
print()
if warnings:
print("⚠️ WARNINGS:")
for warning in warnings:
print(f"{warning}")
print()
if not errors and not warnings:
print("✅ Commit message is valid!")
elif not errors:
print("✅ No errors found (warnings can be ignored)")
else:
print("❌ Validation failed. Please fix errors above.")
print("=" * 60)
# Exit code
if errors or (args.strict and warnings):
return 1
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,181 @@
#!/bin/bash
# TYPO3 Core Contribution Prerequisites Checker
# Verifies accounts, git configuration, and development environment setup
set -e
# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "================================================"
echo "TYPO3 Core Contribution Prerequisites Check"
echo "================================================"
echo
# Track overall status
ALL_CHECKS_PASSED=true
# Function to print status
print_status() {
if [ "$1" = "pass" ]; then
echo -e "${GREEN}${NC} $2"
elif [ "$1" = "fail" ]; then
echo -e "${RED}${NC} $2"
ALL_CHECKS_PASSED=false
elif [ "$1" = "warn" ]; then
echo -e "${YELLOW}${NC} $2"
fi
}
# 1. Check Git installation
echo "1. Checking Git installation..."
if command -v git &> /dev/null; then
GIT_VERSION=$(git --version)
print_status "pass" "Git installed: $GIT_VERSION"
else
print_status "fail" "Git not installed"
fi
echo
# 2. Check Git user configuration
echo "2. Checking Git user configuration..."
GIT_USER_NAME=$(git config --global user.name 2>/dev/null || echo "")
GIT_USER_EMAIL=$(git config --global user.email 2>/dev/null || echo "")
if [ -n "$GIT_USER_NAME" ] && [ -n "$GIT_USER_EMAIL" ]; then
print_status "pass" "Git user configured: $GIT_USER_NAME <$GIT_USER_EMAIL>"
else
print_status "fail" "Git user not configured. Run:"
echo " git config --global user.name \"Your Name\""
echo " git config --global user.email \"your-email@example.org\""
fi
echo
# 2a. Verify Git email matches Gerrit account
echo "2a. Verifying Git email against Gerrit..."
if [ -n "$GIT_USER_EMAIL" ]; then
echo " Your Git email: $GIT_USER_EMAIL"
print_status "warn" " IMPORTANT: Verify this email is registered at:"
echo " https://review.typo3.org/settings#EmailAddresses"
echo " Gerrit will reject pushes if email doesn't match!"
fi
echo
# 3. Check if in TYPO3 repository
echo "3. Checking TYPO3 repository..."
if [ -d ".git" ]; then
REPO_URL=$(git config --get remote.origin.url 2>/dev/null || echo "")
if [[ "$REPO_URL" == *"typo3"* ]]; then
print_status "pass" "In TYPO3 repository"
# Check TYPO3-specific git config
echo " Checking TYPO3-specific configuration..."
# Check auto-rebase
AUTO_REBASE=$(git config --get branch.autosetuprebase 2>/dev/null || echo "")
if [ "$AUTO_REBASE" = "remote" ]; then
print_status "pass" " Auto-rebase configured"
else
print_status "fail" " Auto-rebase not configured. Run: git config branch.autosetuprebase remote"
fi
# Check Gerrit push URL
PUSH_URL=$(git config --get remote.origin.pushurl 2>/dev/null || echo "")
if [[ "$PUSH_URL" == *"review.typo3.org"* ]]; then
print_status "pass" " Gerrit push URL configured"
else
print_status "fail" " Gerrit push URL not configured. Run:"
echo " git config remote.origin.pushurl ssh://<USERNAME>@review.typo3.org:29418/Packages/TYPO3.CMS.git"
fi
# Check push refspec
PUSH_REFSPEC=$(git config --get remote.origin.push 2>/dev/null || echo "")
if [[ "$PUSH_REFSPEC" == *"refs/for/main"* ]]; then
print_status "pass" " Push refspec configured for Gerrit"
else
print_status "fail" " Push refspec not configured. Run:"
echo " git config remote.origin.push +refs/heads/main:refs/for/main"
fi
else
print_status "warn" "In git repository but not TYPO3. URL: $REPO_URL"
fi
else
print_status "warn" "Not in a git repository (run from TYPO3 repo root)"
fi
echo
# 4. Check Git hooks
echo "4. Checking Git hooks..."
if [ -f ".git/hooks/commit-msg" ]; then
print_status "pass" "commit-msg hook installed"
else
print_status "fail" "commit-msg hook not installed. Run: composer gerrit:setup"
fi
if [ -f ".git/hooks/pre-commit" ]; then
print_status "pass" "pre-commit hook installed"
else
print_status "warn" "pre-commit hook not installed (optional but recommended)"
fi
echo
# 5. Check SSH connection to Gerrit
echo "5. Checking Gerrit SSH connection..."
if timeout 5 ssh -p 29418 -o StrictHostKeyChecking=no -o BatchMode=yes review.typo3.org gerrit version &>/dev/null; then
print_status "pass" "Gerrit SSH connection successful"
else
print_status "fail" "Cannot connect to Gerrit via SSH. Check your SSH keys and Gerrit setup"
fi
echo
# 6. Check Composer
echo "6. Checking Composer installation..."
if command -v composer &> /dev/null; then
COMPOSER_VERSION=$(composer --version 2>/dev/null | head -n1)
print_status "pass" "Composer installed: $COMPOSER_VERSION"
else
print_status "warn" "Composer not found (needed for running tests and gerrit:setup)"
fi
echo
# 7. Check PHP
echo "7. Checking PHP installation..."
if command -v php &> /dev/null; then
PHP_VERSION=$(php -v | head -n1)
print_status "pass" "PHP installed: $PHP_VERSION"
else
print_status "warn" "PHP not found (needed for development and testing)"
fi
echo
# 8. Check DDEV (optional)
echo "8. Checking DDEV installation (optional)..."
if command -v ddev &> /dev/null; then
DDEV_VERSION=$(ddev version | head -n1)
print_status "pass" "DDEV installed: $DDEV_VERSION"
else
print_status "warn" "DDEV not found (recommended for development environment)"
fi
echo
# Final Summary
echo "================================================"
if [ "$ALL_CHECKS_PASSED" = true ]; then
echo -e "${GREEN}✓ All critical checks passed!${NC}"
echo "You're ready to contribute to TYPO3 Core."
else
echo -e "${RED}✗ Some checks failed.${NC}"
echo "Please address the issues above before contributing."
fi
echo "================================================"
# Exit with appropriate code
if [ "$ALL_CHECKS_PASSED" = true ]; then
exit 0
else
exit 1
fi

19
skills/typo3-ddev/.gitignore vendored Normal file
View File

@@ -0,0 +1,19 @@
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# Temporary files
*.tmp
*.bak
skill-completion.txt

21
skills/typo3-ddev/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Netresearch DTT GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

427
skills/typo3-ddev/README.md Normal file
View File

@@ -0,0 +1,427 @@
# TYPO3 DDEV Skill
> A Claude Code skill for automating DDEV environment setup in TYPO3 extension projects
[![TYPO3](https://img.shields.io/badge/TYPO3-11%20%7C%2012%20%7C%2013-orange.svg)](https://typo3.org/)
[![DDEV](https://img.shields.io/badge/DDEV-Local%20Development-blue.svg)](https://ddev.com/)
[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
## Overview
This skill helps TYPO3 extension developers quickly set up a complete DDEV development environment with multiple TYPO3 versions. Instead of manually configuring DDEV, this skill automates the entire process - from detecting your extension metadata to generating all configuration files and installing TYPO3.
### What It Does
- ✅ Detects TYPO3 extension projects automatically
- ✅ Extracts extension metadata (key, package name, namespace)
- ✅ Generates complete DDEV configuration
- ✅ Creates multi-version TYPO3 testing environment (11.5, 12.4, 13.4 LTS)
- ✅ Provides backend and frontend access with preconfigured credentials
- ✅ Includes custom DDEV commands for easy TYPO3 installation
### Who It's For
- TYPO3 extension developers
- Teams working on TYPO3 extensions
- Developers needing to test extensions across multiple TYPO3 versions
- Anyone wanting a quick, reproducible TYPO3 development environment
## Prerequisites
Before using this skill, ensure you have:
- [DDEV](https://ddev.readthedocs.io/en/stable/users/install/ddev-installation/) installed
- [Docker](https://www.docker.com/get-started) running
- A TYPO3 extension project (with `ext_emconf.php` or `composer.json`)
- [Claude Code](https://claude.com/claude-code) installed
## Installation
### Option 1: Install as Claude Code Skill
```bash
# Clone this repository into your Claude Code skills directory
cd ~/.claude/skills/ # or your custom skills directory
git clone https://github.com/netresearch/typo3-ddev-skill.git
# The skill is now available in Claude Code
```
### Option 2: Manual Setup
1. Download this repository
2. Copy the `skill.md` file to your project or reference it when needed
3. Use the templates manually by copying from the `templates/` directory
## Usage
### In Claude Code
Once installed, simply invoke the skill in your TYPO3 extension project:
```
/typo3-ddev
```
Or ask Claude:
```
Set up DDEV for my TYPO3 extension
```
The skill will:
1. Validate prerequisites (DDEV, Docker, TYPO3 extension structure)
2. Extract your extension metadata
3. Confirm configuration with you
4. Generate all `.ddev/` files with proper values
5. Guide you through starting DDEV and installing TYPO3
### What You'll Get
After setup, you'll have:
```
project-root/
├── .ddev/
│ ├── config.yaml
│ ├── docker-compose.web.yaml
│ ├── apache/
│ │ └── apache-site.conf
│ ├── web-build/
│ │ └── Dockerfile
│ └── commands/
│ └── web/
│ ├── install-v11
│ ├── install-v12
│ ├── install-v13
│ └── install-all
├── Classes/
├── Configuration/
├── ext_emconf.php
└── composer.json
```
### Accessing Your Environment
Once installed, access TYPO3 at:
**Overview Dashboard:**
```
https://your-ext.ddev.site/
```
**TYPO3 11.5 LTS:**
- Frontend: `https://v11.your-ext.ddev.site/`
- Backend: `https://v11.your-ext.ddev.site/typo3/`
**TYPO3 12.4 LTS:**
- Frontend: `https://v12.your-ext.ddev.site/`
- Backend: `https://v12.your-ext.ddev.site/typo3/`
**TYPO3 13.4 LTS:**
- Frontend: `https://v13.your-ext.ddev.site/`
- Backend: `https://v13.your-ext.ddev.site/typo3/`
### Backend Credentials
```
Username: admin
Password: Password:joh316
```
## Custom DDEV Commands
The skill creates these custom commands:
```bash
# Install specific TYPO3 version
ddev install-v11 # TYPO3 11.5 LTS
ddev install-v12 # TYPO3 12.4 LTS
ddev install-v13 # TYPO3 13.4 LTS
# Install all versions at once
ddev install-all
# Install Introduction Package (demo content)
ddev install-introduction v13
```
## Adding Demo Content
Test your extension with realistic content using the TYPO3 Introduction Package:
```bash
# Install Introduction Package
ddev install-introduction v13
```
**Includes:**
- 86+ pages with example content
- 226+ content elements (text, images, forms, tables)
- Multi-language support (EN, DE, DA)
- Bootstrap Package responsive theme
- Perfect for testing RTE features
## Extension Auto-Configuration
For extensions needing additional setup (RTE config, TSconfig, TypoScript), create a custom configuration command:
```bash
# Copy template
cp .ddev/templates/commands/web/configure-extension.optional \
.ddev/commands/web/configure-myext
# Customize for your extension's needs
# Add: RTE YAML, Page TSConfig, TypoScript, site package setup
# Run after TYPO3 installation
ddev configure-myext v13
```
**Use Cases:**
- **RTE/CKEditor plugins** - Configure toolbar, import plugin YAML
- **Backend modules** - Set up TSconfig, permissions
- **Frontend plugins** - TypoScript configuration, example content
**Pattern Benefits:**
- One-command post-install setup
- Consistent team configuration
- Includes demo content automatically
- Reduces manual configuration errors
See `SKILL.md` for detailed examples and template structure.
## Troubleshooting
### Database Already Exists
If reinstalling TYPO3:
```bash
ddev mysql -e "DROP DATABASE IF EXISTS v13; CREATE DATABASE v13;"
ddev install-v13
```
### Services Not Starting
Check and restart services:
```bash
docker ps --filter "name=ddev-your-ext"
ddev restart
```
### Extension Not Visible
Flush caches:
```bash
ddev exec -d /var/www/html/v13 vendor/bin/typo3 cache:flush
```
## Architecture
The setup creates a unique multi-version environment:
- **Extension Source**: Mounted at `/var/www/{{EXTENSION_KEY}}` (your project root)
- **TYPO3 Installations**: Separate directories for each version (`/var/www/html/v11`, `v12`, `v13`)
- **Extension Installation**: Installed via Composer path repository in each TYPO3 version
- **Persistent Data**: Docker volumes for each TYPO3 version database and files
This architecture allows you to:
- Develop extension code in your project root
- Test immediately across all TYPO3 versions
- Keep TYPO3 installations separate and clean
- Avoid committing TYPO3 core files to your extension repository
## Configuration
### Supported TYPO3 Versions
By default, the skill supports:
- TYPO3 11.5 LTS (PHP 8.0+)
- TYPO3 12.4 LTS (PHP 8.1+)
- TYPO3 13.4 LTS (PHP 8.2+)
PHP version is set to 8.2 for maximum compatibility.
### Customization
After generation, you can customize:
**PHP Version** (`.ddev/config.yaml`):
```yaml
php_version: "8.3" # Change to 8.1 or 8.3 if needed
```
**XDebug** (enable/disable):
```bash
ddev xdebug on # Enable
ddev xdebug off # Disable
```
**Database** (Tiered Selection: SQLite/MariaDB/PostgreSQL/MySQL):
The skill uses **intelligent tiered database selection** based on extension complexity:
**🎯 Tier 1: SQLite (Simple Extensions - Development Optimized)**
For extensions using only TYPO3 Core APIs (no custom tables, no raw SQL):
```yaml
# No .ddev/config.yaml database config needed
# TYPO3 installation automatically uses SQLite
```
**Benefits:**
- ⚡ Startup: 5-10 seconds faster per ddev start
- 💾 RAM: 900 MB saved (no container)
- 💿 Disk: 744 MB saved
- 🔒 Perfect v11/v12/v13 isolation
**⚠️ Development ONLY** - Never use SQLite in production. Switch to MariaDB for final testing.
**🔧 Tier 2: MariaDB 10.11 (Complex Extensions - Production Parity)**
For extensions with custom tables, raw SQL, or unknown complexity:
```yaml
# Default for complex extensions (.ddev/config.yaml)
database:
type: mariadb
version: "10.11"
```
**Why MariaDB 10.11?** Production standard (95% hosting), extension compatibility, 13-36% faster than MySQL 8.
**🌐 Tier 3 & 4: Specialized Databases**
```yaml
# PostgreSQL 16 (for GIS, analytics, full-text search)
database:
type: postgres
version: "16"
# MariaDB 11 (forward-looking performance)
database:
type: mariadb
version: "11.4"
# MySQL 8.0 (corporate/Oracle ecosystem)
database:
type: mysql
version: "8.0"
```
**For detailed rationale**, see `docs/adr/0002-mariadb-default-with-database-alternatives.md`.
**Caching Service** (Valkey or Redis):
The skill provides Valkey 8 as the default caching service (open source, future-proof):
```bash
# Default: Valkey 8
cp .ddev/templates/docker-compose.services.yaml.optional .ddev/docker-compose.services.yaml
# Alternative: Redis 7 (for legacy production parity)
cp .ddev/templates/docker-compose.services-redis.yaml.optional .ddev/docker-compose.services.yaml
# Restart DDEV
ddev restart
```
**Why Valkey?** True open source (BSD-3), AWS/Google/Oracle backing, 30% smaller than Redis 8, cost-effective. See `docs/adr/0001-valkey-default-with-redis-alternative.md` for details.
**Additional Services** (add to `.ddev/docker-compose.services.yaml`):
```yaml
# The services template includes: Valkey/Redis, MailPit, Ofelia
# See SKILL.md for complete documentation
```
## Troubleshooting
### DDEV Won't Start
**Port Conflicts:**
```bash
# Check what's using ports 80/443
sudo lsof -i :80
sudo lsof -i :443
# Option 1: Stop conflicting services
# Option 2: Change ports in .ddev/config.yaml
router_http_port: "8080"
router_https_port: "8443"
```
### Installation Fails
**Check Composer Issues:**
```bash
ddev ssh
composer diagnose
```
**View Installation Logs:**
```bash
ddev logs
```
**Retry Installation:**
```bash
# For specific version
ddev ssh
rm -rf /var/www/html/v13/*
exit
ddev install-v13
```
### Extension Not Appearing
**Verify Extension Key:**
```bash
ddev ssh
echo $EXTENSION_KEY # Should match your ext_emconf.php
```
**Check Composer Repository:**
```bash
ddev ssh
cd /var/www/html/v13
composer config repositories
```
## Contributing
Contributions welcome! Please:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## Credits
This skill is based on the excellent work by [Armin Vieweg](https://github.com/a-r-m-i-n) in [ddev-for-typo3-extensions](https://github.com/a-r-m-i-n/ddev-for-typo3-extensions).
### Additional Resources
- [DDEV Documentation](https://ddev.readthedocs.io/)
- [TYPO3 Extension Development](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/Index.html)
- [TYPO3 DDEV Setup Guide](https://docs.typo3.org/m/typo3/tutorial-getting-started/main/en-us/Installation/UsingDdev.html)
## License
MIT License - see [LICENSE](LICENSE) file for details.
## Support
- **Issues**: [GitHub Issues](https://github.com/netresearch/typo3-ddev-skill/issues)
- **Discussions**: [GitHub Discussions](https://github.com/netresearch/typo3-ddev-skill/discussions)
- **TYPO3 Slack**: #ddev channel
---
**Made with ❤️ for the TYPO3 Community**

903
skills/typo3-ddev/SKILL.md Normal file
View File

@@ -0,0 +1,903 @@
---
name: typo3-ddev
description: "Automate DDEV environment setup for TYPO3 extension development. Use when setting up local development environment, configuring DDEV for TYPO3 extensions, or creating multi-version TYPO3 testing environments. Covers: DDEV configuration generation, TYPO3 11.5/12.4/13.4 LTS installation, custom DDEV commands, Apache vhost setup, Docker volume management, and .gitignore best practices (avoid double-ignore anti-pattern). Provides complete automation from metadata detection to ready-to-use TYPO3 backend access."
license: MIT License - see LICENSE file
metadata:
author: Netresearch
repository: https://github.com/netresearch/typo3-ddev-skill
version: 1.6.0
tags:
- typo3
- ddev
- docker
- development
- testing
- devops
platforms:
- linux
- macos
- windows
typo3-versions:
- 11.5-lts
- 12.4-lts
- 13.4-lts
---
# TYPO3 DDEV Setup Skill
This skill automates DDEV environment setup for TYPO3 extension development projects. It creates a complete TYPO3 development environment for testing and developing extensions across multiple TYPO3 versions.
## Purpose
This skill automates the setup of DDEV for TYPO3 extension development projects. It creates a multi-version TYPO3 testing environment where the extension can be developed and tested across TYPO3 11.5 LTS, 12.4 LTS, and 13.4 LTS simultaneously.
## When to Use This Skill
Use this skill when:
- Developer has a TYPO3 extension project and wants to set up DDEV
- Project contains `ext_emconf.php` or is identified as a TYPO3 extension in `composer.json`
- Developer needs to test extension across multiple TYPO3 versions
- Quick development environment spin-up is needed
## Prerequisites Validation
Before executing ANY DDEV commands, perform comprehensive prerequisite validation:
1. **Run validation script**: Execute `scripts/validate-prerequisites.sh` to check:
- Docker daemon status (must be running)
- Docker CLI version (requires >= 20.10)
- Docker Compose version (requires >= 2.0)
- DDEV installation (must be present)
- TYPO3 extension project structure (must be valid)
2. **If validation fails**: Consult `references/prerequisites-validation.md` for platform-specific installation instructions (Linux/WSL2, macOS, Windows), version requirements, manual validation steps, and troubleshooting
3. **Report results**: Display validation status to user with specific instructions for any missing prerequisites
Always validate on the FIRST DDEV command in a session to catch environment issues early.
## Step-by-Step Workflow
### Step 1: Extract Extension Metadata
Scan the project to extract:
1. **Extension Key** (e.g., `my_ext`):
- From `ext_emconf.php`: Look for the array key or filename pattern
- From `composer.json`: Look for `extra.typo3/cms.extension-key`
- Fallback: Ask user to provide
2. **Composer Package Name** (e.g., `vendor/my-ext`):
- From `composer.json`: Look for `name` field
- Fallback: Construct from extension key or ask user
3. **Vendor Namespace** (e.g., `Vendor\MyExt`):
- From `composer.json`: Look for `autoload.psr-4` keys
- Fallback: Ask user to provide
4. **Extension Name** (PascalCase, e.g., `MyExt`):
- Convert extension key to PascalCase
- Or extract from namespace
### Step 2: Confirm Configuration
Present extracted values to user and confirm:
```
Detected TYPO3 Extension Configuration:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Extension Key: my_ext
Package Name: vendor/my-ext
DDEV Sitename: my-ext
Vendor Namespace: Vendor\MyExt
TYPO3 Versions: 11.5, 12.4, 13.4
PHP Version: 8.2
Is this correct? (y/n)
```
Allow user to adjust any values if needed.
### Step 3: Generate DDEV Configuration
Create the following directory structure and files:
```
.ddev/
├── config.yaml
├── docker-compose.web.yaml
├── apache/
│ └── apache-site.conf
├── web-build/
│ └── Dockerfile
└── commands/
└── web/
├── install-v11
├── install-v12
├── install-v13
└── install-all
.envrc
```
#### 3.1: .ddev/config.yaml
Replace these variables:
- `{{DDEV_SITENAME}}` → DDEV sitename (e.g., `my-ext`)
```yaml
name: {{DDEV_SITENAME}}
type: php
docroot: ""
no_project_mount: true
php_version: "8.2"
composer_version: "2"
webserver_type: apache-fpm
router_http_port: "80"
router_https_port: "443"
xdebug_enabled: false
additional_hostnames:
- docs.{{DDEV_SITENAME}}
- v11.{{DDEV_SITENAME}}
- v12.{{DDEV_SITENAME}}
- v13.{{DDEV_SITENAME}}
additional_fqdns: []
use_dns_when_possible: true
```
#### 3.2: .ddev/docker-compose.web.yaml
Replace these variables:
- `{{EXTENSION_KEY}}` → Extension key with underscores (e.g., `my_ext`)
- `{{PACKAGE_NAME}}` → Composer package name (e.g., `vendor/my-ext`)
- `{{DDEV_SITENAME}}` → DDEV sitename for volumes
```yaml
services:
web:
environment:
- EXTENSION_KEY={{EXTENSION_KEY}}
- PACKAGE_NAME={{PACKAGE_NAME}}
# TYPO3 v11 and v12 config
- TYPO3_INSTALL_DB_DRIVER=mysqli
- TYPO3_INSTALL_DB_USER=root
- TYPO3_INSTALL_DB_PASSWORD=root
- TYPO3_INSTALL_DB_HOST=db
- TYPO3_INSTALL_DB_UNIX_SOCKET=
- TYPO3_INSTALL_DB_USE_EXISTING=0
- TYPO3_INSTALL_ADMIN_USER=admin
- TYPO3_INSTALL_ADMIN_PASSWORD=Password:joh316
- TYPO3_INSTALL_SITE_NAME=EXT:{{EXTENSION_KEY}} Dev Environment
- TYPO3_INSTALL_SITE_SETUP_TYPE=site
- TYPO3_INSTALL_WEB_SERVER_CONFIG=apache
# TYPO3 v13 config
- TYPO3_DB_DRIVER=mysqli
- TYPO3_DB_USERNAME=root
- TYPO3_DB_PASSWORD=root
- TYPO3_DB_HOST=db
- TYPO3_SETUP_ADMIN_EMAIL=admin@example.com
- TYPO3_SETUP_ADMIN_USERNAME=admin
- TYPO3_SETUP_ADMIN_PASSWORD=Password:joh316
- TYPO3_PROJECT_NAME=EXT:{{EXTENSION_KEY}} Dev Environment
- TYPO3_SERVER_TYPE=apache
volumes:
- type: bind
source: ../
target: /var/www/{{EXTENSION_KEY}}
consistency: cached
- v11-data:/var/www/html/v11
- v12-data:/var/www/html/v12
- v13-data:/var/www/html/v13
volumes:
v11-data:
name: "${DDEV_SITENAME}-v11-data"
v12-data:
name: "${DDEV_SITENAME}-v12-data"
v13-data:
name: "${DDEV_SITENAME}-v13-data"
```
#### 3.3-3.7: Additional Configuration Files
Generate the following files using the templates from this skill repository:
- `.ddev/apache/apache-site.conf` (replace `{{EXTENSION_KEY}}` and `{{DDEV_SITENAME}}`)
- `.ddev/web-build/Dockerfile` (replace `{{EXTENSION_KEY}}` and `{{DDEV_SITENAME}}`)
- `.ddev/commands/web/install-v11` (make executable)
- `.ddev/commands/web/install-v12` (make executable)
- `.ddev/commands/web/install-v13` (make executable)
- `.ddev/commands/web/install-all` (make executable)
- `.ddev/commands/host/docs` (make executable) - for rendering documentation
#### 3.8: .envrc (direnv Configuration)
Generate `.envrc` in the project root for direnv integration:
Replace these variables:
- `{{EXTENSION_KEY}}` → Extension key with underscores (e.g., `my_ext`)
- `{{DDEV_SITENAME}}` → DDEV sitename (e.g., `my-ext`)
```bash
# direnv configuration for {{EXTENSION_KEY}}
# Auto-generated by typo3-ddev-skill
# Add composer bin directory to PATH
PATH_add vendor/bin
# Composer configuration
export COMPOSER_PROCESS_TIMEOUT=600
# DDEV environment variables
export DDEV_PROJECT={{DDEV_SITENAME}}
export DDEV_PRIMARY_URL=https://{{DDEV_SITENAME}}.ddev.site
export DDEV_DOCROOT_URL=https://docs.{{DDEV_SITENAME}}.ddev.site
# Load local environment overrides if present
dotenv_if_exists .env.local
# Display activation message
echo "✅ {{EXTENSION_KEY}} development environment activated"
echo ""
echo "🌐 DDEV Environment:"
echo " Primary: https://{{DDEV_SITENAME}}.ddev.site"
echo " Docs: https://docs.{{DDEV_SITENAME}}.ddev.site"
echo " TYPO3 v11: https://v11.{{DDEV_SITENAME}}.ddev.site"
echo " TYPO3 v12: https://v12.{{DDEV_SITENAME}}.ddev.site"
echo " TYPO3 v13: https://v13.{{DDEV_SITENAME}}.ddev.site"
echo ""
echo "🚀 DDEV Commands:"
echo " ddev start Start DDEV environment"
echo " ddev stop Stop DDEV environment"
echo " ddev restart Restart DDEV environment"
echo " ddev install-v11 Install TYPO3 11.5 LTS"
echo " ddev install-v12 Install TYPO3 12.4 LTS"
echo " ddev install-v13 Install TYPO3 13.4 LTS"
echo " ddev install-all Install all TYPO3 versions"
```
**What it does:**
- Adds `vendor/bin` to PATH for Composer tools
- Exports DDEV environment variables for easy access
- Displays helpful information when entering the project directory
- Lists available DDEV URLs and commands
**User action required:** Run `direnv allow` to activate the configuration.
**Optional:** If direnv is not installed, the file can be safely ignored.
### Step 4: Initialize DDEV
After generating all files, guide the user through starting DDEV:
```bash
# Start DDEV containers
ddev start
```
Wait for DDEV to start successfully.
### Step 5: Install TYPO3 Environments
Provide options to user:
**Option A: Install All Versions** (Recommended for compatibility testing)
```bash
ddev install-all
```
**Option B: Install Specific Version** (Faster for targeted development)
```bash
ddev install-v11 # TYPO3 11.5 LTS
ddev install-v12 # TYPO3 12.4 LTS
ddev install-v13 # TYPO3 13.4 LTS
```
Installation time: ~2-5 minutes per version (depending on network speed)
**What gets installed automatically:**
- ✅ TYPO3 Core (specified version)
- ✅ Your extension (activated and ready to use)
- ✅ TYPO3 Backend Styleguide (for UI pattern reference)
- ✅ Extension Manager
- ✅ Introduction Package (86+ pages with demo content for testing)
### Step 6: Provide Access Information
After successful installation, display:
```
✅ DDEV Environment Ready!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 Overview Dashboard:
https://{{DDEV_SITENAME}}.ddev.site/
🌐 TYPO3 Installations:
TYPO3 11.5 LTS
Frontend: https://v11.{{DDEV_SITENAME}}.ddev.site/
Backend: https://v11.{{DDEV_SITENAME}}.ddev.site/typo3/
TYPO3 12.4 LTS
Frontend: https://v12.{{DDEV_SITENAME}}.ddev.site/
Backend: https://v12.{{DDEV_SITENAME}}.ddev.site/typo3/
TYPO3 13.4 LTS
Frontend: https://v13.{{DDEV_SITENAME}}.ddev.site/
Backend: https://v13.{{DDEV_SITENAME}}.ddev.site/typo3/
🔑 Backend Credentials:
Username: admin
Password: Password:joh316
📦 Your Extension:
Installed in all TYPO3 versions
Source: /var/www/{{EXTENSION_KEY}}/ (bind-mounted from project root)
⚙️ Useful Commands:
ddev restart # Restart containers
ddev ssh # SSH into web container
ddev logs # View container logs
ddev describe # Show project details
ddev stop # Stop containers
ddev delete # Remove containers (keeps volumes)
```
### Step 7: Generate Makefile (Optional but Recommended)
After DDEV setup, generate a standardized Makefile following Netresearch's pattern for convenient command access:
```bash
ddev generate-makefile
```
#### Makefile Features
The generated Makefile provides a unified interface with 30+ targets:
**DDEV Management:**
```bash
make up # Complete startup (start DDEV + install all TYPO3 versions)
make start # Start DDEV environment
make stop # Stop DDEV environment
make install-v11 # Install TYPO3 11.5 LTS
make install-v12 # Install TYPO3 12.4 LTS
make install-v13 # Install TYPO3 13.4 LTS
make install-all # Install all TYPO3 versions
make urls # Show all access URLs
```
**Testing & Quality:**
```bash
make test # Run all tests (unit + functional)
make test-unit # Run unit tests only
make test-functional # Run functional tests
make lint # Run all linters (PHP syntax + PHPStan + code style)
make format # Auto-fix code style issues
make typecheck # Run PHPStan static analysis
make ci # Complete CI pipeline (pre-commit checks)
```
**Developer Convenience:**
```bash
make help # Show all available commands (default target)
make ssh # SSH into DDEV web container
make clean # Clean temporary files and caches
```
**Extension-Specific Commands:**
The Makefile includes a customizable section for extension-specific commands. Developers can add their own targets following the pattern:
```makefile
.PHONY: my-command
my-command: ## Description of my command
ddev ssh -d v12 "vendor/bin/typo3 extension:command"
```
#### Why Use the Makefile?
1. **Consistency** - Follows Netresearch's established pattern across TYPO3 extensions
2. **Single Command Setup** - `make up` does everything (DDEV start + install all versions)
3. **Discoverability** - `make help` shows all available commands with descriptions
4. **CI Integration** - Standardized `make ci`, `make test`, `make lint` for pipelines
5. **Developer Experience** - Familiar interface, shorter commands than `ddev` equivalents
6. **Documentation** - Self-documenting with auto-generated help
#### Example Workflow
```bash
# Initial setup
make up # Start DDEV + install all TYPO3 versions
# Development
make lint # Check code quality
make test # Run tests
make format # Fix code style
# Check URLs
make urls # Show all access URLs
# Complete CI check before commit
make ci # Run full quality pipeline
```
#### Customization
After generation, edit the Makefile to add project-specific commands while keeping core targets (up, start, test, lint, ci) unchanged for consistency across projects.
The Makefile is optional but highly recommended for improved developer experience.
### Step 8: Generate Project Index Page (Optional but Recommended)
After DDEV setup, generate a beautiful overview page for easy access to all TYPO3 versions and development tools:
```bash
ddev generate-index
```
#### Index Page Features
The generated `index.html` provides a centralized dashboard with:
**Visual Overview:**
- Modern, responsive design with gradient background
- Card-based layout for each TYPO3 version
- Clear visual hierarchy and hover effects
- Mobile-friendly responsive grid
**Quick Access Links:**
```
TYPO3 v12 LTS:
- Frontend: https://v12.{project}.ddev.site/
- Backend: https://v12.{project}.ddev.site/typo3/
TYPO3 v13 LTS:
- Frontend: https://v13.{project}.ddev.site/
- Backend: https://v13.{project}.ddev.site/typo3/
```
**Development Tools:**
- Backend credentials display (admin / Password:joh316)
- Mailpit access link
- Documentation link (if available)
#### Why Use the Index Page?
1. **Single Entry Point** - Access all TYPO3 versions from one beautiful page
2. **Eliminates URL Guessing** - No need to remember subdomain patterns
3. **Professional Presentation** - Polished interface for the development environment
4. **Credential Reference** - Backend login info always visible
5. **Tool Discovery** - Links to Mailpit and other development tools
6. **Multi-Version Testing** - Easy switching between TYPO3 12 and 13
#### Example Usage
```bash
# After DDEV setup
ddev generate-index
# Access the overview page
open https://{project}.ddev.site/
# or
open http://{project}.ddev.site/
```
The index page is accessible at both the main domain (e.g., `temporal-cache.ddev.site`) and serves as the landing page for the entire development environment.
#### Customization
After generation, you can edit `index.html` to:
- Add extension-specific documentation links
- Include CI/CD status badges
- Add custom tool links (PHPMyAdmin, Redis Commander, etc.)
- Customize colors, branding, or layout
- Add project-specific information or notes
The index page is optional but highly recommended for improved developer experience, especially in multi-version testing environments.
**For advanced branding and customization details including:**
- Automatic branding detection (Netresearch/TYPO3/generic)
- Brand-specific color schemes and typography
- Design specifications
See: `references/index-page-generation.md`
## Error Handling
When encountering errors during setup or installation:
1. **Check error category**: Identify if the error relates to prerequisites, database setup, extension detection, or service configuration
2. **Consult troubleshooting guide**: Read `references/troubleshooting.md` and locate the relevant error category
3. **Apply recommended solution**: Follow the step-by-step resolution guide for the specific error
4. **For Windows/WSL2 environments**: If error persists on Windows, consult `references/windows-fixes.md` for platform-specific solutions (CRLF issues, health check failures, Apache config problems)
5. **Performance issues on Windows**: Consult `references/windows-optimizations.md` for performance tuning
Report the error to the user with the specific solution being applied, then re-attempt the failed operation.
## Advanced Configuration
When user requests customizations beyond the standard setup:
1. **Identify customization type**: Determine if request involves PHP version, database engine, debugging tools, TYPO3 versions, or additional services
2. **Consult advanced options guide**: Read `references/advanced-options.md` for the relevant configuration section
3. **Present options to user**: Show available choices (e.g., PHP 8.1/8.2/8.3, database engines with tiered selection logic)
4. **Apply configuration**: Follow the templates and instructions in `references/advanced-options.md` to implement the requested customization
5. **Configure optional services**: If user needs Valkey/Redis caching, MailPit, Ofelia scheduler, or other services, follow the service configuration templates in the advanced guide
Offer customization options proactively when user mentions performance requirements, specific PHP versions, or debugging needs.
## Documentation Rendering
### Rendering Extension Documentation
If the extension has a `Documentation/` directory with reStructuredText (.rst) files:
```bash
ddev docs
```
**What it does:**
- Renders `.rst` documentation files to HTML using TYPO3's official render-guides Docker image
- Outputs to `Documentation-GENERATED-temp/`
- Makes documentation accessible at `https://docs.{{DDEV_SITENAME}}.ddev.site/`
**Requirements:**
- `Documentation/Index.rst` must exist
- Docker must be running on the host
**Output:**
```
📚 Rendering TYPO3 Extension Documentation
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📖 Source: Documentation/
📦 Output: Documentation-GENERATED-temp/
🔨 Rendering documentation with TYPO3 render-guides...
✅ Documentation rendered successfully!
🌐 View at: https://docs.{{DDEV_SITENAME}}.ddev.site/
```
**Note:** The docs subdomain is automatically configured in `.ddev/apache/apache-site.conf` and accessible after running `ddev docs`.
## Demo Content (Introduction Package)
When discussing testing capabilities with users, inform them that the Introduction Package is automatically installed with every TYPO3 version during setup, providing:
- 86+ pages with full page tree structure for testing navigation
- 226+ content elements (text, images, forms, tables) for testing rendering
- Multi-language support (English, German, Danish) for testing translations
- Bootstrap Package responsive theme for testing frontend integration
- RTE feature examples for testing rich text functionality
Direct users to test their extension against this demo content at:
- Frontend: `https://v11.{{DDEV_SITENAME}}.ddev.site/` (or v12, v13)
- Backend: `https://v11.{{DDEV_SITENAME}}.ddev.site/typo3/` (or v12, v13)
No manual installation required—the package installs automatically during `ddev install-all` or individual version commands.
## Extension Auto-Configuration
When user's extension requires additional setup beyond standard installation (RTE configuration, Page TSConfig, TypoScript), guide them through creating a custom configuration command:
```bash
# Copy the configure-extension template
cp .ddev/templates/commands/web/configure-extension.optional .ddev/commands/web/configure-{{EXTENSION_KEY}}
chmod +x .ddev/commands/web/configure-{{EXTENSION_KEY}}
# Edit to add your extension-specific configuration
# Examples: RTE YAML, Page TSConfig, site package setup
# Run after TYPO3installation
ddev configure-{{EXTENSION_KEY}} v13
```
### Identify Configuration Needs by Extension Type
**For RTE/CKEditor Extensions**, configure:
- RTE YAML configuration importing the plugin
- Toolbar buttons and editor config
- Page TSConfig for RTE presets
- Example reference: `rte_ckeditor_image` with custom image handling
**For Backend Module Extensions**, configure:
- Page TSConfig for module access
- User permissions setup
- Initial database records
**For Frontend Plugin Extensions**, configure:
- TypoScript configuration
- Example content elements
- Plugin settings
### Template Structure
The `configure-extension.optional` template includes:
1. **Introduction Package Installation** - Demo content for testing
2. **Site Package Creation** - Programmatic extension setup with:
- `ext_emconf.php` - Extension metadata
- `ext_localconf.php` - Global configuration
- `ext_tables.php` - Table definitions and TSConfig loading
3. **Configuration Files** - RTE YAML, Page TSConfig, TypoScript
4. **Cache Flushing** - Ensure changes take effect
5. **Success Message** - Clear next steps for developer
### TYPO3 v13 Site Sets Approach
**Important:** TYPO3 v13 introduces site sets as the modern approach for TypoScript and configuration loading, replacing static templates.
**Site Sets vs Static Templates:**
| Feature | Site Sets (TYPO3 v13+) | Static Templates (Legacy) |
|---------|------------------------|---------------------------|
| Configuration | `config.yaml` in site config | `sys_template` database records |
| Loading Order | Dependency-based | Include order in template |
| Type Safety | Schema-validated | No validation |
| Maintenance | Version-controlled files | Database-stored |
| Best Practice | ✅ Use for TYPO3 v13+ | ⚠️ Legacy approach |
**How Extensions Should Provide Configuration:**
1. **Create a Site Set** in your extension:
```
Configuration/Sets/YourExtensionName/
├── config.yaml # Site set metadata
├── setup.typoscript # TypoScript configuration
└── page.tsconfig # Page TSConfig (optional)
```
2. **Define Site Set** in `config.yaml`:
```yaml
name: vendor/extension-name
label: 'Your Extension Name'
# Optional dependencies load BEFORE your set
optionalDependencies:
- typo3/fluid-styled-content
- bk2k/bootstrap-package
```
3. **Import TypoScript** in `setup.typoscript`:
```typoscript
@import 'EXT:your_extension/Configuration/TypoScript/*.typoscript'
```
4. **Users Add to Site Configuration** (`config/sites/main/config.yaml`):
```yaml
dependencies:
- bootstrap-package/full # Frontend rendering
- vendor/extension-name # Your extension
```
**Benefits:**
- ✅ No sys_template database records needed
- ✅ Proper dependency ordering
- ✅ Version-controlled configuration
- ✅ No static template conflicts
- ✅ Type-safe settings with schema
**Common Pitfall:** Avoid loading TypoScript via BOTH site sets AND static templates - this causes double-loading and configuration conflicts (e.g., duplicate `lib.parseFunc_RTE.tags.img` processors causing unexpected behavior).
**Debugging Site Sets:**
```bash
# Check active site sets for a site
ddev exec vendor/bin/typo3 site:show <siteIdentifier>
# View resolved TypoScript (TYPO3 v13)
Backend → Site Management → Sites → [Your Site] → Dependencies
```
### Example: RTE Configuration Command
For a CKEditor plugin extension:
```bash
#!/bin/bash
VERSION=${1:-v13}
INSTALL_DIR=/var/www/html/$VERSION
# Install demo content
composer require typo3/cms-introduction -d $INSTALL_DIR
vendor/bin/typo3 extension:setup --extension=introduction
# Create site package
mkdir -p $INSTALL_DIR/public/typo3conf/ext/site_rte/Configuration/RTE
# Create RTE YAML importing your plugin
cat > .../Configuration/RTE/Default.yaml << 'EOF'
imports:
- { resource: "EXT:rte_ckeditor/Configuration/RTE/Default.yaml" }
- { resource: "EXT:your_extension/Configuration/RTE/Plugin.yaml" }
editor:
config:
toolbar:
items:
- your_custom_button
- ...
EOF
# Flush caches
vendor/bin/typo3 cache:flush
```
**Benefits:**
- One-command setup after TYPO3 installation
- Consistent configuration across team
- Demo content ready for testing
- Reduces manual configuration errors
### Step 9: Verify Installation
After installation completes, perform these verification steps in order:
1. **Confirm prerequisites passed**: Review output from Step 1 prerequisite validation
2. **Verify extension metadata**: Check that extracted values match project configuration
3. **Test DDEV status**: Run `ddev describe` to confirm containers are running
4. **Validate TYPO3 installations**: Confirm at least one version installed without errors
5. **Test URL accessibility**: Open each TYPO3 version URL in browser to verify routing
6. **Verify backend access**: Log in to TYPO3 backend with credentials (admin / Password:joh316)
## Troubleshooting
### Database Already Exists Error
If reinstalling TYPO3 and you get "database already exists" errors:
```bash
# Clean up and recreate database
ddev mysql -e "DROP DATABASE IF EXISTS v13; CREATE DATABASE v13;"
# Now retry installation
ddev install-v13
```
### TYPO3 Setup Shows "Database contains tables" Error
```bash
# Option 1: Clean database
ddev mysql v13 -e "DROP DATABASE v13; CREATE DATABASE v13;"
# Option 2: Use different database name
# Edit install-v* script to use different DB name
```
### Services Not Loading
If Redis, MailPit, or Ofelia containers don't start:
```bash
# Check container status
docker ps --filter "name=ddev-{{DDEV_SITENAME}}"
# View logs
docker logs ddev-{{DDEV_SITENAME}}-redis
docker logs ddev-{{DDEV_SITENAME}}-mailpit
docker logs ddev-{{DDEV_SITENAME}}-ofelia
# Restart DDEV
ddev restart
```
### Extension Not Appearing in Backend
```bash
# Flush all caches
ddev exec -d /var/www/html/v13 vendor/bin/typo3 cache:flush
# Check extension is symlinked
ddev exec ls -la /var/www/html/v13/vendor/{{VENDOR}}/{{EXTENSION_KEY}}
```
### .gitignore Configuration Best Practices
**CRITICAL:** Ensure `.ddev/.gitignore` is committed to version control, NOT ignored!
**The Double-Ignore Anti-Pattern:**
DDEV often generates `.ddev/.gitignore` with this problematic structure:
```
#ddev-generated: Automatically generated ddev .gitignore.
# You can remove the above line if you want to edit and maintain this file yourself.
/.gitignore ← File ignores ITSELF!
```
**The Problem:**
1. `.ddev/.gitignore` ignores itself (`/.gitignore`)
2. Root `.gitignore` also ignores it (`.ddev/.gitignore`)
3. Result: DDEV ignore rules NOT shared with team
4. Each developer must generate their own DDEV config
5. Inconsistent git behavior across team members
**The Fix:**
1. **Remove from root `.gitignore`:**
```diff
# .gitignore (ROOT)
.ddev/.homeadditions
.ddev/.ddev-docker-compose-full.yaml
- .ddev/.gitignore ← REMOVE THIS LINE
```
2. **Fix `.ddev/.gitignore` to not self-ignore:**
```diff
# .ddev/.gitignore (UPDATED)
- #ddev-generated: Automatically generated ddev .gitignore.
- # You can remove the above line if you want to edit and maintain this file yourself.
- /.gitignore
+ # DDEV gitignore rules
+ # Manually maintained to ensure consistency across team
/**/*.example
/.dbimageBuild
/.ddev-docker-*.yaml
```
3. **Commit DDEV configuration to share with team:**
```bash
git add .ddev/.gitignore .ddev/config.yaml .ddev/docker-compose.*.yaml
git add .ddev/apache/ .ddev/commands/ .ddev/web-build/
git commit -m "[TASK] Add DDEV configuration for development environment"
```
**What to Commit vs. Ignore:**
✅ **Commit (share with team):**
- `.ddev/.gitignore` (ignore rules)
- `.ddev/config.yaml` (main config)
- `.ddev/docker-compose.*.yaml` (custom services)
- `.ddev/apache/` (custom Apache config)
- `.ddev/commands/` (custom DDEV commands)
- `.ddev/web-build/` (custom Dockerfile)
❌ **Ignore (personal/generated):**
- `.ddev/.homeadditions` (personal shell config)
- `.ddev/.ddev-docker-compose-full.yaml` (auto-generated)
- `.ddev/db_snapshots/` (database snapshots)
- `.ddev/.sshimageBuild` (build artifacts)
- `.ddev/.webimageBuild` (build artifacts)
**Validation:**
After fixing, verify:
```bash
# Should show .ddev/.gitignore as tracked
git ls-files .ddev/.gitignore
# Should list DDEV files to be committed
git status .ddev/
```
**Benefits:**
- ✅ Consistent environment across all developers
- ✅ Shared DDEV ignore rules
- ✅ Quick onboarding: `git clone && ddev start`
- ✅ No manual DDEV configuration needed
- ✅ Team follows same git patterns
## Validation Checklist
Before completing, verify:
1. **Confirm prerequisites passed**: Review output from Step 1 prerequisite validation
2. **Verify extension metadata**: Check that extracted values match project configuration
3. **Test DDEV status**: Run `ddev describe` to confirm containers are running
4. **Validate TYPO3 installations**: Confirm at least one version installed without errors
5. **Test URL accessibility**: Open each TYPO3 version URL in browser to verify routing
6. **Verify backend access**: Log in to TYPO3 backend with credentials (admin / Password:joh316)
7. **Confirm extension activation**: Check Extension Manager to ensure extension is loaded
If any verification fails, consult the Troubleshooting section above for resolution steps.
## Output Formatting Guidelines
Throughout all steps, format output for clarity:
- Use emojis for visual indicators: ✅ (success), ❌ (error), 🔧 (configuration), 📦 (installation), 🌐 (URLs), 🔑 (credentials)
- Display progress indicators during operations taking >5 seconds
- Provide copy-pasteable command blocks with explanations
- Present configuration values in formatted tables or code blocks
- Explain what each command does before executing it

View File

@@ -0,0 +1,44 @@
# direnv configuration for {{EXTENSION_KEY}}
# Auto-generated by typo3-ddev-skill
#
# Usage: Run `direnv allow` to activate this configuration
# Install direnv: https://direnv.net/
# Add composer bin directory to PATH
PATH_add vendor/bin
# Composer configuration
export COMPOSER_PROCESS_TIMEOUT=600
# DDEV environment variables
export DDEV_PROJECT={{DDEV_SITENAME}}
export DDEV_PRIMARY_URL=https://{{DDEV_SITENAME}}.ddev.site
export DDEV_DOCROOT_URL=https://docs.{{DDEV_SITENAME}}.ddev.site
# Load local environment overrides if present
dotenv_if_exists .env.local
# Display activation message
echo "✅ {{EXTENSION_KEY}} development environment activated"
echo ""
echo "🌐 DDEV Environment:"
echo " Primary: https://{{DDEV_SITENAME}}.ddev.site"
echo " Docs: https://docs.{{DDEV_SITENAME}}.ddev.site"
echo " TYPO3 v11: https://v11.{{DDEV_SITENAME}}.ddev.site"
echo " TYPO3 v12: https://v12.{{DDEV_SITENAME}}.ddev.site"
echo " TYPO3 v13: https://v13.{{DDEV_SITENAME}}.ddev.site"
echo ""
echo "🚀 Quick Start:"
echo " make up Complete startup (start + setup) ⭐"
echo " make start Start DDEV environment"
echo " make setup Complete setup (docs + install + configure)"
echo ""
echo "📚 DDEV Commands:"
echo " ddev docs Render extension documentation"
echo " ddev install-v11 Install TYPO3 11.5 LTS"
echo " ddev install-v12 Install TYPO3 12.4 LTS"
echo " ddev install-v13 Install TYPO3 13.4 LTS"
echo " ddev install-all Install all TYPO3 versions"
echo ""
echo "📦 Composer Tools: Available in vendor/bin/"
echo "🔧 More Commands: make help (if Makefile present)"

View File

@@ -0,0 +1,124 @@
# Makefile for {{EXTENSION_NAME}} TYPO3 Extension
# Auto-generated by ddev setup
.PHONY: help
help: ## Show available targets
@awk 'BEGIN{FS=":.*##";print "\nUsage: make <target>\n"} /^[a-zA-Z0-9_.-]+:.*##/ {printf " %-22s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
# ===================================
# DDEV Environment Commands
# ===================================
.PHONY: up
up: start setup ## Complete startup (start DDEV + run setup) - ONE COMMAND TO RULE THEM ALL
.PHONY: start
start: ## Start DDEV environment
ddev start
.PHONY: stop
stop: ## Stop DDEV environment
ddev stop
.PHONY: setup
setup: ## Complete setup (install all TYPO3 versions)
@ddev describe >/dev/null 2>&1 || ddev start
ddev install-all
.PHONY: install-v12
install-v12: ## Install TYPO3 v12.4 LTS with extension
ddev install-v12
.PHONY: install-v13
install-v13: ## Install TYPO3 v13.0 LTS with extension
ddev install-v13
.PHONY: install-all
install-all: ## Install all TYPO3 versions
ddev install-all
.PHONY: ddev-restart
ddev-restart: ## Restart DDEV containers
ddev restart
.PHONY: ssh
ssh: ## SSH into DDEV web container
ddev ssh
# ===================================
# Composer & Quality Commands
# ===================================
.PHONY: install
install: ## Install composer dependencies
composer install
.PHONY: lint
lint: ## Run all linters (PHP syntax + PHPStan + code style)
@echo "==> Running PHP lint..."
composer ci:test:php:lint || composer lint
@echo "==> Running PHPStan..."
composer phpstan || composer ci:phpstan || echo "⚠️ PHPStan not configured"
@echo "==> Running code style check..."
composer ci:test:php:cgl || composer cs:check || echo "⚠️ Code style check not configured"
@echo "✅ All linters passed"
.PHONY: format
format: ## Auto-fix code style issues (PSR-12)
composer code:fix || composer cs:fix
.PHONY: typecheck
typecheck: ## Run PHPStan static analysis
composer phpstan || composer ci:phpstan
.PHONY: test
test: ## Run all tests (unit + functional)
composer test
.PHONY: test-unit
test-unit: ## Run unit tests only
composer test:unit
.PHONY: test-functional
test-functional: ## Run functional tests
composer test:functional
.PHONY: test-coverage
test-coverage: ## Generate test coverage report
composer test:coverage
.PHONY: ci
ci: ## Run complete CI pipeline (pre-commit checks)
composer ci || composer ci:test
.PHONY: clean
clean: ## Clean temporary files and caches
rm -rf .php-cs-fixer.cache
rm -rf var/
rm -rf .Build/.cache
# ===================================
# Extension-Specific Commands
# (Customize based on your extension)
# ===================================
.PHONY: urls
urls: ## Show all access URLs
@echo ""
@echo "{{EXTENSION_NAME}} - Access URLs"
@echo "==============================="
@echo ""
@echo "TYPO3 v12.4 LTS:"
@echo " Frontend: https://v12.{{DDEV_PROJECT}}.ddev.site/"
@echo " Backend: https://v12.{{DDEV_PROJECT}}.ddev.site/typo3/"
@echo ""
@echo "TYPO3 v13.0 LTS:"
@echo " Frontend: https://v13.{{DDEV_PROJECT}}.ddev.site/"
@echo " Backend: https://v13.{{DDEV_PROJECT}}.ddev.site/typo3/"
@echo ""
@echo "Backend Credentials:"
@echo " Username: admin"
@echo " Password: Password:joh316"
@echo ""
.DEFAULT_GOAL := help

View File

@@ -0,0 +1,361 @@
# DDEV Services Documentation
This DDEV setup includes additional services for TYPO3 extension development.
## Included Services
### 1. Redis (Caching)
**Container**: `ddev-rte-ckeditor-image-redis`
**Image**: `redis:7-alpine`
**Port**: 6379 (internal)
**Purpose**: High-performance caching for TYPO3
**Access**:
```bash
# From host
ddev redis-cli
# From web container
ddev ssh
redis-cli -h redis
```
**Configuration**:
- See `.ddev/config.redis.yaml` for TYPO3 configuration example
- Add to `/var/www/html/v13/config/system/additional.php`
**Testing**:
```bash
ddev ssh
redis-cli -h redis ping
# Should return: PONG
```
---
### 2. MailPit (Email Testing)
**Container**: `ddev-{{DDEV_SITENAME}}-mailpit`
**Image**: `axllent/mailpit`
**Ports**:
- 1025 (SMTP)
- 8025 (Web UI)
**Purpose**: Catch all emails sent by TYPO3 for testing
**Access**:
- **Web UI**: `http://rte-ckeditor-image.ddev.site:8025`
- **SMTP**: `mailpit:1025` (from containers)
**TYPO3 Configuration**:
Already configured in `.ddev/docker-compose.web.yaml`:
```yaml
TYPO3_INSTALL_MAIL_TRANSPORT: smtp
TYPO3_INSTALL_MAIL_TRANSPORT_SMTP_SERVER: mailpit:1025
```
Or manually in `AdditionalConfiguration.php`:
```php
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] = 'smtp';
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_server'] = 'mailpit:1025';
```
**Testing**:
```bash
# Send test email from TYPO3
ddev ssh
cd /var/www/html/v13
vendor/bin/typo3 mailer:spool:send
# View in MailPit UI
open http://rte-ckeditor-image.ddev.site:8025
```
---
### 3. Ofelia (Cron/Scheduler)
**Container**: `ddev-rte-ckeditor-image-ofelia`
**Image**: `ghcr.io/netresearch/ofelia:latest`
**Purpose**: Run TYPO3 scheduler tasks automatically
**Configuration**: `.ddev/docker-compose.ofelia.yaml`
**Scheduled Jobs**:
- TYPO3 scheduler for v11, v12, v13: Every 1 minute
- Cache warmup for v13: Every 1 hour
**View Logs**:
```bash
# Check if Ofelia is running
docker ps | grep ofelia
# View Ofelia logs
docker logs -f ddev-rte-ckeditor-image-ofelia
# Check scheduler execution
ddev ssh
cd /var/www/html/v13
vendor/bin/typo3 scheduler:list
```
**Manual Execution**:
```bash
ddev ssh
t3-scheduler-v13 # alias for scheduler:run on v13
t3-scheduler-all # run scheduler on all versions
```
---
## Alternative: Traditional Cron in Web Container
If you prefer traditional cron instead of Ofelia:
1. **Enable cron in Dockerfile**:
Edit `.ddev/web-build/Dockerfile` and add:
```dockerfile
RUN apt-get update && apt-get install -y cron
COPY install-cron.sh /opt/install-cron.sh
RUN chmod +x /opt/install-cron.sh && /opt/install-cron.sh
```
2. **Restart DDEV**:
```bash
ddev restart
```
3. **Verify cron**:
```bash
ddev ssh
crontab -l
service cron status
```
---
## Service Management
### Start/Stop Services
```bash
# Restart all services
ddev restart
# Stop DDEV (keeps volumes)
ddev stop
# Remove containers (keeps volumes)
ddev delete
# Remove everything including volumes
ddev delete --omit-snapshot --yes
docker volume rm rte-ckeditor-image-redis-data
```
### View Service Status
```bash
# All DDEV containers
ddev describe
# All containers
docker ps | grep rte-ckeditor-image
# Service logs
docker logs ddev-rte-ckeditor-image-redis
docker logs ddev-{{DDEV_SITENAME}}-mailpit
docker logs ddev-rte-ckeditor-image-ofelia
```
### Access Services
```bash
# Redis CLI
ddev redis-cli
# Or from web container
ddev ssh
redis-cli -h redis
# MailPit web interface
open http://rte-ckeditor-image.ddev.site:8025
# Check Redis connection from TYPO3
ddev ssh
cd /var/www/html/v13
vendor/bin/typo3 cache:flush
```
---
## TYPO3 Scheduler Configuration
### Enable Scheduler Tasks
1. **Access TYPO3 Backend**: https://v13.rte-ckeditor-image.ddev.site/typo3/
2. **Login**: admin / Password:joh316
3. **System → Scheduler**
4. **Create tasks** (examples):
- Table garbage collection
- Index queue worker
- File abstraction layer indexing
- Import/Export tasks
### Verify Scheduler is Running
```bash
ddev ssh
cd /var/www/html/v13
# List all scheduler tasks
vendor/bin/typo3 scheduler:list
# Run manually (for testing)
vendor/bin/typo3 scheduler:run
# Check last execution time
vendor/bin/typo3 scheduler:list --verbose
```
---
## Performance Tuning
### Redis
**Memory Limit**: Currently 256MB
**Eviction Policy**: `allkeys-lru` (Least Recently Used)
To adjust:
```yaml
# .ddev/docker-compose.services.yaml
environment:
- REDIS_MAXMEMORY=512mb # Increase if needed
```
### MailPit
No tuning needed for development. All emails are stored in memory.
### Ofelia/Cron
**Frequency**: Default is every 1 minute
To adjust, edit `.ddev/docker-compose.ofelia.yaml`:
```yaml
# Every 5 minutes instead
ofelia.job-exec.typo3-scheduler-v13.schedule: "@every 5m"
# Specific time (e.g., 2am daily)
ofelia.job-exec.cache-warmup.schedule: "0 2 * * *"
```
---
## Troubleshooting
### Redis Not Connecting
```bash
# Test Redis
ddev ssh
redis-cli -h redis ping
# Should return: PONG
# If not, check Redis container
docker logs ddev-rte-ckeditor-image-redis
```
### MailPit Not Receiving Emails
```bash
# Check TYPO3 mail configuration
ddev ssh
cd /var/www/html/v13
vendor/bin/typo3 configuration:show MAIL
# Test email sending
vendor/bin/typo3 mailer:spool:send
```
### Scheduler Not Running
```bash
# Check Ofelia logs
docker logs -f ddev-rte-ckeditor-image-ofelia
# Manually run scheduler
ddev ssh
t3-scheduler-v13
# Check for errors
cd /var/www/html/v13
vendor/bin/typo3 scheduler:list --verbose
```
### Remove Service
To remove a service, comment it out in `.ddev/docker-compose.services.yaml` and restart:
```bash
ddev restart
```
---
## Additional Services (Optional)
### Adminer (Database GUI)
```bash
ddev get ddev/ddev-adminer
ddev restart
# Access: https://rte-ckeditor-image.ddev.site:9999
```
### Elasticsearch
```yaml
# Add to .ddev/docker-compose.services.yaml
elasticsearch:
image: elasticsearch:8.10.2
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200"
```
### Solr
```yaml
# Add to .ddev/docker-compose.services.yaml
solr:
image: solr:9
ports:
- "8983"
volumes:
- solr-data:/var/solr
```
---
## Quick Reference
| Service | Access | Purpose |
|---------|--------|---------|
| Redis | `redis-cli -h redis` | Caching |
| MailPit UI | http://localhost:8025 | Email testing |
| MailPit SMTP | `mailpit:1025` | Email delivery |
| Ofelia | Background | Cron jobs |
| Web | https://*.ddev.site | TYPO3 instances |
| Database | `ddev mysql` | MariaDB |
---
**Questions?** Check DDEV docs: https://ddev.readthedocs.io/

View File

@@ -0,0 +1,161 @@
ServerName {{DDEV_SITENAME}}.ddev.site
<VirtualHost *:80>
# Workaround from https://mail-archives.apache.org/mod_mbox/httpd-users/201403.mbox/%3C49404A24C7FAD94BB7B45E86A9305F6214D04652@MSGEXSV21103.ent.wfb.bank.corp%3E
# See also https://gist.github.com/nurtext/b6ac07ac7d8c372bc8eb
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} =https
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
<Directory "/var/www/html/">
AllowOverride All
Allow from All
</Directory>
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
# Simple ddev technique to get a phpstatus
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
<VirtualHost *:443>
SSLEngine on
SSLCertificateFile /etc/ssl/certs/master.crt
SSLCertificateKeyFile /etc/ssl/certs/master.key
# Workaround from https://mail-archives.apache.org/mod_mbox/httpd-users/201403.mbox/%3C49404A24C7FAD94BB7B45E86A9305F6214D04652@MSGEXSV21103.ent.wfb.bank.corp%3E
# See also https://gist.github.com/nurtext/b6ac07ac7d8c372bc8eb
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} =https
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
<Directory "/var/www/html/">
AllowOverride All
Allow from All
</Directory>
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
# Simple ddev technique to get a phpstatus
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
<VirtualHost *:80>
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
DocumentRoot /var/www/{{EXTENSION_KEY}}/Documentation-GENERATED-temp
ServerAlias docs.{{DDEV_SITENAME}}.ddev.site
<Directory "/var/www/{{EXTENSION_KEY}}/Documentation-GENERATED-temp/">
AllowOverride All
Allow from All
DirectoryIndex Index.html
</Directory>
ErrorLog /dev/stdout
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
<VirtualHost *:80>
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} =https
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
DocumentRoot /var/www/html/v11/public
ServerAlias v11.{{DDEV_SITENAME}}.ddev.site
<Directory "/var/www/html/v11/">
AllowOverride All
Allow from All
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
<VirtualHost *:80>
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} =https
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
DocumentRoot /var/www/html/v12/public
ServerAlias v12.{{DDEV_SITENAME}}.ddev.site
<Directory "/var/www/html/v12/">
AllowOverride All
Allow from All
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
<VirtualHost *:80>
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} =https
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
DocumentRoot /var/www/html/v13/public
ServerAlias v13.{{DDEV_SITENAME}}.ddev.site
<Directory "/var/www/html/v13/">
AllowOverride All
Allow from All
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet

View File

@@ -0,0 +1,74 @@
#!/bin/bash
## Description: Render TYPO3 extension documentation to HTML
## Usage: docs
## Example: "ddev docs"
set -e
PROJECT_DIR="$(pwd)"
DOC_SOURCE="${PROJECT_DIR}/Documentation"
DOC_OUTPUT="${PROJECT_DIR}/Documentation-GENERATED-temp"
echo "📚 Rendering TYPO3 Extension Documentation"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Check if Documentation directory exists
if [ ! -d "$DOC_SOURCE" ]; then
echo "❌ Error: Documentation directory not found"
echo "💡 Tip: Create a Documentation/ directory with Index.rst to get started"
exit 1
fi
# Check if Index.rst exists
if [ ! -f "$DOC_SOURCE/Index.rst" ]; then
echo "❌ Error: Index.rst not found in Documentation/"
echo "💡 Tip: Create Documentation/Index.rst as the main entry point"
exit 1
fi
echo "📖 Source: Documentation/"
echo "📦 Output: Documentation-GENERATED-temp/"
echo ""
# Clean previous output
if [ -d "$DOC_OUTPUT" ]; then
echo "🧹 Cleaning previous documentation build..."
rm -rf "$DOC_OUTPUT"
fi
# Render documentation using TYPO3's official docker image
echo "🔨 Rendering documentation with TYPO3 render-guides..."
echo ""
docker run --rm \
-v "${PROJECT_DIR}:/project" \
--user "$(id -u):$(id -g)" \
ghcr.io/typo3-documentation/render-guides:latest \
--no-progress \
--output=/project/Documentation-GENERATED-temp \
/project/Documentation 2>&1
if [ $? -eq 0 ]; then
# Check for output files (structure may vary)
if [ -f "$DOC_OUTPUT/Result/project/0.0.0/Index.html" ] || \
[ -f "$DOC_OUTPUT/Index.html" ] || \
[ -d "$DOC_OUTPUT" ]; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Documentation rendered successfully!"
echo ""
echo "🌐 View at: https://docs.$(ddev describe -j | jq -r .raw.name).ddev.site/"
echo "📁 Output: Documentation-GENERATED-temp/"
echo ""
else
echo ""
echo "⚠️ Build completed but output structure unclear"
echo "📁 Check: Documentation-GENERATED-temp/"
fi
else
echo ""
echo "❌ Documentation rendering failed"
echo "💡 Check your .rst files for syntax errors"
exit 1
fi

View File

@@ -0,0 +1,17 @@
#!/bin/bash
## Description: Capture git info for landing page before container build
## Usage: Runs automatically before ddev start
## Example: "ddev start"
# Get git info from host
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
GIT_PR=$(gh pr view --json number 2>/dev/null | jq -r '.number // "unknown"' || echo "unknown")
# Export for docker-compose to use
export DDEV_GIT_BRANCH="$GIT_BRANCH"
export DDEV_GIT_COMMIT="$GIT_COMMIT"
export DDEV_GIT_PR="$GIT_PR"
echo "Git info captured: $GIT_BRANCH @ $GIT_COMMIT (PR: $GIT_PR)"

Some files were not shown because too many files have changed in this diff Show More