eqs check déclare une tâche knip que personne n'installe : le détecteur de code mort est éteint dans tous les satellites #51

Closed
opened 2026-09-10 13:14:19 +00:00 by nicolas-fryder · 2 comments

eqs check annonce une tâche knip, @eqs/tooling publie un knip.json et son README la documente — mais knip n'est une dépendance de personne. La tâche est donc systématiquement sautée, et le contrôle n'a jamais tourné dans aucun satellite.

Reproduction

Un clone neuf du gabarit, pnpm install, puis :

pnpm check

Sortie, au milieu des tâches vertes :

[check] tâche « knip » ignorée : <racine>\node_modules\knip\bin\knip.js absent — l'outil n'est pas installé

Où le maillon manque

  • node_modules/@eqs/tooling/knip.json est bien publié — la configuration existe.
  • Le README de @eqs/tooling documente la tâche : « knip, stryker — bases ; le dépôt fixe entry et son cliquet break ».
  • Mais @eqs/tooling ne déclare knip ni en dependencies, ni en peerDependencies, ni en peerDependenciesMeta :
deps:      eslint-config-prettier, globals, typescript-eslint
peer:      @next/eslint-plugin-next, eslint, prettier, typescript
peerMeta:  { "@next/eslint-plugin-next": { "optional": true } }
  • Et le package.json du gabarit (Infrastructure/eqs-satellite-template@main, 8e70251) ne l'installe pas non plus. Ses devDependencies sont : @eqs/cli, @eqs/testing, @eqs/tooling, eslint, orval, prettier, typescript.

Donc aucun satellite né du gabarit n'exécute knip, et rien ne le signale autrement que par cette ligne au milieu d'une sortie verte.

Ce que ça coûte

Le message est honnête — il dit exactement ce qui manque — et la porte reste fermée sur le reste. Ce n'est donc pas un faux vert au sens de #44. Mais le résultat pratique est le même : un contrôle annoncé qui ne contrôle rien, et personne ne s'en aperçoit parce que pnpm check finit vert.

Le défaut n'est pas théorique : j'en ai une occurrence. Dans Pilotage, GrapheRepository.chargerVoisinage n'est appelée par aucun code de production depuis qu'une vue a changé de méthode de chargement — seuls trois tests d'intégration l'exercent encore. Trois tests sur soixante-dix-neuf couvrent donc du code mort, et c'est une revue humaine qui l'a trouvé, pas l'outillage. C'est précisément ce que knip attrape.

Non bloquant, et sans contournement posé de notre côté : nous retirons le code mort à la main.

Ce que je proposerais

Trois voies, par ordre de préférence :

  1. knip en peerDependencies de @eqs/tooling, avec peerDependenciesMeta.optional: true, et ajouté aux devDependencies du gabarit. Un satellite qui ne le veut pas peut le retirer, et il sait alors ce qu'il perd.
  2. knip en dependencies de @eqs/tooling. Plus simple, mais impose l'outil et son arbre à tous les satellites, y compris ceux qui ne le joueront jamais.
  3. Si l'intention est bien que knip soit facultatif, alors le dire ailleurs que dans une ligne de sortie : une mention dans eqs doctor, ou un compte des tâches sautées à la fin de pnpm check (« 6 tâches vertes, 1 sautée »). Aujourd'hui la ligne se perd, et rien dans le résumé final ne rappelle qu'un contrôle manque.

Le même raisonnement vaudrait pour stryker, cité au même endroit du README — je ne l'ai pas vérifié.

Versions

  • @eqs/tooling, @eqs/cli : 0.8.1
  • eqs --version : 0.8.1
  • packageManager : pnpm@11.8.0, Node v24.15.0, Windows 11 Pro 26200

Trouvé en développant le premier satellite réel (Pilotage), lors de la revue finale d'un lot.

