@mostajs/kind-catalog 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,92 @@
2
2
 
3
3
  **Auteur** : Dr Hamid MADANI <drmdh@msn.com>
4
4
 
5
+ ## 0.3.0 — 2026-09-04
6
+
7
+ ### Ajouté — `diagnostiquer()` : le tranchant « AMÉLIORER »
8
+
9
+ Lire le plan d'une application et lui dire **ce qui lui manque**. Livrable en une heure, sur un
10
+ plan qu'on nous donne, sans toucher au code.
11
+
12
+ > *« Votre plan porte quatre exigences de périmètre. La fiche en connaît six erreurs. Trois ne sont
13
+ > mentionnées nulle part chez vous — voici lesquelles, et ce qu'elles coûtent. »*
14
+
15
+ **Les deux sorties sont DÉCOUPLÉES, et c'est le point.** Elles n'ont pas le même optimum, et les
16
+ lier faisait taire l'une pour l'autre :
17
+
18
+ - les **occurrences** proposées demandent de la **précision** — à deux mots communs, le diagnostic
19
+ proposait **vingt-sept** occurrences pour une seule règle : personne ne confirme vingt-sept
20
+ lignes. Seuil porté à cinq, liste courte ;
21
+ - les **erreurs non couvertes** demandent du **rappel** — c'est le livrable, et taire celle qui
22
+ manque coûte bien plus que d'en proposer une déjà couverte. Elles sont cherchées dans **tout** le
23
+ plan, et rendues **même quand aucune exigence n'a pu être rapprochée**.
24
+
25
+ Une règle **transverse** rend ses erreurs dans tous les cas ; une règle **métier** seulement si une
26
+ exigence l'a désignée — sinon un plan universitaire recevrait les pièges de l'apiculture.
27
+
28
+ **Il propose, il n'applique pas** (`T-KC-21`), et **chaque candidat rend les mots qui l'ont
29
+ désigné** (`T-KC-17`) : un score opaque ne se conteste pas, donc il ne se corrige jamais. C'est
30
+ `KIND-ECRITURE-GARDEE-01` appliqué à l'outil lui-même.
31
+
32
+ **Il ne modifie pas le plan qu'il lit** (`T-KC-20`).
33
+
34
+ ### Enrichi — l'âge d'une donnée, et non son effacement
35
+
36
+ `KIND-CHIFFRE-SANS-SOURCE-01` gagne **TicketFlow 2.0** et **`@mostajs/paiement-tpe`** — quatre
37
+ projets indépendants — et deux erreurs que ces projets ont fait apparaître :
38
+
39
+ - **la valeur périmée est présentée sans son âge** → *elle a l'air normale, donc personne ne la
40
+ vérifie. Un affichage périmé qui a l'air normal coûte plus cher qu'un affichage qui avoue son
41
+ âge : le second se corrige, le premier se propage en décision.*
42
+ - **la valeur périmée est simplement effacée** → *on perd la dernière valeur CONNUE, souvent
43
+ utile. Les deux remèdes ne se valent pas : DATER conserve l'information, BASCULER la jette.*
44
+
45
+ C'est la nuance que CollabTrax (« bascule en sans données ») et TicketFlow (« file au 04/09 à
46
+ 11h12 ») ont résolue différemment. La fiche porte désormais les deux, avec leur compromis.
47
+
48
+ `@mostajs/paiement-tpe` y entre par son `sonder()` qui rend `'inconnu'` et jamais `false` — *« qui
49
+ ferait croire l'appareil en panne alors qu'on n'en sait rien »*.
50
+
51
+ **21 fiches · 86 erreurs cataloguées · 38 incidents · 32 essais.**
52
+
53
+ ## 0.2.0 — 2026-09-03
54
+
55
+ ### Ajouté — `enrichir()` : projeter pour CRÉER, enrichir pour EXPLOITER
56
+
57
+ Le passage à qatrax a rendu visible une faute de modèle, et le critère qui la tranche :
58
+
59
+ > **Dans le RÉFÉRENTIEL, une même règle deux fois est une duplication. Dans une APPLICATION, la
60
+ > même règle appliquée à un objet précis est une OCCURRENCE d'exploitation — et il en faut autant
61
+ > qu'il y a d'objets.**
62
+
63
+ `toDevtest` projetait la règle **générique** dans le plan de l'application : ni référentiel, ni
64
+ occurrence — **la règle recopiée**. ATC portait déjà **quatre** occurrences du périmètre (le
65
+ portail, le coaching, l'e-learning, le cumul de rôles) et recevait une cinquième exigence qui ne
66
+ parlait de rien. RestoTrax de même, avec `PIL-10` à `PIL-13`.
67
+
68
+ ```js
69
+ enrichir(plan, { kind, specRefs: ['SPEC-POR-01', 'SPEC-COA-01', 'SPEC-ELE-01', 'SPEC-ROL-02'] })
70
+ ```
71
+
72
+ Les erreurs connues de la fiche descendent dans les exigences **que l'application a déjà**.
73
+ **Aucune exigence, aucun essai n'est ajouté** — ce qui referme *par construction* le défaut que
74
+ qatrax avait signalé : des cas de plan que rien ne peut exécuter, parce qu'ils prétendaient qu'un
75
+ essai portant un autre nom les couvrait.
76
+
77
+ `toDevtest` garde son emploi : une application **neuve**, sans aucune exigence, à qui la fiche
78
+ donne les siennes.
79
+
80
+ **Rejouable** (`T-KC-14`) : le bloc est borné par un marqueur et se remplace au lieu de s'empiler —
81
+ le script d'une application se relance à chaque montée du catalogue.
82
+
83
+ **Refuse plutôt que de faire semblant** (`T-KC-15`) : une exigence introuvable — renommée, le plus
84
+ souvent — est une erreur, pas un avertissement ; et enrichir sans rien désigner est refusé, parce
85
+ que c'est ne rien faire tout en croyant l'avoir fait.
86
+
87
+ `retirerProjection()` reprend ce qu'une projection avait posé, pour les applications qui basculent.
88
+
89
+ 27 essais.
90
+
5
91
  ## 0.1.1 — 2026-09-03
6
92
 
7
93
  ### Ajouté — `bind` : lier une épreuve à l'essai qui la couvre DÉJÀ
@@ -41,6 +41,18 @@
41
41
  "title": "Le corpus livré est valide, sourcé et lisible",
42
42
  "priority": "critical",
43
43
  "status": "verified"
44
+ },
45
+ {
46
+ "ref": "SPEC-KC-08",
47
+ "title": "ENRICHIR les occurrences existantes plutôt que RECOPIER la règle dans le plan de l'application",
48
+ "priority": "critical",
49
+ "status": "verified"
50
+ },
51
+ {
52
+ "ref": "SPEC-KC-09",
53
+ "title": "DIAGNOSTIQUER un plan existant : proposer les occurrences, NOMMER les erreurs qu'il ne mentionne pas",
54
+ "priority": "critical",
55
+ "status": "verified"
44
56
  }
