@mostajs/layer-lint
v0.2.0
Published
Porte d'architecture DEVRULES §5.bis : vérifie la règle de dépendance descendante (un module N(k) ne dépend que de niveaux ≤ k), détecte les cycles et les niveaux incohérents. Sait aussi INFÉRER le plancher de niveau imposé par le graphe.
Maintainers
Readme
@mostajs/layer-lint
Auteur : Dr Hamid MADANI [email protected] · AGPL-3.0-or-later · zéro dépendance
La porte d'architecture de DEVRULES §5.bis : un module de niveau N(k) ne dépend que de modules de niveau ≤ k. Toute inversion de couche ou tout cycle est une faute → refus de publication.
§5.bis est normative depuis le 2026-06-30 et annonçait une vérification « en CI via
mostajs-layer-lint». L'outil n'existait pas, et aucunpackage.jsonne déclarait son niveau : la règle était inapplicable, donc invérifiable, donc morte. Ce module la rend exécutable.
Les cinq couches
| | Nature | Exemples |
|---|---|---|
| N0 | primitif | config, orm, url, palette |
| N1 | générique transverse | auth, rbac, audit, storage |
| N2 | composé / UI | app-shell-ui, crud-ui, *-ui |
| N3 | métier | incubator, school, marketplace |
| N4 | vertical | packs, solutions, assemblages |
Usage
npx mostajs-layer-lint # vérifie (0 = conforme · 1 = faute d'architecture)
npx mostajs-layer-lint --strict # un niveau non déclaré devient une ERREUR (Definition of Done 1.0)
npx mostajs-layer-lint --infer # PROPOSE un niveau pour chaque module
npx mostajs-layer-lint --infer --write # …et l'écrit dans les package.jsonLe manifeste porte alors :
"mostajs": { "niveau": "N1", "nature": "generique", "reutilisabilite": "transverse",
"depend_de": ["@mostajs/config", "@mostajs/url"] }Démo
node examples/demo.mjsElle monte un écosystème jouet contenant la faute la plus courante — un module transverse (notifications, N1) qui dépend d'un module métier (incubator, N3) — puis lance le lint sur le vrai écosystème.
Ce que la démo montre, et qui est tout l'intérêt de l'outil : la correction n'est pas une évidence technique. Soit notifications n'a rien à faire de l'incubateur (on retire la dépendance), soit il en porte vraiment la logique — et alors ce n'est pas un module transverse : le déclarer N3, c'est accepter de ne plus pouvoir le réutiliser ailleurs. Le lint ne choisit pas : il rend le coût visible avant qu'il soit payé.
Deux distinctions qui font tout
Arêtes dures vs optionnelles. dependencies et les peers requis sont un vrai couplage. Un peer optionnel (peerDependenciesMeta.optional) dit « si tu as X, je m'intègre avec » — un cycle qui n'emprunte que des arêtes optionnelles n'est pas une faute. Sans cette distinction, le lint criait sur auth ↔ net (intégration facultative parfaitement saine) ; avec elle, il ne reste qu'une vraie faute dans 238 modules.
Contrainte vs proposition. Le plancher (max(niveau(deps))) est la contrainte normative : un N2 peut dépendre d'un autre N2. La classification (--infer) est une proposition adossée aux exemples de §5.bis, relevée au plancher par point fixe. Une inférence par simple profondeur de graphe classait auth en N4 — alors que §5.bis le cite comme exemple de N1 : composer trois primitives ne fait pas de vous un vertical. Un niveau dit une nature ; une nature ne se lit pas dans un graphe.
API
readEcosystem(root) · lint(mods, {strict}) → {errors, warnings, stats} · infer(mods) · write(mods, props) · findCycles · computeFloors · classify · LEVELS / NATURES / BASE / NEUTRAL.
12 tests verts (@mostajs/mjs-unit).
