Compare commits

..

4 Commits

Author SHA1 Message Date
Matthieu 73ebd6902d chore(release) : bump version to 1.9.3
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 09:17:39 +01:00
Matthieu ded1f7a8b6 chore(submodule) : update frontend pointer (comment attachments + fixes)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 09:10:41 +01:00
Matthieu 3b35598b07 fix(structure) : stabilize piece/component/product ordering in machines
All findBy(['machine' => ...]) queries now sort by createdAt ASC.
Without explicit ORDER BY, PostgreSQL returned rows in heap order which
changed on every INSERT, causing the displayed order to shuffle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 09:02:36 +01:00
Matthieu 06ce9fb1f2 chore(config) : update reference.php + remove disabled config files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 08:49:59 +01:00
8 changed files with 57 additions and 50 deletions
+1 -1
View File
@@ -1 +1 @@
1.9.1 1.9.3
-20
View File
@@ -1,20 +0,0 @@
mcp:
app: 'inventory'
version: '1.0.0'
description: 'Inventory MCP Server - Gestion inventaire industriel (machines, pièces, composants, produits)'
instructions: |
Serveur MCP pour gérer un inventaire industriel.
Entités principales : Machine, Composant, Pièce, Produit, Site, Constructeur.
Utilisez search_inventory pour chercher dans toutes les entités.
Utilisez get_model_type pour comprendre la structure attendue avant de créer un composant ou une pièce.
Consultez la resource inventory://schema/entities pour voir le schéma complet.
Authentification requise : envoyez X-Profile-Id et X-Profile-Password dans les headers HTTP.
client_transports:
stdio: true
http: true
http:
path: /_mcp
session:
store: file
directory: '%kernel.cache_dir%/mcp-sessions'
ttl: 3600
@@ -1,6 +0,0 @@
framework:
rate_limiter:
mcp_auth:
policy: sliding_window
limit: 5
interval: '1 minute'
+37 -4
View File
@@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
// This file is auto-generated and is for apps only. Bundles SHOULD NOT rely on its content. // This file is auto-generated and is for apps only. Bundles SHOULD NOT rely on its content.
namespace Symfony\Component\DependencyInjection\Loader\Configurator; namespace Symfony\Component\DependencyInjection\Loader\Configurator;
@@ -624,7 +622,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* }>, * }>,
* }, * },
* rate_limiter?: bool|array{ // Rate limiter configuration * rate_limiter?: bool|array{ // Rate limiter configuration
* enabled?: bool|Param, // Default: false * enabled?: bool|Param, // Default: true
* limiters?: array<string, array{ // Default: [] * limiters?: array<string, array{ // Default: []
* lock_factory?: scalar|null|Param, // The service ID of the lock factory used by this limiter (or null to disable locking). // Default: "auto" * lock_factory?: scalar|null|Param, // The service ID of the lock factory used by this limiter (or null to disable locking). // Default: "auto"
* cache_pool?: scalar|null|Param, // The cache pool to use for storing the current limiter state. // Default: "cache.rate_limiter" * cache_pool?: scalar|null|Param, // The cache pool to use for storing the current limiter state. // Default: "cache.rate_limiter"
@@ -1387,7 +1385,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* mercure?: bool|array{ * mercure?: bool|array{
* enabled?: bool|Param, // Default: false * enabled?: bool|Param, // Default: false
* hub_url?: scalar|null|Param, // The URL sent in the Link HTTP header. If not set, will default to the URL for MercureBundle's default hub. // Default: null * hub_url?: scalar|null|Param, // The URL sent in the Link HTTP header. If not set, will default to the URL for MercureBundle's default hub. // Default: null
* include_type?: bool|Param, // Always include @var in updates (including delete ones). // Default: false * include_type?: bool|Param, // Always include @type in updates (including delete ones). // Default: false
* }, * },
* messenger?: bool|array{ * messenger?: bool|array{
* enabled?: bool|Param, // Default: false * enabled?: bool|Param, // Default: false
@@ -1614,6 +1612,37 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* enable_static_query_cache?: bool|Param, // Default: true * enable_static_query_cache?: bool|Param, // Default: true
* connection_keys?: list<mixed>, * connection_keys?: list<mixed>,
* } * }
* @psalm-type McpConfig = array{
* app?: scalar|null|Param, // Default: "app"
* version?: scalar|null|Param, // Default: "0.0.1"
* description?: scalar|null|Param, // Default: null
* icons?: list<array{ // Default: []
* src: scalar|null|Param,
* mime_type?: scalar|null|Param, // Default: null
* sizes?: list<scalar|null|Param>,
* }>,
* website_url?: scalar|null|Param, // Default: null
* pagination_limit?: int|Param, // Default: 50
* instructions?: scalar|null|Param, // Default: null
* client_transports?: array{
* stdio?: bool|Param, // Default: false
* http?: bool|Param, // Default: false
* },
* discovery?: array{
* scan_dirs?: list<scalar|null|Param>,
* exclude_dirs?: list<scalar|null|Param>,
* },
* http?: array{
* path?: scalar|null|Param, // Default: "/_mcp"
* session?: array{
* store?: "file"|"memory"|"cache"|Param, // Default: "file"
* directory?: scalar|null|Param, // Default: "%kernel.cache_dir%/mcp-sessions"
* cache_pool?: scalar|null|Param, // Default: "cache.mcp.sessions"
* prefix?: scalar|null|Param, // Default: "mcp-"
* ttl?: int|Param, // Default: 3600
* },
* },
* }
* @psalm-type ConfigType = array{ * @psalm-type ConfigType = array{
* imports?: ImportsConfig, * imports?: ImportsConfig,
* parameters?: ParametersConfig, * parameters?: ParametersConfig,
@@ -1626,6 +1655,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* nelmio_cors?: NelmioCorsConfig, * nelmio_cors?: NelmioCorsConfig,
* api_platform?: ApiPlatformConfig, * api_platform?: ApiPlatformConfig,
* lexik_jwt_authentication?: LexikJwtAuthenticationConfig, * lexik_jwt_authentication?: LexikJwtAuthenticationConfig,
* mcp?: McpConfig,
* "when@dev"?: array{ * "when@dev"?: array{
* imports?: ImportsConfig, * imports?: ImportsConfig,
* parameters?: ParametersConfig, * parameters?: ParametersConfig,
@@ -1638,6 +1668,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* nelmio_cors?: NelmioCorsConfig, * nelmio_cors?: NelmioCorsConfig,
* api_platform?: ApiPlatformConfig, * api_platform?: ApiPlatformConfig,
* lexik_jwt_authentication?: LexikJwtAuthenticationConfig, * lexik_jwt_authentication?: LexikJwtAuthenticationConfig,
* mcp?: McpConfig,
* }, * },
* "when@prod"?: array{ * "when@prod"?: array{
* imports?: ImportsConfig, * imports?: ImportsConfig,
@@ -1651,6 +1682,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* nelmio_cors?: NelmioCorsConfig, * nelmio_cors?: NelmioCorsConfig,
* api_platform?: ApiPlatformConfig, * api_platform?: ApiPlatformConfig,
* lexik_jwt_authentication?: LexikJwtAuthenticationConfig, * lexik_jwt_authentication?: LexikJwtAuthenticationConfig,
* mcp?: McpConfig,
* }, * },
* "when@test"?: array{ * "when@test"?: array{
* imports?: ImportsConfig, * imports?: ImportsConfig,
@@ -1665,6 +1697,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* api_platform?: ApiPlatformConfig, * api_platform?: ApiPlatformConfig,
* lexik_jwt_authentication?: LexikJwtAuthenticationConfig, * lexik_jwt_authentication?: LexikJwtAuthenticationConfig,
* dama_doctrine_test?: DamaDoctrineTestConfig, * dama_doctrine_test?: DamaDoctrineTestConfig,
* mcp?: McpConfig,
* }, * },
* ...<string, ExtensionType|array{ // extra keys must follow the when@%env% pattern or match an extension alias * ...<string, ExtensionType|array{ // extra keys must follow the when@%env% pattern or match an extension alias
* imports?: ImportsConfig, * imports?: ImportsConfig,
+12 -12
View File
@@ -53,9 +53,9 @@ class MachineStructureController extends AbstractController
return $this->json(['success' => false, 'error' => 'Machine not found.'], 404); return $this->json(['success' => false, 'error' => 'Machine not found.'], 404);
} }
$componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $machine]); $componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']);
$pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $machine]); $pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']);
$productLinks = $this->machineProductLinkRepository->findBy(['machine' => $machine]); $productLinks = $this->machineProductLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']);
return $this->json($this->normalizeStructureResponse( return $this->json($this->normalizeStructureResponse(
$machine, $machine,
@@ -159,9 +159,9 @@ class MachineStructureController extends AbstractController
$this->entityManager->flush(); $this->entityManager->flush();
$componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $newMachine]); $componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $newMachine], ['createdAt' => 'ASC']);
$pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $newMachine]); $pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $newMachine], ['createdAt' => 'ASC']);
$productLinks = $this->machineProductLinkRepository->findBy(['machine' => $newMachine]); $productLinks = $this->machineProductLinkRepository->findBy(['machine' => $newMachine], ['createdAt' => 'ASC']);
return $this->json($this->normalizeStructureResponse( return $this->json($this->normalizeStructureResponse(
$newMachine, $newMachine,
@@ -209,7 +209,7 @@ class MachineStructureController extends AbstractController
*/ */
private function cloneComponentLinks(Machine $source, Machine $target): array private function cloneComponentLinks(Machine $source, Machine $target): array
{ {
$sourceLinks = $this->machineComponentLinkRepository->findBy(['machine' => $source]); $sourceLinks = $this->machineComponentLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']);
$linkMap = []; $linkMap = [];
// First pass: create all links without parent relationships // First pass: create all links without parent relationships
@@ -242,7 +242,7 @@ class MachineStructureController extends AbstractController
*/ */
private function clonePieceLinks(Machine $source, Machine $target, array $componentLinkMap): array private function clonePieceLinks(Machine $source, Machine $target, array $componentLinkMap): array
{ {
$sourceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $source]); $sourceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']);
$linkMap = []; $linkMap = [];
foreach ($sourceLinks as $link) { foreach ($sourceLinks as $link) {
@@ -276,7 +276,7 @@ class MachineStructureController extends AbstractController
array $componentLinkMap, array $componentLinkMap,
array $pieceLinkMap, array $pieceLinkMap,
): void { ): void {
$sourceLinks = $this->machineProductLinkRepository->findBy(['machine' => $source]); $sourceLinks = $this->machineProductLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']);
$linkMap = []; $linkMap = [];
// First pass: create all links // First pass: create all links
@@ -319,7 +319,7 @@ class MachineStructureController extends AbstractController
private function applyComponentLinks(Machine $machine, array $payload): array|JsonResponse private function applyComponentLinks(Machine $machine, array $payload): array|JsonResponse
{ {
$existing = $this->indexLinksById($this->machineComponentLinkRepository->findBy(['machine' => $machine])); $existing = $this->indexLinksById($this->machineComponentLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']));
$keepIds = []; $keepIds = [];
$pendingParents = []; $pendingParents = [];
$links = []; $links = [];
@@ -376,7 +376,7 @@ class MachineStructureController extends AbstractController
private function applyPieceLinks(Machine $machine, array $payload, array $componentLinks): array|JsonResponse private function applyPieceLinks(Machine $machine, array $payload, array $componentLinks): array|JsonResponse
{ {
$existing = $this->indexLinksById($this->machinePieceLinkRepository->findBy(['machine' => $machine])); $existing = $this->indexLinksById($this->machinePieceLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']));
$componentIndex = $this->indexLinksById($componentLinks); $componentIndex = $this->indexLinksById($componentLinks);
$keepIds = []; $keepIds = [];
$pendingParents = []; $pendingParents = [];
@@ -443,7 +443,7 @@ class MachineStructureController extends AbstractController
array $componentLinks, array $componentLinks,
array $pieceLinks, array $pieceLinks,
): array|JsonResponse { ): array|JsonResponse {
$existing = $this->indexLinksById($this->machineProductLinkRepository->findBy(['machine' => $machine])); $existing = $this->indexLinksById($this->machineProductLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']));
$componentIndex = $this->indexLinksById($componentLinks); $componentIndex = $this->indexLinksById($componentLinks);
$pieceIndex = $this->indexLinksById($pieceLinks); $pieceIndex = $this->indexLinksById($pieceLinks);
$keepIds = []; $keepIds = [];
+3 -3
View File
@@ -123,7 +123,7 @@ class CloneMachineTool
*/ */
private function cloneComponentLinks(Machine $source, Machine $target): array private function cloneComponentLinks(Machine $source, Machine $target): array
{ {
$sourceLinks = $this->machineComponentLinkRepository->findBy(['machine' => $source]); $sourceLinks = $this->machineComponentLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']);
$linkMap = []; $linkMap = [];
// First pass: create all links without parent relationships // First pass: create all links without parent relationships
@@ -156,7 +156,7 @@ class CloneMachineTool
*/ */
private function clonePieceLinks(Machine $source, Machine $target, array $componentLinkMap): array private function clonePieceLinks(Machine $source, Machine $target, array $componentLinkMap): array
{ {
$sourceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $source]); $sourceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']);
$linkMap = []; $linkMap = [];
foreach ($sourceLinks as $link) { foreach ($sourceLinks as $link) {
@@ -190,7 +190,7 @@ class CloneMachineTool
array $componentLinkMap, array $componentLinkMap,
array $pieceLinkMap, array $pieceLinkMap,
): void { ): void {
$sourceLinks = $this->machineProductLinkRepository->findBy(['machine' => $source]); $sourceLinks = $this->machineProductLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']);
$linkMap = []; $linkMap = [];
// First pass: create all links // First pass: create all links
@@ -46,9 +46,9 @@ class MachineStructureTool
$this->mcpError('not_found', "Machine not found: {$machineId}"); $this->mcpError('not_found', "Machine not found: {$machineId}");
} }
$componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $machine]); $componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']);
$pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $machine]); $pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']);
$productLinks = $this->machineProductLinkRepository->findBy(['machine' => $machine]); $productLinks = $this->machineProductLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']);
return $this->jsonResponse($this->normalizeStructureResponse( return $this->jsonResponse($this->normalizeStructureResponse(
$machine, $machine,