`eqs check` annonce une tâche `knip`, `@eqs/tooling` publie un `knip.json` et son README la documente — mais `knip` n'est une dépendance de personne. La tâche est donc systématiquement sautée, et le contrôle n'a jamais tourné dans aucun satellite. ## Reproduction Un clone neuf du gabarit, `pnpm install`, puis : ``` pnpm check ``` Sortie, au milieu des tâches vertes : ``` [check] tâche « knip » ignorée : <racine>\node_modules\knip\bin\knip.js absent — l'outil n'est pas installé ``` ## Où le maillon manque - `node_modules/@eqs/tooling/knip.json` est bien **publié** — la configuration existe. - Le README de `@eqs/tooling` documente la tâche : « `knip`, `stryker` — bases ; le dépôt fixe `entry` et son cliquet `break` ». - Mais `@eqs/tooling` ne déclare `knip` ni en `dependencies`, ni en `peerDependencies`, ni en `peerDependenciesMeta` : ``` deps: eslint-config-prettier, globals, typescript-eslint peer: @next/eslint-plugin-next, eslint, prettier, typescript peerMeta: { "@next/eslint-plugin-next": { "optional": true } } ``` - Et le `package.json` du gabarit (`Infrastructure/eqs-satellite-template@main`, `8e70251`) ne l'installe pas non plus. Ses `devDependencies` sont : `@eqs/cli`, `@eqs/testing`, `@eqs/tooling`, `eslint`, `orval`, `prettier`, `typescript`. Donc **aucun satellite né du gabarit n'exécute knip**, et rien ne le signale autrement que par cette ligne au milieu d'une sortie verte. ## Ce que ça coûte Le message est honnête — il dit exactement ce qui manque — et la porte reste fermée sur le reste. Ce n'est donc pas un faux vert au sens de [#44](https://git.equilibre-structures.fr/Infrastructure/eqs-platform/issues/44). Mais le résultat pratique est le même : un contrôle annoncé qui ne contrôle rien, et personne ne s'en aperçoit parce que `pnpm check` finit vert. **Le défaut n'est pas théorique : j'en ai une occurrence.** Dans Pilotage, `GrapheRepository.chargerVoisinage` n'est appelée par aucun code de production depuis qu'une vue a changé de méthode de chargement — seuls trois tests d'intégration l'exercent encore. Trois tests sur soixante-dix-neuf couvrent donc du code mort, et c'est une revue humaine qui l'a trouvé, pas l'outillage. C'est précisément ce que knip attrape. Non bloquant, et sans contournement posé de notre côté : nous retirons le code mort à la main. ## Ce que je proposerais Trois voies, par ordre de préférence : 1. **`knip` en `peerDependencies` de `@eqs/tooling`, avec `peerDependenciesMeta.optional: true`**, et ajouté aux `devDependencies` du gabarit. Un satellite qui ne le veut pas peut le retirer, et il sait alors ce qu'il perd. 2. **`knip` en `dependencies` de `@eqs/tooling`.** Plus simple, mais impose l'outil et son arbre à tous les satellites, y compris ceux qui ne le joueront jamais. 3. Si l'intention est bien que knip soit **facultatif**, alors le dire ailleurs que dans une ligne de sortie : une mention dans `eqs doctor`, ou un compte des tâches sautées à la fin de `pnpm check` (« 6 tâches vertes, 1 sautée »). Aujourd'hui la ligne se perd, et rien dans le résumé final ne rappelle qu'un contrôle manque. Le même raisonnement vaudrait pour `stryker`, cité au même endroit du README — je ne l'ai pas vérifié. ## Versions - `@eqs/tooling`, `@eqs/cli` : 0.8.1 - `eqs --version` : 0.8.1 - `packageManager` : `pnpm@11.8.0`, Node v24.15.0, Windows 11 Pro 26200 Trouvé en développant le premier satellite réel (Pilotage), lors de la revue finale d'un lot.
Author
Owner

Vrai, et j'ai une dette à reconnaître avant le reste : ce trou était déjà consigné ce matin, dans reference/chasses.md, sous « reste à regarder » — « le gabarit et les satellites n'installent toujours pas knip ». Je l'avais bouché pour eqs-platform seul et noté le reste au lieu de le faire. Vous l'avez retrouvé six heures plus tard. Consigner un trou n'est pas le boucher.

Vos trois options sont bonnes, et j'ai pris la 1 et la 3. Il manquait une quatrième chose, sans laquelle la 1 aurait été pire que le mal.

Ce qui manquait : la configuration

knip 6 n'a pas de champ extends. Un dépôt ne peut donc pas partir de knip.json en une ligne de JSON — il lui faut du code. Et du code de configuration copié dans trente satellites dérive.

Sans configuration, mesuré sur le gabarit :

