Add a deterministic script to auto-generate the providers/operation-types matrix from GitLab
## Description The provider/operation-type matrix at `docs/providers/matris.md` is maintained by hand. Today the only way it stays current is a note at the bottom asking contributors to open a pull request when their provider is missing — so it drifts out of date and is incomplete. We should add a **deterministic** script under `scripts/` that discovers every AI provider plugin published on `git.drupalcode.org`, works out which operation types each one supports, and regenerates `docs/providers/matris.md` from that data. Determinism matters so the script can run in CI / on a schedule and produce a reviewable, stable diff (same input → byte-identical output, rows sorted, no run-to-run noise). A provider plugin is any class that extends `OpenAiBasedProviderClientBase`, extends `AiProviderClientBase`, or implements `AiProviderInterface`, and lives at a `Plugin/AiProvider` path. ## Tasks * [x] Add a script under `scripts/` (matching the existing `scripts/*.sh` convention or it can also run php if that is easier that regenerates `docs/providers/matris.md`. * [x] Query the GitLab blobs search API against the `project` group (group id `2`) for all three base types: * `https://git.drupalcode.org/api/v4/groups/2/search?scope=blobs&search=%22extends%20OpenAiBasedProviderClientBase%22` * `https://git.drupalcode.org/api/v4/groups/2/search?scope=blobs&search=%22extends%20AiProviderClientBase%22` * `https://git.drupalcode.org/api/v4/groups/2/search?scope=blobs&search=%22implements%20AiProviderInterface%22` * [x] Page through **all** results for each query (the API is paginated — follow `per_page` / `x-next-page` until exhausted), then merge and de-duplicate hits across the three searches. * [x] Filter results so the file path contains `Plugin/AiProvider` **and** excludes test code. Note that test fixtures also live under `Plugin/AiProvider` (e.g. `tests/modules/.../src/Plugin/AiProvider/FixedResponseProvider.php`), so exclude any path containing a `tests/` (or `Tests/`) segment. Also collapse mirrored checkout paths such as `web/modules/contrib/ai/...` so the same plugin is not counted twice. * [x] For each surviving plugin file, fetch its contents and determine the supported operation types (e.g. parse the `getSupportedOperationTypes()` return / the operation-type definitions the plugin or base classes declares). * [x] For each owning project, fetch project information (project path / drupal.org project machine name and human-readable title) to build the provider name and its `https://www.drupal.org/project/<name>` link. * [x] Render `docs/providers/matris.md`: keep the existing column set and header links, one row per provider, a `☑` in each supported operation-type column, rows sorted deterministically (e.g. alphabetically by provider name). * [x] Make the run reproducible: stable sort order, no timestamps or other volatile content in the output, and a non-zero exit / clear diff if the committed file is stale. * [x] Document how to run the script (and any required token/auth) in the script header or `docs/`. The operation-type columns to populate (current matrix): Chat, Vector Database, Embeddings, Moderation, TextToImage, TextToSpeech, SpeechToText, SpeechToSpeech, AudioToAudio, TranslateText, ImageClassification, ImageToImage, ObjectDetection. ## Acceptance criteria * Running the script regenerates `docs/providers/matris.md` from live GitLab data with no manual editing. * Output is deterministic: running it twice against unchanged data produces a byte-identical file. * Only real provider plugins are included — paths matched on `Plugin/AiProvider`, with all test code and duplicate/mirrored paths excluded. * Each provider row reflects the operation types actually declared in its plugin source, and links to the correct `drupal.org/project/<name>` page. * The script can be run locally and is suitable for CI/scheduled execution to flag a stale matrix.
issue