45
57
  ],
46
58
  "realisations": [
@@ -97,6 +109,24 @@
97
109
  "artifact": "src/",
98
110
  "progress": 100,
99
111
  "status": "done"
112
+ },
113
+ {
114
+ "ref": "REL-KC-08",
115
+ "specRef": "SPEC-KC-08",
116
+ "kind": "feature",
117
+ "title": "enrichir() · retirerProjection()",
118
+ "artifact": "src/enrichir.js",
119
+ "progress": 100,
120
+ "status": "done"
121
+ },
122
+ {
123
+ "ref": "REL-KC-09",
124
+ "specRef": "SPEC-KC-09",
125
+ "kind": "feature",
126
+ "title": "diagnostiquer() · rapportDiagnostic()",
127
+ "artifact": "src/diagnostic.js",
128
+ "progress": 100,
129
+ "status": "done"
100
130
  }
101
131
  ],
102
132
  "tests": [
@@ -421,6 +451,132 @@
421
451
  "expected": "la liée est `automated` avec son renvoi ; l'autre est `manual` sans renvoi — la déclarer automatisée prétendrait qu'un essai la couvre, et ouvrirait un trou dans la porte qualité de l'application"
422
452
  }
423
453
  ]
454
+ },
455
+ {
456
+ "ref": "T-KC-13",
457
+ "specRef": "SPEC-KC-08",
458
+ "type": "automated",
459
+ "priority": "critical",
460
+ "title": "T-KC-13 — enrichir dépose les erreurs dans les exigences EXISTANTES, sans rien ajouter",
461
+ "autoRef": "test-scripts/unit/catalogue.test.mjs::T-KC-13",
462
+ "steps": [
463
+ {
464
+ "action": "enrichir deux exigences d'un plan",
465
+ "expected": "les descriptions reçoivent les erreurs connues ; aucune exigence ni aucun essai n'est ajouté — c'est ce qui referme le défaut des cas que rien ne peut exécuter"
466
+ }
467
+ ]
468
+ },
469
+ {
470
+ "ref": "T-KC-14",
471
+ "specRef": "SPEC-KC-08",
472
+ "type": "automated",
473
+ "priority": "critical",
474
+ "title": "T-KC-14 — enrichir deux fois ne DOUBLE pas le bloc",
475
+ "autoRef": "test-scripts/unit/catalogue.test.mjs::T-KC-14",
476
+ "steps": [
477
+ {
478
+ "action": "rejouer l'enrichissement",
479
+ "expected": "description inchangée — le script d'une application se rejoue à chaque montée du catalogue"
480
+ }
481
+ ]
482
+ },
483
+ {
484
+ "ref": "T-KC-15",
485
+ "specRef": "SPEC-KC-08",
486
+ "type": "automated",
487
+ "priority": "critical",
488
+ "title": "T-KC-15 — enrichir REFUSE une exigence introuvable, et refuse de ne rien désigner",
489
+ "autoRef": "test-scripts/unit/catalogue.test.mjs::T-KC-15",
490
+ "steps": [
491
+ {
492
+ "action": "exigence renommée, puis liste vide",
493
+ "expected": "refus des deux — une exigence renommée ferait partir l'enrichissement dans le vide, en silence"
494
+ }
495
+ ]
496
+ },
497
+ {
498
+ "ref": "T-KC-16",
499
+ "specRef": "SPEC-KC-08",
500
+ "type": "automated",
501
+ "priority": "critical",
502
+ "title": "T-KC-16 — `retirerProjection` reprend ce qu'une projection avait posé",
503
+ "autoRef": "test-scripts/unit/catalogue.test.mjs::T-KC-16",
504
+ "steps": [
505
+ {
506
+ "action": "projeter puis retirer",
507
+ "expected": "les entrées projetées disparaissent, celles de l'application survivent"
508
+ }
509
+ ]
510
+ },
511
+ {
512
+ "ref": "T-KC-17",
513
+ "specRef": "SPEC-KC-09",
514
+ "type": "automated",
515
+ "priority": "critical",
516
+ "title": "T-KC-17 — le diagnostic PROPOSE une occurrence et rend LES MOTS qui l'ont désignée",
517
+ "autoRef": "test-scripts/unit/catalogue.test.mjs::T-KC-17",
518
+ "steps": [
519
+ {
520
+ "action": "auditer un plan qui porte une exigence de périmètre",
521
+ "expected": "l'occurrence est proposée avec les mots communs — un score opaque ne se conteste pas, donc il ne se corrige jamais"
522
+ }
523
+ ]
524
+ },
525
+ {
526
+ "ref": "T-KC-18",
527
+ "specRef": "SPEC-KC-09",
528
+ "type": "automated",
529
+ "priority": "critical",
530
+ "title": "T-KC-18 — il NOMME les erreurs connues que le plan ne mentionne nulle part",
531
+ "autoRef": "test-scripts/unit/catalogue.test.mjs::T-KC-18",
532
+ "steps": [
533
+ {
534
+ "action": "auditer un plan d'une seule exigence",
535
+ "expected": "les manques sont nommés, chacun avec sa conséquence"
536
+ }
537
+ ]
538
+ },
539
+ {
540
+ "ref": "T-KC-19",
541
+ "specRef": "SPEC-KC-09",
542
+ "type": "automated",
543
+ "priority": "critical",
544
+ "title": "T-KC-19 — les deux sorties sont DÉCOUPLÉES : une règle transverse parle même sans occurrence",
545
+ "autoRef": "test-scripts/unit/catalogue.test.mjs::T-KC-19",
546
+ "steps": [
547
+ {
548
+ "action": "auditer un plan sans rapport avec le catalogue",
549
+ "expected": "les règles transverses rendent leurs erreurs ; les règles métier se taisent — sinon un plan de bordereaux reçoit les pièges de l'apiculture"
550
+ }
551
+ ]
552
+ },
553
+ {
554
+ "ref": "T-KC-20",
555
+ "specRef": "SPEC-KC-09",
556
+ "type": "automated",
557
+ "priority": "critical",
558
+ "title": "T-KC-20 — le diagnostic NE MODIFIE PAS le plan qu'il lit",
559
+ "autoRef": "test-scripts/unit/catalogue.test.mjs::T-KC-20",
560
+ "steps": [
561
+ {
562
+ "action": "auditer puis comparer le plan",
563
+ "expected": "intact — un audit qui change son objet ne distingue plus ce qui venait du plan"
564
+ }
565
+ ]
566
+ },
567
+ {
568
+ "ref": "T-KC-21",
569
+ "specRef": "SPEC-KC-09",
570
+ "type": "automated",
571
+ "priority": "critical",
572
+ "title": "T-KC-21 — le rapport dit que les rapprochements sont PROPOSÉS, jamais appliqués",
573
+ "autoRef": "test-scripts/unit/catalogue.test.mjs::T-KC-21",
574
+ "steps": [
575
+ {
576
+ "action": "relire le rapport rendu",
577
+ "expected": "la mention y est, ainsi que l'explicabilité — c'est KIND-ECRITURE-GARDEE-01 appliqué à l'outil lui-même"
578
+ }
579
+ ]
424
580
  }