Unused files (38)
  apps/web/generated/api/**          ← tout le client généré
  variantes/next-fullstack/**        ← pas même dans le pnpm-workspace
  .claude/refuser-genere.mjs         ← les hooks de l'éditeur
  …

Trente-huit fichiers, et les quatre vraies trouvailles noyées dedans. Installer knip sans la configuration aurait donné une porte qui crie sur du code vivant, c'est-à-dire une porte éteinte en une semaine — et plus jamais rallumée.

D'où @eqs/tooling/knip-satellite : knipSatellite() lit la forme du dépôt (les apps/*, le eqs.openapi du manifeste) et en déduit les points d'entrée. Le knip.js d'un satellite tient en deux lignes :

const { knipSatellite } = require('@eqs/tooling/knip-satellite')
module.exports = knipSatellite()

Avec ça, sur votre gabarit, il ne reste que du vrai.

Une de vos hypothèses était fausse, et c'est votre porte qui l'a dit

knip annonçait zod inutilisée dans apps/web. Je l'ai retirée. types:web a refusé :

generated/api/zod/version/version.ts(8,22): error TS2307: Cannot find module 'zod'

La cause est dans la base de la flotte : **/generated/** était dans ignore, et ignore retire les fichiers du périmètre entier — leurs imports ne comptent plus. Une dépendance que seul le client généré utilise passe donc pour morte. Le client généré est maintenant déclaré en entry : ni signalé comme mort (une entrée ne l'est jamais), ni invisible. Vérifié en la retirant à nouveau — knip la rend alors en dépendance non déclarée, ce qui est la vérité.

Il restait donc deux dépendances mortes, pas trois : @eqs/errors et @eqs/telemetry dans apps/api, qui arrivent de toute façon par @eqs/platform-nest.

Votre option 3, faite aussi

Vous aviez raison : la ligne se perd. Elle était imprimée en tête, avant toute la sortie des tâches. Elle est maintenant répétée dans le bloc final, avec un compte :

  ✓ lint        1.2s
  ✓ unit        3.4s
  ⊘ knip      sautée  node_modules/knip/bin/knip.js absent — l'outil n'est pas installé
  total        3.4s  / budget 60.0s
  2 tâche(s) jouée(s), 1 sautée(s) — l'outil manque, la porte est incomplète.

Même règle que pour les contrôles de conformité, appliquée au lanceur : un contrôle non exécutable n'est pas un contrôle vert.

Ce que knip a trouvé en tournant, et qui vaut plus que les dépendances

Votre test:e2e :

eqs e2e up && playwright test; eqs e2e down

Deux défauts pour un caractère, un par plateforme, et le second est grave :

plateforme ce qui se passe
Windows cmd.exe ne sépare pas sur ; : eqs e2e down ne tourne jamais, la pile Docker reste debout après chaque exécution
Linux le code de sortie d'une liste A; B est celui de B : un playwright test rouge était suivi d'un down vert, et l'étage sortait à 0

Mesuré des deux côtés : node dire.mjs un; node dire.mjs deux rend ["un;","node","dire.mjs","deux"], et sh -c 'true && false; true' rend 0.

L'étage e2e ne pouvait pas échouer, y compris en CI. Une porte qui ne peut pas refuser n'est pas une porte. scripts/e2e.mjs met la descente dans un finally et rend le code de la mesure, pas du ménage. Le ; rejoint tooling.script-shell-posix — hors guillemets seulement, node -e "const a = 1; …" reste vert.

Au passage : @playwright/test n'est pas installé, il n'y a ni configuration ni scénario. Le script le dit maintenant avant de monter la pile, au lieu de laisser playwright échouer sur « command not found » après coup. eqs e2e up/down fonctionnent ; c'est le côté Playwright qui reste à poser, et c'est un vrai chantier, pas un oubli.

stryker, puisque vous posez la question

Ce n'est pas le même cas, et j'ai vérifié avant de répondre. Aucune tâche ne déclare Stryker nulle part — eqs check ne le mentionne pas. Donc rien ne ment : knip était annoncé et absent, Stryker n'est pas annoncé. La mutation prend des minutes, la porte vise soixante secondes, et le cliquet est fait pour tourner à part.

Ce qui était trompeur, c'est la ligne du README qui les mettait sur le même plan. Elle est corrigée, et l'état réel est écrit : base publiée, aucun dépôt de la flotte n'installe Stryker, aucun cliquet posé.

Où en sont les branches

dépôt branche fusionnable
eqs-platform chasse/knip-et-code-mort oui
eqs-satellite-template fix/scripts-windows (porte le test:e2e) oui
eqs-satellite-template chore/deps-mortes oui
eqs-satellite-template le câblage knip non — après la publication du socle 0.9.0

La dernière ligne mérite d'être dite franchement : le knip.js du gabarit a besoin de @eqs/tooling/knip-satellite, donc du socle publié. J'ai essayé de la pousser ; votre pre-push l'a refusée, parce que pnpm check ne peut pas être vert avant. Il a eu raison, et je n'ai pas passé --no-verify — #50 vient d'être écrit sur ce sujet précis. Ce morceau attend donc la sortie, et c'est le seul.

Je laisse ce ticket ouvert jusqu'à la publication : chez vous, knip ne tournera qu'à ce moment-là.

Vrai, et j'ai une dette à reconnaître avant le reste : **ce trou était déjà consigné ce matin**, dans `reference/chasses.md`, sous « reste à regarder » — « le gabarit et les satellites n'installent toujours pas knip ». Je l'avais bouché pour `eqs-platform` seul et noté le reste au lieu de le faire. Vous l'avez retrouvé six heures plus tard. Consigner un trou n'est pas le boucher. Vos trois options sont bonnes, et j'ai pris la 1 **et** la 3. Il manquait une quatrième chose, sans laquelle la 1 aurait été pire que le mal. ## Ce qui manquait : la configuration knip 6 n'a **pas** de champ `extends`. Un dépôt ne peut donc pas partir de `knip.json` en une ligne de JSON — il lui faut du code. Et du code de configuration copié dans trente satellites dérive. Sans configuration, mesuré sur le gabarit : ``` Unused files (38) apps/web/generated/api/** ← tout le client généré variantes/next-fullstack/** ← pas même dans le pnpm-workspace .claude/refuser-genere.mjs ← les hooks de l'éditeur … ``` Trente-huit fichiers, et les quatre vraies trouvailles noyées dedans. Installer knip sans la configuration aurait donné une porte qui crie sur du code vivant, c'est-à-dire une porte éteinte en une semaine — et plus jamais rallumée. D'où `@eqs/tooling/knip-satellite` : `knipSatellite()` lit la forme du dépôt (les `apps/*`, le `eqs.openapi` du manifeste) et en déduit les points d'entrée. Le `knip.js` d'un satellite tient en deux lignes : ```js const { knipSatellite } = require('@eqs/tooling/knip-satellite') module.exports = knipSatellite() ``` Avec ça, sur votre gabarit, il ne reste que du vrai. ## Une de vos hypothèses était fausse, et c'est votre porte qui l'a dit knip annonçait `zod` inutilisée dans `apps/web`. Je l'ai retirée. `types:web` a refusé : ``` generated/api/zod/version/version.ts(8,22): error TS2307: Cannot find module 'zod' ``` La cause est dans la base de la flotte : `**/generated/**` était dans `ignore`, et `ignore` retire les fichiers du périmètre **entier** — leurs imports ne comptent plus. Une dépendance que *seul* le client généré utilise passe donc pour morte. Le client généré est maintenant déclaré en `entry` : ni signalé comme mort (une entrée ne l'est jamais), ni invisible. Vérifié en la retirant à nouveau — knip la rend alors en dépendance **non déclarée**, ce qui est la vérité. Il restait donc **deux** dépendances mortes, pas trois : `@eqs/errors` et `@eqs/telemetry` dans `apps/api`, qui arrivent de toute façon par `@eqs/platform-nest`. ## Votre option 3, faite aussi Vous aviez raison : la ligne se perd. Elle était imprimée **en tête**, avant toute la sortie des tâches. Elle est maintenant répétée dans le bloc final, avec un compte : ``` ✓ lint 1.2s ✓ unit 3.4s ⊘ knip sautée node_modules/knip/bin/knip.js absent — l'outil n'est pas installé total 3.4s / budget 60.0s 2 tâche(s) jouée(s), 1 sautée(s) — l'outil manque, la porte est incomplète. ``` Même règle que pour les contrôles de conformité, appliquée au lanceur : un contrôle non exécutable n'est pas un contrôle vert. ## Ce que knip a trouvé en tournant, et qui vaut plus que les dépendances Votre `test:e2e` : ``` eqs e2e up && playwright test; eqs e2e down ``` Deux défauts pour un caractère, un par plateforme, et le second est grave : | plateforme | ce qui se passe | | --- | --- | | Windows | `cmd.exe` ne sépare pas sur `;` : `eqs e2e down` ne tourne **jamais**, la pile Docker reste debout après chaque exécution | | Linux | le code de sortie d'une liste `A; B` est celui de **B** : un `playwright test` rouge était suivi d'un `down` vert, et l'étage sortait à **0** | Mesuré des deux côtés : `node dire.mjs un; node dire.mjs deux` rend `["un;","node","dire.mjs","deux"]`, et `sh -c 'true && false; true'` rend `0`. **L'étage e2e ne pouvait pas échouer**, y compris en CI. Une porte qui ne peut pas refuser n'est pas une porte. `scripts/e2e.mjs` met la descente dans un `finally` et rend le code de la mesure, pas du ménage. Le `;` rejoint `tooling.script-shell-posix` — hors guillemets seulement, `node -e "const a = 1; …"` reste vert. Au passage : `@playwright/test` n'est pas installé, il n'y a ni configuration ni scénario. Le script le dit maintenant **avant** de monter la pile, au lieu de laisser `playwright` échouer sur « command not found » après coup. `eqs e2e up`/`down` fonctionnent ; c'est le côté Playwright qui reste à poser, et c'est un vrai chantier, pas un oubli. ## `stryker`, puisque vous posez la question Ce n'est **pas** le même cas, et j'ai vérifié avant de répondre. Aucune tâche ne déclare Stryker nulle part — `eqs check` ne le mentionne pas. Donc rien ne ment : knip était annoncé et absent, Stryker n'est pas annoncé. La mutation prend des minutes, la porte vise soixante secondes, et le cliquet est fait pour tourner à part. Ce qui était trompeur, c'est la ligne du README qui les mettait sur le même plan. Elle est corrigée, et l'état réel est écrit : base publiée, aucun dépôt de la flotte n'installe Stryker, aucun cliquet posé. ## Où en sont les branches | dépôt | branche | fusionnable | | --- | --- | --- | | `eqs-platform` | `chasse/knip-et-code-mort` | oui | | `eqs-satellite-template` | `fix/scripts-windows` (porte le `test:e2e`) | oui | | `eqs-satellite-template` | `chore/deps-mortes` | oui | | `eqs-satellite-template` | le câblage knip | **non — après la publication du socle 0.9.0** | La dernière ligne mérite d'être dite franchement : le `knip.js` du gabarit a besoin de `@eqs/tooling/knip-satellite`, donc du socle publié. J'ai essayé de la pousser ; votre `pre-push` l'a refusée, parce que `pnpm check` ne peut pas être vert avant. Il a eu raison, et je n'ai pas passé `--no-verify` — [#50](https://git.equilibre-structures.fr/Infrastructure/eqs-platform/issues/50) vient d'être écrit sur ce sujet précis. Ce morceau attend donc la sortie, et c'est le seul. Je laisse ce ticket ouvert jusqu'à la publication : chez vous, `knip` ne tournera qu'à ce moment-là.
nicolas-fryder referenced this issue from a commit 2026-09-12 14:30:20 +00:00
Author
Owner

Corrigé. knip est installé et joué : testing.etage-declare-vide le connaît (OUTILS de packages/testing/src/etages.ts), et le contrôle refuse désormais tout étage déclaré qu'aucun outil ne peut jouer — c'est la classe entière, pas la seule instance. AGENTS.md du socle le dit : « knip n'y a été branché que le 2026-09-10 : il était annoncé depuis le début, publiait sa base dans @eqs/tooling, et n'était installé nulle part. »

Fermé après vérification contre le code de dev le 2026-09-21. Ce ticket restait ouvert faute d'un Closes #51 dans le commit, pas faute de correctif.

Corrigé. knip est installé et joué : `testing.etage-declare-vide` le connaît (`OUTILS` de `packages/testing/src/etages.ts`), et le contrôle refuse désormais tout étage **déclaré** qu'aucun outil ne peut jouer — c'est la classe entière, pas la seule instance. `AGENTS.md` du socle le dit : « knip n'y a été branché que le 2026-09-10 : il était annoncé depuis le début, publiait sa base dans `@eqs/tooling`, et n'était installé nulle part. » Fermé après vérification contre le code de `dev` le 2026-09-21. Ce ticket restait ouvert faute d'un `Closes #51` dans le commit, pas faute de correctif.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Infrastructure/eqs-platform#51
No description provided.