Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/mate/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
CHANGELOG
=========

Expand All @@ -20,6 +20,8 @@
* Allow Symfony profiler capabilities (`ProfilerResourceTemplate` and `ProfilerTool`) to be instantiated without a `ProfilerDataProvider`, throwing a clear `RuntimeException` when invoked in workspaces without profiler support
* Add `--ignore-missing-file` option to the `discover` command that exits successfully without doing any work when `mate/extensions.php` does not exist (intended for unconditional invocation from Composer scripts wired by the Symfony Flex recipe)
* Make `json-input` argument optional in `mcp:tools:call` command (defaults to `{}`)
* Add Agent Skills (`SKILL.md`) to the Symfony bridge — `symfony-profiler-debugging` and `symfony-container-introspection` — colocated with the tools they orchestrate, anticipating the MCP Skills extension (SEP-2640)
* Restructure the Symfony bridge `INSTRUCTIONS.md` into a thin tool overview that points to the new skills for the how-to

0.7
---
Expand Down
37 changes: 18 additions & 19 deletions src/mate/src/Bridge/Symfony/INSTRUCTIONS.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
## Symfony Bridge

### Container Introspection
Prefer these MCP tools over running `bin/console` directly: they read the compiled
container and stored profiles, are environment-aware, and redact sensitive data.

| Instead of... | Use |
|--------------------------------|--------------------|
| `bin/console debug:container` | `symfony-services` |
| Tool / resource | Use for |
|---|---|
| `symfony-services`, `symfony-service-detail` | Container introspection (replaces `debug:container`). |
| `symfony-profiler-list`, `symfony-profiler-get` | Finding and resolving profiler profiles. |
| `symfony-profiler://profile/{token}` | Profile triage: metadata + available collectors. |
| `symfony-profiler://profile/{token}/{collector}` | One collector's data (`db`, `time`, `exception`, …). |

- Direct access to compiled container
- Environment-aware (auto-detects dev/test/prod)
- Supports filtering by service ID or class name via query parameter
Profiler tools require `symfony/http-kernel`. Cookies, session data, auth headers,
and sensitive env vars are redacted automatically.

### Profiler Access
### Skills — read these for the *how*, before using the tools

When `symfony/http-kernel` is installed, profiler tools become available:
The tables above say *what* exists; the skills below say *how to orchestrate* them.
When a task matches one, read its `SKILL.md` first and follow it — it will tell you
which tool and which collector to reach for, in order.

| Tool | Description |
|-----------------------------|---------------------------------------------------------|
| `symfony-profiler-list` | List and filter profiles by method, URL, IP, status, date range |
| `symfony-profiler-get` | Get profile by token |

**Resources:**
- `symfony-profiler://profile/{token}` - Full profile with collector list
- `symfony-profiler://profile/{token}/{collector}` - Collector-specific data

**Security:** Cookies, session data, auth headers, and sensitive env vars are automatically redacted.
| Skill | Read when |
|---|---|
| [`skill://symfony-profiler-debugging/SKILL.md`](skills/symfony-profiler-debugging/SKILL.md) | A request is slow, 500s, has an N+1, or behaves unexpectedly — debugging via the profiler. |
| [`skill://symfony-container-introspection/SKILL.md`](skills/symfony-container-introspection/SKILL.md) | A `ServiceNotFoundException`/autowiring failure, or any "is X registered / how is it built / what carries tag Y" question. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
name: symfony-container-introspection
description: >-
Inspect a Symfony dependency-injection container through Mate's service tools.
Use when the user hits a ServiceNotFoundException or autowiring failure, asks
whether a service is registered or what class an id resolves to, wants every
service carrying a given DI tag (event listeners, Twig extensions, …), or needs
to know how a service is constructed (factory, tags, method calls).
---

# Inspecting the Symfony service container

This skill turns Mate's container capabilities into a search → detail workflow. It
reads the compiled `*DebugContainer.xml` and auto-detects the environment
(dev/test/prod), so it reflects the *real* compiled container, not source
annotations.

## Capabilities you will use

