diff --git a/CLAUDE.md b/CLAUDE.md index 240573c..5d761a2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -191,7 +191,10 @@ cache incluent l'application pour éviter toute pollution croisée (D26). | `ad-service` | **un par (application, type)** (20 types) | `AD_ELEMENT_TYPES` : `View` 200, `Workflow` 5000, `Resource` 15000, autres 100000 | **Ne préchargez jamais les 9 applications** : seule l'application demandée est -chargée (D26). +chargée (D26). `search_workflows` et `search_ad_elements` rappellent toujours +l'application interrogée et, sur résultat **vide**, ajoutent un `hint` nommant +les autres — construit depuis la liste d'applications **déjà en cache**, jamais +par un appel réseau (D26). Les tailles de page par type viennent de l'observation des timeouts serveur — ne les augmentez pas à l'aveugle. diff --git a/DECISIONS.md b/DECISIONS.md index d77ad1b..facfced 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -622,3 +622,26 @@ Règles associées : ne détaille que les entrées **effectivement en cache** : la sortie reste bornée quel que soit le nombre d'applications interrogées (D24). Il expose aussi les caches de workflows par application. + +**Le paramètre ne suffisait pas : il faut que la réponse le dise** (lot 5, +25/08/2026). Cas réel : une session Cowork cherchant des workflows `CST_*` sans +passer `application: "CustomApp"` a conclu que l'AD n'en contenait aucun — alors +que `CST_PickingTasksSequencing_PR` et `CST_ChooseDestinationFromPS` existent. +Le paramètre était disponible et documenté ; ce qui manquait, c'est que **rien +dans la réponse ne disait qu'on n'avait regardé qu'une application sur neuf**. +Un défaut silencieux se lit comme une exhaustivité. + +`search_workflows` et `search_ad_elements` rappellent donc **toujours** +l'application effectivement interrogée (plus seulement quand le paramètre a été +passé), et ajoutent un `hint` quand la recherche revient **vide** : + +- Seuil à **0 résultat**, pas « peu ». Toute valeur non nulle produirait un hint + parasite sur une recherche légitimement étroite, et le mode d'échec observé + est bien le zéro pris pour une absence. +- Le hint nomme les autres applications depuis la liste allégée **déjà en + cache** ; sans elle, il reste générique et renvoie vers + `list_workflow_categories`. **Jamais de fetch pour construire un hint** — + ce serait précisément le préchargement que cette décision interdit. +- Il nomme `CustomApp` en clair, sauf quand c'est déjà l'application + interrogée : c'est une connaissance statique, déjà portée par les + descriptions d'outils, pas une donnée à aller chercher. diff --git a/ROADMAP.md b/ROADMAP.md index 6adfd18..b8202e2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -82,37 +82,6 @@ plus riche que `/AD/api/Application/GetAll`), `GET /healthcheck?tenantCode=` et --- ---- - -## Lot 5 — Clore la famille D24 (rejets client sur sorties volumineuses) - -Mesures du 25/08/2026 (protocole, LIMAGRAIN), toutes au-dessus du seuil de -rejet client (~70 000 caractères, D24) : - -| Appel | Taille | -|---|---:| -| `query_wms_entities("Products", limit: 200)` — Reading ordinaire | **957 234** | -| `search_wms_data("PAL")` | **847 543** | -| `get_workflow_details(CST_SendRejectContainersToPK)` | ~101 800 | -| `call_query_api("Products", query_type: 1, limit: 1)` — **une seule ligne** Writing | **95 288** | - -### L5.4 — Rendre les autres applications découvrables depuis `search_workflows` - -Cas réel (25/08/2026, session Cowork sur LIMAGRAIN) : une session cherchant -des workflows `CST_*` sans passer `application: "CustomApp"` a conclu à tort -« l'AD contient 4 012 workflows, aucun CST_ » — alors que -`CST_PickingTasksSequencing_PR` et `CST_ChooseDestinationFromPS` existent bien -dans CustomApp (vérifié en révision). Le paramètre `application` (D26) existe, -mais rien dans la **réponse** ne signale qu'on n'a regardé qu'une application. - -Ajouter aux réponses de `search_workflows` (et `search_ad_elements`) un rappel -peu coûteux : l'application interrogée, et — quand la recherche renvoie peu ou -pas de résultats — un hint listant les autres applications (la liste allégée -d'`Application/GetAll` est déjà en cache, D26) avec renvoi vers le paramètre -`application`. Aucun préchargement des autres applications (D26). - ---- - ## Écarté | Proposition | Raison | diff --git a/src/services/ad-service.js b/src/services/ad-service.js index 66a843c..8ce9761 100644 --- a/src/services/ad-service.js +++ b/src/services/ad-service.js @@ -275,6 +275,7 @@ function getAvailableTypes() { } module.exports = { + resolveApplication, getElements, searchElements, getElementDetails, diff --git a/src/services/workflow-service.js b/src/services/workflow-service.js index c159371..d981896 100644 --- a/src/services/workflow-service.js +++ b/src/services/workflow-service.js @@ -133,6 +133,49 @@ async function fetchApplications() { return applicationsCache; } +/** + * Liste des applications déjà en cache, ou null si le cache est vide/expiré. + * Ne déclenche AUCUN appel réseau — c'est ce qui permet d'enrichir une réponse + * de recherche sans jamais précharger une application non demandée (D26). + * @returns {Array<{name: string, id: string, version: number}>|null} + */ +function getCachedApplications() { + if (applicationsCache && applicationsTimestamp && + Date.now() - applicationsTimestamp < CACHE_TTL) { + return applicationsCache; + } + return null; +} + +/** + * Hint de découvrabilité (L5.4). Une recherche n'interroge qu'UNE application + * sur les neuf déclarées, et rien dans la réponse ne le disait : une session + * cherchant des workflows `CST_*` sans `application: "CustomApp"` a conclu à + * tort qu'il n'y en avait aucun (25/08/2026). + * + * Les autres applications sont nommées depuis la liste allégée **déjà en + * cache** ; sans elle, le hint reste générique et renvoie vers + * `list_workflow_categories` — jamais de fetch pour construire un hint. + * + * @param {string} application - application effectivement interrogée + * @param {string} sujet - ce qui a été cherché ('workflow', 'élément Command'…) + */ +function buildOtherApplicationsHint(application, sujet) { + const cached = getCachedApplications(); + const others = (cached || []).map(a => a.name).filter(n => n !== application); + + const liste = others.length + ? `Autres applications déclarées sur ce tenant : ${others.join(', ')}.` + : `Appelez list_workflow_categories pour lister les autres applications déclarées.`; + + const custom = application.toLowerCase() === 'customapp' + ? '' + : ` Le spécifique client (préfixe CST_) vit dans "CustomApp" : relancez avec application: "CustomApp".`; + + return `Aucun ${sujet} trouvé dans l'application "${application}" — c'est la SEULE interrogée, ` + + `les autres ne le sont jamais implicitement.${custom} ${liste}`; +} + /** * Search workflows by query string. * Real AD keys (lowercase, cf. D5): id, name, version, applicationName, @@ -251,6 +294,9 @@ function getCacheStatus() { module.exports = { fetchAllWorkflows, fetchApplications, + getCachedApplications, + buildOtherApplicationsHint, + resolveApplication, searchWorkflows, getWorkflowDetails, getWorkflowStats, diff --git a/src/tools/ad-tools.js b/src/tools/ad-tools.js index fa300a2..0916224 100644 --- a/src/tools/ad-tools.js +++ b/src/tools/ad-tools.js @@ -247,15 +247,25 @@ async function searchADElementsTool(args) { code: e.code || e.Code })); + // L5.4 : même correctif que search_workflows — l'application interrogée est + // toujours rappelée, et un résultat vide signale que les huit autres n'ont + // pas été regardées. Le hint se construit depuis la liste d'applications + // DÉJÀ en cache : aucun appel réseau, aucun préchargement (D26). + const effectiveApplication = adService.resolveApplication(application); + const hint = mappedResults.length === 0 + ? workflowService.buildOtherApplicationsHint(effectiveApplication, `élément ${element_type}`) + : null; + return { content: [{ type: 'text', text: JSON.stringify({ success: true, elementType: element_type, - ...(application ? { application } : {}), + application: effectiveApplication, query, count: mappedResults.length, + ...(hint ? { hint } : {}), elements: mappedResults }, null, 2) }] diff --git a/src/tools/workflow-tools.js b/src/tools/workflow-tools.js index 7ad3e66..47bacf4 100644 --- a/src/tools/workflow-tools.js +++ b/src/tools/workflow-tools.js @@ -134,13 +134,23 @@ async function searchWorkflows(args) { const results = await workflowService.searchWorkflows(query, category, limit, application); + // L5.4 : l'application interrogée est TOUJOURS rappelée (pas seulement quand + // elle a été passée), et un résultat vide dit qu'une seule application sur + // neuf a été regardée — c'est ce silence qui avait fait conclure à tort à + // l'absence de workflows CST_. + const effectiveApplication = workflowService.resolveApplication(application); + const hint = results.length === 0 + ? workflowService.buildOtherApplicationsHint(effectiveApplication, 'workflow') + : null; + return { content: [{ type: 'text', text: JSON.stringify({ success: true, - ...(application ? { application } : {}), + application: effectiveApplication, count: results.length, + ...(hint ? { hint } : {}), // Clés réelles de l'API AD (minuscules, cf. D5) : id, name, version, // applicationName, commonInfo. Pas de code/category/description. workflows: results.map(w => {