From 04b7d39ecd2e836890740703177874e5aeb837b2 Mon Sep 17 00:00:00 2001 From: Johannes Wachter Date: Fri, 4 Sep 2026 17:44:00 +0200 Subject: [PATCH] [FrameworkBundle] Add AGENTS.md for AI coding agents (#1563) * [FrameworkBundle] Add AGENTS.md for AI coding agents * [FrameworkBundle] Point CLAUDE.md at AGENTS.md via @-import * [FrameworkBundle] Add heading to CLAUDE.md * [FrameworkBundle] Apply review feedback to AGENTS.md Split the operational bullets out of Conventions into a new Everyday workflow section, generalise what remains around the best practices page, and replace the stale-training-data note with a concrete list of discovery commands. Keeps three specifics under Conventions: MapRequestPayload over hand-rolled json_decode(), no readonly on a service that may become lazy, and symfony/lock for mutual exclusion. Those are the failure modes the file exists to address, and a general rule does not change a model's behaviour the way naming them does. * [FrameworkBundle] Broaden the attribute and injection guidance Attributes were only shown on controllers. They now cover properties, commands, listeners, message handlers and service declaration. The autowiring bullet also contradicted itself: it sent scalar arguments to an explicit service definition, which is what #[Autowire] exists to avoid. Injection now points at #[Autowire] and #[Target] first, with a YAML definition as the last resort. --- symfony/framework-bundle/8.1/AGENTS.md | 108 +++++++++++++++++++++ symfony/framework-bundle/8.1/CLAUDE.md | 3 + symfony/framework-bundle/8.1/manifest.json | 4 +- 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 symfony/framework-bundle/8.1/AGENTS.md create mode 100644 symfony/framework-bundle/8.1/CLAUDE.md diff --git a/symfony/framework-bundle/8.1/AGENTS.md b/symfony/framework-bundle/8.1/AGENTS.md new file mode 100644 index 00000000..d259e926 --- /dev/null +++ b/symfony/framework-bundle/8.1/AGENTS.md @@ -0,0 +1,108 @@ +# AGENTS.md + +This is a Symfony project. Check `composer.json` for the exact Symfony/PHP version +in use, and read `symfony.lock` to see which recipes ran. Don't assume Doctrine, +Twig, API Platform, Messenger, or Lock are installed unless one of those says so. + +## Ask before generating + +If the task doesn't specify, ask rather than guess: + +- Persistence: Doctrine ORM, Doctrine ODM, or none? +- Interface: server-rendered (Twig), API (Serializer, maybe API Platform), or both? +- Auth: SecurityBundle, and which authenticator? + +If you can't ask (no interactive channel), state the assumption you're making and +pick the smallest option (e.g. no persistence layer) rather than scaffolding a +full stack nobody asked for. + +## Adding features: Flex, not hand-wiring + +Install new capabilities with `composer require ` (e.g. `symfony/lock`, +`symfony/messenger`, `orm-pack`) and let the Flex recipe register the bundle and +generate its config. Don't hand-edit `config/bundles.php` or hand-write a bundle's +base config; that's what the recipe is for. Don't skip a good-fit component just +because it isn't installed yet; installing it is one command. + +## Conventions + +Follow https://symfony.com/doc/current/best_practices.html to write idiomatic +Symfony: + +- Use PHP attributes for framework metadata, and not only on controllers: + `#[Route]`, `#[MapRequestPayload]`, `#[IsGranted]` on actions, `#[Assert\...]` + on properties, `#[AsCommand]`, `#[AsEventListener]`, `#[AsMessageHandler]`, and + `#[AsAlias]` / `#[AsTaggedItem]` / `#[Autoconfigure]` on services. No YAML or + XML routing. +- Rely on autowiring and autoconfiguration. Type-hint constructor arguments and + let the container resolve them. Where a type-hint can't express it, stay in the + class with `#[Autowire]` (parameters, env vars, expressions) or `#[Target]` (one + of several implementations of an interface). A YAML service definition is the + last resort, not the first. +- Controllers extend `AbstractController`, stay thin, and delegate to services. +- Use the framework for what it already does: Form for server-rendered forms, + Validator for validation, Serializer for JSON, Messenger for async work, + Security (voters, authenticators) for access control, Twig `path()`/`url()` + instead of hardcoded URLs. +- Before hand-writing infrastructure (locks, queues, caches, HTTP clients, + mailers, schedulers) or reaching for a third-party library, check whether a + Symfony component covers it. It usually does. + +Three specifics worth spelling out, because they are easy to get wrong: + +- Bind request data with `#[MapRequestPayload]` / `#[MapQueryString]` on action + arguments, which wires up Serializer and Validator for you, instead of calling + `json_decode()` or `SerializerInterface` by hand. If neither package is + installed yet, `composer require` them rather than falling back to manual + parsing. +- Use constructor property promotion, and `readonly` for DTOs and value objects. + Don't mark a service `readonly` if it might become `lazy: true`: a lazy proxy + can't extend a `readonly` class. +- Use `symfony/lock` (`LockFactory`) for mutual exclusion. A hand-built flag or + lock file looks fine in review and is usually wrong under concurrency. + +## Everyday workflow + +- Run the app with `symfony serve -d`, and commands with `symfony console ...` + (or `bin/console` when the Symfony CLI isn't available). +- When something fails, read `var/log/dev.log` and the web profiler + (`/_profiler`) before changing code. +- If `maker-bundle` is installed, prefer `bin/console make:*` with every argument + passed up front and `--no-interaction` where supported: makers prompt on a + terminal by default, which hangs a non-interactive shell. If a maker still + needs interactive input, hand-write the code instead. +- If Doctrine ORM is installed, schema changes go through migrations + (`bin/console make:migration`, then `doctrine:migrations:migrate`), never + `doctrine:schema:update` or hand-written SQL. +- `.env` is committed and holds defaults only. Real secrets belong in `.env.local` + (git-ignored) or the secrets vault (`bin/console secrets:set`), read via + `%env(...)%`. + +## Testing + +Install `symfony/test-pack` if it isn't already. Functional/HTTP tests extend +`WebTestCase`; service-level tests extend `KernelTestCase`. Run +`php bin/phpunit` (falls back to `vendor/bin/phpunit`). A feature isn't done +until it has a test that exercises it the way a caller would, an HTTP request for +a controller or a service call for a service, not just "it didn't throw." + +## Code style + +Symfony's coding standard, the `@Symfony` php-cs-fixer ruleset (a PSR-12-derived +superset). Run `vendor/bin/php-cs-fixer fix` if `friendsofphp/php-cs-fixer` is +installed; it isn't part of the skeleton by default. + +## Discover, don't guess + +Framework APIs change between versions and your training data may be stale. Look +things up in the project instead of relying on memory: + +- `bin/console about`: versions, environment, paths. +- `bin/console debug:router`, `debug:container`, `debug:autowiring `, + `debug:config `, `config:dump-reference `: what exists and how + it is configured. +- `bin/console lint:container`, plus `lint:twig templates/` and + `lint:yaml config/` where those packages are installed: validate before running. +- Read the installed source and docblocks under `vendor/`. +- Docs: https://symfony.com/doc/current/ (switch to the version matching + `composer.json` if it differs). diff --git a/symfony/framework-bundle/8.1/CLAUDE.md b/symfony/framework-bundle/8.1/CLAUDE.md new file mode 100644 index 00000000..f6aa6c02 --- /dev/null +++ b/symfony/framework-bundle/8.1/CLAUDE.md @@ -0,0 +1,3 @@ +# CLAUDE.md + +@AGENTS.md diff --git a/symfony/framework-bundle/8.1/manifest.json b/symfony/framework-bundle/8.1/manifest.json index 227c9405..6d85dcaa 100644 --- a/symfony/framework-bundle/8.1/manifest.json +++ b/symfony/framework-bundle/8.1/manifest.json @@ -6,7 +6,9 @@ "config/": "%CONFIG_DIR%/", "public/": "%PUBLIC_DIR%/", "src/": "%SRC_DIR%/", - ".editorconfig": ".editorconfig" + ".editorconfig": ".editorconfig", + "AGENTS.md": "AGENTS.md", + "CLAUDE.md": "CLAUDE.md" }, "composer-scripts": { "cache:clear": "symfony-cmd",