| Capability | Type | Purpose |
|---|---|---|
| `symfony-services` | tool | Search/filter the container; returns a map of service id → class. |
| `symfony-service-detail` | tool | Full definition of one service by exact id: class, tags, calls, factory. |

## Workflow

Follow **search → detail**: cast a filter to find the right id, then open that one
id for the full definition. Do not ask for detail before you have an exact id.

### 1. Search

Call `symfony-services` with the narrowest filter that fits the question:

- **By id or class** — `query` does a case-insensitive partial match against both
the service id and its class. `query=mailer`, `query=App\\Service\\Invoice`.
- **By tag** — `tag` returns every service carrying a DI tag, e.g.
`tag=kernel.event_listener`, `tag=twig.extension`,
`tag=monolog.logger`.

The result is a triage map (`id => class`). An **empty result is itself the
answer** to "is this registered?" — it is not.

### 2. Detail

Once you have an **exact** id, call `symfony-service-detail` with it to get:

- `class` — the concrete class behind the id;
- `tags` — each tag with its attributes (priority, event, alias, …);
- `calls` — setter/method calls applied after instantiation;
- `factory` — present only when the service is built via a factory
(`Class::method`) rather than a plain constructor.

## Symptom → move

| Symptom / question | Move |
|---|---|
| `ServiceNotFoundException: "app.foo"` | `symfony-services query=foo` — find the real id or confirm it is missing. |
| "Autowiring can't find an argument of type `X`" | `symfony-services query=X` — is the class registered, and under which id/alias? |
| "What is `mailer` actually?" | `symfony-service-detail id=mailer`. |
| "Which listeners run on `kernel.request`?" | `symfony-services tag=kernel.event_listener`, then detail the candidates and read their tag `event`/`priority`. |
| "Why isn't my decorator/factory used?" | `symfony-service-detail` on the id — check `factory` and `calls`. |
| Error happens *during a request* | This is a profiler case, not a container one — use the
[symfony-profiler-debugging](../symfony-profiler-debugging/SKILL.md) skill. |

## Rules