425
581
  ]
426
582
  }
@@ -100,7 +100,9 @@ ce sont ceux qu'on omet, et ce sont ceux qui servent.
100
100
  |---|---|---|
101
101
  | **0.1.0** | fiche + validation + projection + instanciation + corpus initial (5 fiches tirées d'incidents réels) | §11.1 : essais mjs-unit verts, plan DEVTEST à trois trous nuls |
102
102
  | 0.2.0 | index inverse des emplois (B5), `findKinds` enrichi, premières fiches **agronomie** et **électronique** | corpus ≥ 15 fiches, ≥ 2 applications instanciées |
103
- | 0.3.0 | pont `assistant-pilote` : une fiche déclare son `kind` ro-pla et devient une question activable | une question née d'une fiche, activée en production |
103
+ | **0.3.0-a** | **AMÉLIORER** `diagnostiquer(plan, kinds)` : rapproche les exigences d'une application des fiches, **propose** la correspondance, et NOMME les erreurs connues qu'aucune exigence ne mentionne | un audit rendu sur le plan d'une application tierce, sans toucher au code |
104
+ | **0.3.0-b** | **BÂTIR** — `demarrer({ domaines, project, prefix })` : plan de départ complet, épreuves **manuelles** | un projet neuf démarre sur un plan qu'il n'a pas écrit |
105
+ | 0.4.0 | pont `assistant-pilote` : une fiche déclare son `kind` ro-pla et devient une question activable | une question née d'une fiche, activée en production |
104
106
 
105
107
  ### Le corpus initial de 0.1.0 — **des incidents réels, pas des suppositions**
106
108
 
@@ -112,6 +114,55 @@ ce sont ceux qu'on omet, et ce sont ceux qui servent.
112
114
  | `KIND-ACQUIS-01` — consulter un acquis ne doit pas l'effacer | `@mostajs/elearning` | `apprentissage` |
113
115
  | `KIND-DONNEE-INSUFFISANTE-01` — un calcul sur trop peu de données doit REFUSER, en chiffrant ce qui manque | RestoTrax, ATC | `decision` |
114
116
 
117
+ ## 4.bis · L'OUTIL À DOUBLE TRANCHANT *(orientation produit, 03/09/2026)*
118
+
119
+ Le critère « duplication au référentiel / occurrence à l'application » a séparé deux fonctions.
120
+ Elles ne sont pas deux options techniques : ce sont **deux produits, pour deux marchés**.
121
+
122
+ | | **BÂTIR** | **AMÉLIORER** |
123
+ |---|---|---|
124
+ | fonction | `toDevtest` | `enrichir` |
125
+ | à qui | un projet qui **commence** | un projet qui **tourne** |
126
+ | ce qu'il reçoit | ses exigences de départ, avec leurs épreuves | les erreurs connues, dans ses exigences à lui |
127
+ | ce qu'il évite | écrire un plan de test depuis une page blanche | repayer un défaut qu'un autre a déjà payé |
128
+ | valeur perçue | « on démarre juste » | **« vous avez six erreurs connues que votre plan ne mentionne nulle part »** |
129
+ | marché | projets neufs | **le parc existant — bien plus vaste** |
130
+
131
+ ### Ce que chaque tranchant exige encore
132
+
133
+ **BÂTIR — il manque la SÉLECTION.** Aujourd'hui l'application nomme ses fiches une par une. Un
134
+ assistant de démarrage part du **domaine** : *« un logiciel de gestion d'élèves »* → les fiches
135
+ `acces`, `donnees`, `decision`, plus le métier. `findKinds({ domaine })` existe ; ce qui manque est
136
+ un `demarrer({ domaines, project, prefix })` qui rende un plan de départ complet, dont les épreuves
137
+ sont **manuelles** — un travail à faire, honnêtement affiché, jamais un plan qui prétend être tenu.
138
+
139
+ **AMÉLIORER — il manque le DIAGNOSTIC, et c'est le vrai produit.** Aujourd'hui l'humain déclare la
140
+ correspondance (`occurrences: ['SPEC-POR-01', …]`). Ce qui a de la valeur, c'est l'inverse : **lire
141
+ le plan d'une application et lui dire ce qui lui manque**.
142
+
143
+ > *« Votre plan porte quatre exigences de périmètre. La fiche en connaît six erreurs. Trois ne sont
144
+ > mentionnées nulle part chez vous — voici lesquelles, et ce qu'elles coûtent. »*
145
+
146
+ C'est un audit livrable **en une heure**, sur un plan qu'on nous donne, sans toucher au code.
147
+
148
+ ### ⚠️ Le diagnostic PROPOSE, il n'applique pas
149
+
150
+ Un rapprochement automatique entre les exigences d'une application et les fiches du catalogue **se
151
+ trompera** : les intitulés varient, les métiers diffèrent, une ressemblance de mots n'est pas une
152
+ identité de règle. Un outil qui appliquerait ses rapprochements tout seul poserait des blocs
153
+ d'erreurs sur des exigences qui n'ont rien à voir, et ruinerait la confiance dans les blocs justes.
154
+
155
+ **Il propose une correspondance ; l'humain confirme.** C'est `KIND-ECRITURE-GARDEE-01`, appliqué à
156
+ l'outil lui-même — et c'est la meilleure démonstration possible du catalogue : il tient sa propre
157
+ règle sur son propre produit.
158
+
159
+ ### Ce que cela change à la feuille de route
160
+
161
+ Le jalon **0.3.0** devient double, et la partie « améliorer » passe devant : c'est elle qui se vend
162
+ sans qu'un projet ait besoin de commencer.
163
+
164
+ ---
165
+
115
166
  ## 5.bis · Les DOMAINES du catalogue
116
167
 
117
168
  Un `domaine` regroupe les fiches d'un même métier. Le vocabulaire est **ouvert** — un domaine
@@ -16,6 +16,7 @@ export const kinds = [
16
16
  'chaque valeur affichée provient d’une source RÉELLEMENT interrogée',
17
17
  'chaque compteur est cliquable vers la liste qui le compose',
18
18
  'la fraîcheur de chaque valeur est visible, et au-delà du seuil la valeur bascule en « sans données »',
19
+ 'un affichage périmé AVOUE SON ÂGE — « au 04/09 à 11h12 » — plutôt que de présenter un chiffre comme actuel',
19
20
  'trois états, et trois seulement : non branché · branché sans données · branché réel',
20
21
  ],
21
22
  erreurs: [
@@ -25,6 +26,10 @@ export const kinds = [
25
26
  consequence: 'zéro est un FAIT ; « je ne sais pas » n’en est pas un. On lit une chute d’activité là où il y a une panne de collecte' },
26
27
  { titre: 'la valeur périmée continue d’être servie',
27
28
  consequence: 'le tableau de bord affiche avec le même aplomb une mesure d’hier et une d’il y a six mois' },
29
+ { titre: 'la valeur périmée est présentée SANS SON ÂGE',
30
+ consequence: 'elle a l’air normale, donc personne ne la vérifie. **Un affichage périmé qui a l’air normal coûte plus cher qu’un affichage qui avoue son âge** — le second se corrige, le premier se propage en décision' },
31
+ { titre: 'la valeur périmée est simplement effacée au profit de « sans données »',
32
+ consequence: 'on perd la dernière valeur CONNUE, qui reste souvent utile — mieux vaut « au 04/09 à 11h12 » que rien du tout. Les deux remèdes ne se valent pas : DATER conserve l’information, BASCULER la jette' },
28
33
  { titre: 'le compteur ne mène nulle part',
29
34
  consequence: 'un chiffre qu’on ne peut pas ouvrir ne se conteste pas — donc il ne se corrige jamais' },
30
35
  { titre: 'un second tableau de suivi est tenu à la main à côté',
@@ -32,7 +37,7 @@ export const kinds = [
32
37
  ],
33
38
  test: [
34
39
  { action: 'couper une source, puis afficher', attendu: '« sans données », jamais zéro' },
35
- { action: 'vieillir une valeur au-delà du seuil de fraîcheur', attendu: 'elle bascule en « sans données » et le dit' },
40
+ { action: 'vieillir une valeur au-delà du seuil de fraîcheur', attendu: 'son ÂGE est affiché — « au 04/09 à 11h12 » — ou elle bascule en « sans données » ; jamais présentée comme actuelle' },
36
41
  { action: 'chercher dans le rendu un nombre absent des sources', attendu: 'aucun' },
37
42
  { action: 'suivre un compteur', attendu: 'il ouvre la liste filtrée qui le compose' },
38
43
  ],
@@ -40,6 +45,8 @@ export const kinds = [
40
45
  origine: [
41
46
  { type: 'incident', source: 'CollabTrax — « N’afficher aucune valeur sans source interrogée : jamais de nombre en dur, jamais de valeur d’exemple » ; trois états ; fraîcheur ; compteur cliquable', date: '2026-08-03' },
42
47
  { type: 'incident', source: 'ATC — le pilotage compose @mostajs/reporting ; la vue ne recalcule aucun total (T-PIL-10)', date: '2026-09-01' },
48
+ { type: 'incident', source: 'TicketFlow 2.0 — la page affiche l’ÂGE de la donnée : « file au 04/09 à 11h12 » quand le site est déconnecté, et non un chiffre présenté comme actuel', date: '2026-09-04' },
49
+ { type: 'incident', source: '@mostajs/paiement-tpe — `sonder()` rend « inconnu », jamais `false`, qui ferait croire l’appareil en panne alors qu’on n’en sait rien', date: '2026-08-20' },
43
50
  ],
44
51
  }),
45
52
 
@@ -78,6 +78,7 @@ export const kinds = [
78
78
  { type: 'incident', source: 'LabTrax — « Fonctionner central injoignable : la perte du lien n’empêche NI le pointage, NI la délibération »', date: '2026-08-31' },
79
79
  { type: 'incident', source: 'SofTrax — « La révocation est signée donc opposable hors ligne, et coupe l’activation »', date: '2026-08-20' },
80
80
  { type: 'incident', source: 'LabTraxAdmin — même exigence, côté central : la licence ne se demande pas depuis l’instance', date: '2026-08-25' },
81
+ { type: 'incident', source: 'TicketFlow 2.0 — site déconnecté : la page le DIT et date la donnée, au lieu de servir un chiffre d’allure normale', date: '2026-09-04' },
81
82
  ],
82
83
  }),