- **Filter before you fetch.** `symfony-services` with no filter can return a very
large map; always pass `query` or `tag` unless the user truly wants everything.
- **Match the exact id for detail.** `symfony-service-detail` needs the precise id
from the search step — partial ids fail.
- **Report the resolution, not the dump.** Answer with the id/class/tag that
resolves the question (e.g. "`app.invoice_mailer` → `App\\Mailer\\InvoiceMailer`,
tagged `kernel.event_listener` on `order.placed`"), not the whole definition.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
name: symfony-profiler-debugging
description: >-
Diagnose a slow, failing, or misbehaving Symfony HTTP request by orchestrating
Mate's Symfony profiler tools. Use when the user reports a 500 error, a slow
endpoint, an N+1 query problem, an unexpected response, or asks "what happened
on the last request". Walks from locating the right profile to reading only the
collector that explains the symptom.
---

# Debugging a Symfony request with the profiler

This skill turns Mate's Symfony profiler capabilities into a repeatable triage
workflow. It assumes the `symfony/http-kernel` profiler is enabled (dev/test) and
that the project has been served with `bin/mate serve`.

## Capabilities you will use

| Capability | Type | Purpose |
|---|---|---|
| `symfony-profiler-list` | tool | Find profiles by method, URL, IP, status code, date range, or context. |
| `symfony-profiler-get` | tool | Resolve one token to its metadata and the list of available collectors. |
| `symfony-profiler://profile/{token}` | resource | Triage view: request metadata + which collectors exist. |
| `symfony-profiler://profile/{token}/{collector}` | resource | Detail view: the data for a single collector. |
| `symfony-services` | tool | Container introspection, for dependency-injection / "service not found" cases. |

## Workflow

Follow the **triage → detail** pattern. Do not read every collector — read the one
that matches the symptom.

### 1. Locate the profile

- "the last request" → `symfony-profiler-list` with `limit=1`.
- A specific failure → filter: `symfony-profiler-list` with `statusCode=500`, or
`url=/checkout`, or `method=POST`, narrowing with `from`/`to` when the user gives
a time window.

Each result carries a `resource_uri`. Prefer it over guessing token strings.

### 2. Triage

Read `symfony-profiler://profile/{token}` (or call `symfony-profiler-get`). This is
the cheap overview: HTTP method, URL, status code, total time, and the list of
collectors that actually have data for this request. Use it to decide *which*
collector to open next — not to dump everything.

### 3. Read only the collector that explains the symptom

Map the symptom to a collector, then read
`symfony-profiler://profile/{token}/{collector}`:

| Symptom | Collector | What to look for |
|---|---|---|
| 500 / uncaught error | `exception` | Exception class, message, the failing frame. |
| Slow response | `time` | Total + per-event durations; find the dominant span. |
| Slow response with a DB cause | `db` | Query count and duration; repeated near-identical queries = N+1. |
| Emails not sent / wrong recipient | `mailer` | Queued vs. sent messages, envelope. |
| Missing / wrong translations | `translation` | Defined, missing, and fallback messages. |
| Unexpected output or routing | `request` | Controller, route, request/response attributes. |
| Memory pressure | `memory` | Peak usage for the request. |
| Something logged during the request | `logger` | Log entries captured for this request. |

See [references/collectors.md](references/collectors.md) for the per-collector
field guide and how to read each one.

### 4. Service / dependency-injection problems

If the exception is a `ServiceNotFoundException`, an autowiring failure, or the
user asks "is service X registered / what is it aliased to", switch to
`symfony-services` and filter by the service id or class instead of the profiler.

### 5. Correlate with logs (optional)

When the profiler shows an error but not the root cause, and the Monolog bridge is
installed, cross-reference with `monolog-log-search` around the request's
timestamp.

## Rules

- **One collector at a time.** The triage view tells you which one is worth the
context. Reading all of them defeats the purpose.
- **Trust the redaction.** Cookies, session data, auth headers, and sensitive env
vars are already redacted by the bridge — do not try to route around that to
recover secrets.
- **Report the diagnosis, not the dump.** End with the specific cause (e.g. "N+1:
42 identical `SELECT … FROM product` queries from the cart renderer") and the
fix, not a paste of the collector payload.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Profiler collector field guide

Reference for step 3 of the [debugging workflow](../SKILL.md). Each section covers
one collector reachable at `symfony-profiler://profile/{token}/{collector}`. Only
collectors with data for a given request appear in that request's triage view, so
confirm availability there first.

## `exception`

The first stop for any 5xx. Look at:

- the exception **class** and **message** — usually names the failure directly;
- the **trace head** — the frame inside application code (skip vendor frames) is
where to look in the source;
- a `previous` exception, if present — the real cause is often the innermost one.

## `time`

For "this is slow". The collector reports the total request duration and a set of
named events with durations.

- Find the **single dominant span** before optimizing anything — most requests are
slow because of one thing, not many.
- A large gap between total time and the sum of named events points at work that
isn't instrumented (often I/O or an external call).

## `db`

For slow requests with a database cause, when Doctrine is present.

- **Query count** is the headline. A page issuing hundreds of queries is almost
always an **N+1**: many near-identical statements differing only by a bound id.
- High duration on a single query points at a missing index or an unbounded result
set instead.
- Report the offending query shape and the call site, not every row.

## `request`

For "wrong output" / routing questions.

- `controller` and `route` confirm the request reached the handler you expect.
- request/response attributes and headers explain content negotiation and
redirects.
- Payload values may be redacted — diagnose from shape and keys, not secrets.

## `mailer`

- Distinguish **queued** from **sent** messages.
- Check the **envelope** (from/to) when delivery goes to the wrong place.

## `translation`

- `missing` and `fallback` buckets explain untranslated or wrong-locale strings.
- The defined-message count confirms the catalogue loaded at all.

## `logger`

- Log entries captured during this request, in order.
- Useful to confirm whether an error was caught-and-logged versus thrown; for
broader history use the Monolog bridge's `monolog-log-search` instead.

## `memory`

- Peak memory for the request. Relevant for OOM and for batch/export endpoints;
rarely the first thing to read for a normal page.
Loading