83
84
  ];
package/llms.txt CHANGED
@@ -28,7 +28,15 @@ PIÈGES
28
28
  - Les fiches sont des FICHIERS VERSIONNES (kinds/*.kind.mjs). Ce qui va en base, c'est l'USAGE —
29
29
  et cela appartient a @mostajs/skill-library et au journal d'@mostajs/assistant-pilote.
30
30
  - AUCUN FORMAT NOUVEAU : la projection produit du mostajs-devtest/1, valide par le parseur officiel.
31
+ - PROJETER POUR CREER, ENRICHIR POUR EXPLOITER. Dans le REFERENTIEL, une regle deux fois = duplication ;
32
+ dans une APPLICATION, la regle appliquee a un objet = OCCURRENCE, et il en faut autant qu'il y a d'objets.
33
+ toDevtest = app NEUVE (elle recoit ses exigences) · enrichir = app EXISTANTE (ses occurrences recoivent
34
+ les erreurs connues). enrichir n'AJOUTE ni exigence ni essai — sinon qatrax porte des cas inexecutables.
31
35
  - loadCatalogue() fait un import() : charger un corpus, c'est EXECUTER du code. Une fiche se revoit
32
36
  comme du code, jamais comme un document (docs/15-REVUE-SECURITE).
33
37
  - validateKind() applique les memes defauts que defineKind : on peut lui passer un objet BRUT.
38
+ - DIAGNOSTIC : les deux sorties sont DECOUPLEES. Occurrences = precision (seuil 5 ; a 2 il en proposait
39
+ 27 pour une regle). Erreurs non couvertes = rappel, cherchees dans TOUT le plan, rendues meme sans
40
+ occurrence. Regle TRANSVERSE parle toujours ; regle METIER seulement si reconnue.
41
+ - Le diagnostic PROPOSE, il n'applique pas, et il ne modifie pas le plan qu'il lit.
34
42
  CORPUS LIVRE 21 fiches · 15 eprouvees (71 %) · 84 erreurs cataloguees · 35 incidents · 11 domaines.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/kind-catalog",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Catalogue de KINDS — fiches d'exigence réutilisables, projetables en plan mostajs-devtest/1 lisible par qatrax. N'exécute rien, ne stocke rien.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-or-later",
@@ -0,0 +1,165 @@
1
+ /**
2
+ * DIAGNOSTIQUER — lire le plan d'une application et lui dire ce qui lui MANQUE.
3
+ *
4
+ * ── LE TRANCHANT « AMÉLIORER » ─────────────────────────────────────────────
5
+ * Le catalogue sert deux fois : à BÂTIR (une application neuve reçoit ses exigences) et à
6
+ * AMÉLIORER (une application qui tourne reçoit ce que d'autres ont payé). Ce fichier est le
7
+ * second, et c'est celui qui se rend en une heure, sur un plan qu'on nous donne, sans toucher au
8
+ * code :
9
+ *
10
+ * « Votre plan porte quatre exigences de périmètre. La fiche en connaît six erreurs.
11
+ * Trois ne sont mentionnées nulle part chez vous — voici lesquelles, et ce qu'elles coûtent. »
12
+ *
13
+ * ⚠️ IL PROPOSE, IL N'APPLIQUE PAS. Un rapprochement automatique entre les exigences d'une
14
+ * application et les fiches SE TROMPERA : les intitulés varient, les métiers diffèrent, et une
15
+ * ressemblance de mots n'est pas une identité de règle. Un outil qui appliquerait ses
16
+ * rapprochements tout seul poserait des blocs d'erreurs sur des exigences sans rapport, et
17
+ * ruinerait la confiance dans les blocs justes. C'est `KIND-ECRITURE-GARDEE-01` appliqué à
18
+ * l'outil lui-même.
19
+ *
20
+ * ⚠️ LE RAPPROCHEMENT EST EXPLICABLE, ET C'EST SA SEULE DÉFENSE. Chaque candidat rend LES MOTS
21
+ * qui l'ont désigné : un score opaque serait pire que pas de score du tout — on ne peut ni le
22
+ * contester ni le corriger.
23
+ *
24
+ * Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
25
+ */
26
+
27
+ /** Mots vides du français — ils apparaissent partout et ne désignent rien. */
28
+ const VIDES = new Set(`le la les un une des du de d au aux et ou ni mais donc or car que qui quoi
29
+ dont ou sur sous dans par pour avec sans vers chez entre est sont etre ete a ont avoir plus moins
30
+ tres peu tout tous toute toutes meme aussi ainsi alors quand comme si ne pas non oui son sa ses
31
+ leur leurs notre nos votre vos ce cet cette ces cela ceci il elle ils elles on nous vous je tu
32
+ doit doivent peut peuvent faire fait fais rien jamais toujours deja encore apres avant lors
33
+ chaque autre autres bien mal seul seule sauf selon`.split(/\s+/).filter(Boolean));
34
+
35
+ /** Normalise : minuscules, sans accents, sans ponctuation. */
36
+ export const normaliser = (t) => String(t ?? '')
37
+ .toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '')
38
+ .replace(/[^a-z0-9]+/g, ' ').trim();
39
+
40
+ /** Les mots qui DÉSIGNENT quelque chose — au moins cinq lettres, hors mots vides. */
41
+ export function motsCles(texte) {
42
+ return new Set(normaliser(texte).split(' ').filter((m) => m.length >= 5 && !VIDES.has(m)));
43
+ }
44
+
45
+ const commun = (a, b) => [...a].filter((m) => b.has(m));
46
+
47
+ /** Tout le texte d'une exigence, épreuves comprises : c'est là que la règle se dit. */
48
+ function texteDeSpec(plan, spec) {
49
+ const essais = (plan.tests ?? []).filter((t) => t.specRef === spec.ref);
50
+ return [spec.title, spec.description,
51
+ ...essais.flatMap((t) => [t.title, ...(t.steps ?? []).flatMap((s) => [s.action, s.expected])])]
52
+ .filter(Boolean).join(' ');
53
+ }
54
+
55
+ /**
56
+ * Les domaines TRANSVERSES : leurs règles valent pour tout logiciel, qu'on ait su rapprocher une
57
+ * exigence ou non. Les domaines MÉTIER, eux, ne concernent que les applications de ce métier.
58
+ */
59
+ export const TRANSVERSES = new Set(['acces', 'donnees', 'apprentissage', 'decision', 'integration']);
60
+
61
+ /**
62
+ * Diagnostique un plan contre un corpus.
63
+ *
64
+ * ⚠️ LES DEUX SORTIES SONT DÉCOUPLÉES, ET C'EST LE POINT. Elles n'ont pas le même seuil optimal,
65
+ * et les lier faisait taire l'une pour l'autre :
66
+ *
67
+ * · les OCCURRENCES proposées demandent de la PRÉCISION — l'humain doit les confirmer, et
68
+ * personne ne confirme vingt-sept lignes. Seuil haut, liste courte.
69
+ * · les ERREURS NON COUVERTES demandent du RAPPEL — c'est le livrable, et taire celle qui
70
+ * manque coûte bien plus que d'en proposer une déjà couverte. Elles sont calculées sur TOUT
71
+ * le plan, et rendues même quand aucune exigence n'a pu être rapprochée.
72
+ *
73
+ * Une règle TRANSVERSE rend ses erreurs dans tous les cas ; une règle MÉTIER seulement si une
74
+ * exigence l'a désignée — sinon un plan universitaire recevrait les pièges de l'apiculture.
75
+ *
76
+ * @param {object} plan plan `mostajs-devtest/1` — NON modifié
77
+ * @param {object[]} kinds
78
+ * @param {object} [o]
79
+ * @param {number} [o.minCommuns] mots communs exigés pour PROPOSER une occurrence (défaut 5)
80
+ * @returns {object[]} un rapport par fiche
81
+ */
82
+ export function diagnostiquer(plan, kinds = [], { minCommuns = 5 } = {}) {
83
+ if (!plan?.specs) throw new Error('diagnostiquer: plan `mostajs-devtest/1` requis');
84
+ const toutLePlan = motsCles([
85
+ ...plan.specs.map((s) => `${s.title} ${s.description ?? ''}`),
86
+ ...(plan.tests ?? []).flatMap((t) => [t.title, ...(t.steps ?? []).flatMap((x) => [x.action, x.expected])]),
87
+ ].join(' '));
88
+
89
+ return kinds.map((k) => {
90
+ const cles = motsCles(`${k.enonce} ${k.succes.join(' ')} ${k.erreurs.map((e) => e.titre).join(' ')}`);
91
+
92
+ // 1 · LES OCCURRENCES CANDIDATES — proposées, jamais posées.
93
+ const occurrences = [];
94
+ for (const s of plan.specs) {
95
+ const c = commun(cles, motsCles(texteDeSpec(plan, s)));
96
+ if (c.length >= minCommuns) occurrences.push({ specRef: s.ref, titre: s.title, communs: c.sort() });
97
+ }
98
+ occurrences.sort((a, b) => b.communs.length - a.communs.length);
99
+
100
+ // 2 · LES ERREURS QUE LE PLAN NE MENTIONNE NULLE PART — c'est le livrable.
101
+ // Une erreur est tenue pour COUVERTE si le plan emploie la moitié de ses mots désignants.
102
+ // ⚠️ Le seuil est délibérément BAS : mieux vaut proposer une erreur déjà couverte — l'humain
103
+ // l'écarte en dix secondes — que taire celle qui manque, qu'il ne cherchera jamais.
104
+ const erreursNonCouvertes = k.erreurs.filter((e) => {
105
+ const mots = motsCles(`${e.titre} ${e.consequence}`);
106
+ if (mots.size === 0) return false;
107
+ return commun(mots, toutLePlan).length < Math.max(2, Math.ceil(mots.size / 2));
108
+ });
109
+
110
+ const transverse = TRANSVERSES.has(k.domaine);
111
+ return {
112
+ kind: k.ref, domaine: k.domaine, verdict: k.verdict, enonce: k.enonce,
113
+ occurrences,
114
+ couverte: occurrences.length > 0,
115
+ transverse,
116
+ erreurs: k.erreurs.length,
117
+ // Une règle métier non rapprochée ne dit rien : elle ne concerne pas cette application.
118
+ erreursNonCouvertes: (transverse || occurrences.length) ? erreursNonCouvertes : [],
119
+ };
120
+ }).sort((a, b) => b.occurrences.length - a.occurrences.length);
121
+ }
122
+
123
+ /** Le rapport en texte — c'est lui qu'on présente. */
124
+ export function rapportDiagnostic(diag, { titre = 'Diagnostic', maxOccurrences = 4 } = {}) {
125
+ const l = [`# ${titre}`, ''];
126
+ const aTrous = diag.filter((d) => d.erreursNonCouvertes.length);
127
+ const reconnues = diag.filter((d) => d.couverte);
128
+ const trous = aTrous.reduce((n, d) => n + d.erreursNonCouvertes.length, 0);
129
+
130
+ l.push(`**${trous}** erreur(s) connue(s) que ce plan ne mentionne nulle part, sur **${aTrous.length}** règle(s) · **${reconnues.length}** règle(s) rapprochée(s) d’au moins une exigence.`, '');
131
+ l.push('> ⚠️ Les rapprochements sont **proposés**, jamais appliqués : une ressemblance de mots n’est pas une identité de règle. Chaque candidat rend **les mots qui l’ont désigné**, pour qu’on puisse le contester.', '');
132
+ l.push('> Les erreurs, elles, sont cherchées dans **tout** le plan — titres, descriptions et épreuves. Une erreur signalée à tort s’écarte en dix secondes ; une erreur tue ne se cherche jamais.', '');
133
+
134
+ l.push('## Ce qui manque', '');
135
+ if (!aTrous.length) l.push('Aucune erreur connue du catalogue n’est absente de ce plan.', '');
136
+ for (const d of aTrous) {
137
+ l.push(`### ${d.kind} — ${d.enonce}`, '');
138
+ if (d.occurrences.length) {
139
+ l.push(`*Reconnue dans ${d.occurrences.length} exigence(s) :* ` + d.occurrences.slice(0, maxOccurrences).map((o) => `\`${o.specRef}\``).join(', ')
140
+ + (d.occurrences.length > maxOccurrences ? ` *(+${d.occurrences.length - maxOccurrences})*` : ''), '');
141
+ } else {
142
+ l.push(`*Règle transverse — aucune exigence de ce plan ne la porte explicitement.*`, '');
143
+ }
144
+ l.push(`**${d.erreursNonCouvertes.length} sur ${d.erreurs} erreur(s) connue(s) ne sont mentionnées nulle part :**`, '');
145
+ for (const e of d.erreursNonCouvertes) l.push(`- **${e.titre}** — ${e.consequence}`);
146
+ l.push('');
147
+ }
148
+
149
+ const propres = reconnues.filter((d) => !d.erreursNonCouvertes.length);
150
+ if (propres.length) {
151
+ l.push('## Ce qui est couvert', '');
152
+ l.push('Ces règles sont reconnues dans le plan, et **toutes** leurs erreurs connues y sont évoquées.', '');
153
+ for (const d of propres) {
154
+ l.push(`- \`${d.kind}\` — ${d.enonce} *(${d.occurrences.slice(0, maxOccurrences).map((o) => o.specRef).join(', ')})*`);
155
+ }
156
+ l.push('');
157
+ }
158
+ const hors = diag.filter((d) => !d.couverte && !d.erreursNonCouvertes.length);
159
+ if (hors.length) {
160
+ l.push('## Hors périmètre apparent', '');
161
+ l.push('Aucune exigence ne les désigne — elles ne concernent probablement pas cette application.', '');
162
+ for (const d of hors) l.push(`- \`${d.kind}\` (${d.domaine})`);
163
+ }
164
+ return l.join('\n');
165
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * ENRICHIR — faire descendre les erreurs connues d'une fiche dans les exigences QUE
3
+ * L'APPLICATION A DÉJÀ.
4
+ *
5
+ * ── PROJETER POUR CRÉER, ENRICHIR POUR EXPLOITER ───────────────────────────
6
+ * `toDevtest` sert à une application NEUVE : elle n'a aucune exigence, la fiche lui donne les
7
+ * siennes. `enrichir` sert à une application qui EN A DÉJÀ — et c'est le cas courant, puisqu'une
8
+ * fiche naît précisément de ce que des applications ont éprouvé.
9
+ *
10
+ * ⚠️ LE CRITÈRE QUI SÉPARE LES DEUX (décision du 03/09/2026, après un passage à qatrax qui l'a
11
+ * rendu visible) :
12
+ *
13
+ * · dans le RÉFÉRENTIEL, une même règle deux fois est une DUPLICATION ;
14
+ * · dans une APPLICATION, la même règle appliquée à un objet précis est une OCCURRENCE
15
+ * d'exploitation, et il en faut autant qu'il y a d'objets.
16
+ *
17
+ * D'où la faute que cette fonction répare : projeter la règle GÉNÉRIQUE dans le plan d'une
18
+ * application ajoutait une exigence qui n'était appliquée à rien. Ni référentiel, ni occurrence :
19
+ * la règle recopiée. ATC portait déjà QUATRE occurrences du périmètre — le portail, le coaching,
20
+ * l'e-learning, le cumul de rôles — et recevait une cinquième exigence qui ne parlait de rien.
21
+ *
22
+ * ⚠️ ELLE N'AJOUTE NI EXIGENCE NI ESSAI. C'est ce qui referme, par construction, le défaut que
23
+ * qatrax avait signalé : des cas de plan que rien ne peut exécuter, parce qu'ils prétendaient
24
+ * qu'un essai portant un autre nom les couvrait.
25
+ *
26
+ * Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
27
+ */
28
+ const propre = (v) => String(v ?? '').trim();
29
+
30
+ /** La borne du bloc ajouté — elle rend l'enrichissement REJOUABLE sans empiler. */
31
+ export const marqueur = (ref) => `⟦${ref}⟧`;
32
+
33
+ /** Le bloc que la fiche dépose dans une exigence de l'application. */
34
+ export function blocDeFiche(k) {
35
+ return [
36
+ marqueur(k.ref),
37
+ `Fiche ${k.ref} v${k.version} (${k.verdict}) — ce que d'autres projets ont payé :`,
38
+ ...k.erreurs.map((e) => `⚠ ${e.titre} — ${e.consequence}`),
39
+ ].join('\n');
40
+ }
41
+
42
+ /**
43
+ * Enrichit des exigences EXISTANTES avec les erreurs connues d'une fiche.
44
+ *
45
+ * @param {object} plan plan `mostajs-devtest/1` de l'application — modifié en place
46
+ * @param {object} o
47
+ * @param {object} o.kind la fiche (ou son instance affinée)
48
+ * @param {string[]} o.specRefs les exigences de l'application qui sont des OCCURRENCES de la règle
49
+ * @returns {{ enrichies: string[], bloc: string }}
50
+ */
51
+ export function enrichir(plan, { kind, specRefs = [] } = {}) {
52
+ if (!plan?.specs) throw new Error('enrichir: plan `mostajs-devtest/1` requis');
53
+ if (!kind?.ref) throw new Error('enrichir: fiche requise');
54
+ if (!specRefs.length) {
55
+ // ⚠️ ENRICHIR SANS DIRE QUOI, C'EST NE RIEN FAIRE — et croire l'avoir fait. On refuse plutôt
56
+ // que de rendre un rapport vide qui passerait pour un succès.
57
+ throw new Error(`enrichir(${kind.ref}) : aucune exigence désignée — une fiche s’applique à des OCCURRENCES, nommez-les`);
58
+ }
59
+
60
+ const bloc = blocDeFiche(kind);
61
+ const enrichies = [];
62
+ for (const ref of specRefs) {
63
+ const s = plan.specs.find((x) => x.ref === propre(ref));
64
+ // ⚠️ UNE EXIGENCE INTROUVABLE EST UNE ERREUR, pas un avertissement : le plus probable est
65
+ // qu'elle a été renommée, et l'enrichissement partirait alors dans le vide en silence.
66
+ if (!s) throw new Error(`enrichir(${kind.ref}) : exigence introuvable — ${ref}`);
67
+ const tete = String(s.description ?? '').split(marqueur(kind.ref))[0].trimEnd();
68
+ s.description = tete ? `${tete}\n\n${bloc}` : bloc;
69
+ enrichies.push(s.ref);
70
+ }
71
+ return { enrichies, bloc };
72
+ }
73
+
74
+ /**
75
+ * Retire d'un plan ce qu'une PROJECTION y avait posé — exigences, réalisations et essais.
76
+ * Utile une fois : quand une application passe de `toDevtest` à `enrichir`.
77
+ */
78
+ export function retirerProjection(plan, { kind, prefix } = {}) {
79
+ const corps = propre(kind?.ref ?? kind).replace(/^KIND-/, '');
80
+ const p = propre(prefix).toUpperCase();
81
+ const cible = { specs: `SPEC-${p}-${corps}`, realisations: `REL-${p}-${corps}`, tests: `T-${p}-${corps}-` };
82
+ const retires = [];
83
+ for (const [cle, debut] of Object.entries(cible)) {
84
+ const reste = [];
85
+ for (const l of plan[cle] ?? []) {
86
+ if (l.ref === debut || String(l.ref).startsWith(debut)) retires.push(l.ref); else reste.push(l);
87
+ }
88
+ plan[cle] = reste;
89
+ }
90
+ return retires;
91
+ }
package/src/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export { defineKind, validateKind, VERDICTS, PROVENANCES, DOMAINES } from './kind.js';
2
2
  export { toDevtest, PLAN_VERSION } from './projection.js';
3
+ export { enrichir, retirerProjection, blocDeFiche, marqueur } from './enrichir.js';
4
+ export { diagnostiquer, rapportDiagnostic, motsCles, normaliser, TRANSVERSES } from './diagnostic.js';
3
5
  export { instantiate, diffInstance, emplois, AFFINABLES } from './instance.js';
4
6
  export { loadCatalogue, findKinds, auditCatalogue, statsCatalogue } from './catalogue.js';