@mostajs/kind-catalog 0.1.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +103 -0
  3. package/docs/00-ETAPE-INAUGURALE-KIND-CATALOG.md +105 -0
  4. package/docs/03bis-PROMPT-IMAGE-OBJECTIF-KIND-CATALOG.md +99 -0
  5. package/docs/09bis-MATRICE-SOURCING-KIND-CATALOG.md +176 -0
  6. package/docs/10-PRESENTATION-COMMERCIALE-KIND-CATALOG.md +140 -0
  7. package/docs/11-SLIDES-COMMERCIAL-KIND-CATALOG.md +154 -0
  8. package/docs/12-DOC-TECHNIQUE-KIND-CATALOG.md +164 -0
  9. package/docs/13-SLIDES-DEV-KIND-CATALOG.md +162 -0
  10. package/docs/14-PROMPT-IMAGE-KIND-CATALOG.md +88 -0
  11. package/docs/15-REVUE-SECURITE-KIND-CATALOG.md +98 -0
  12. package/docs/16-DPIA-CONFORMITE-KIND-CATALOG.md +100 -0
  13. package/docs/ARTICLE-SEO-KIND-CATALOG.md +96 -0
  14. package/docs/AUDIT-EXISTANT-KIND-CATALOG.md +127 -0
  15. package/docs/DEVTEST-PLAN.kind-catalog.json +412 -0
  16. package/docs/ETUDE-ETAT-ART-KIND-CATALOG-02092026.md +207 -0
  17. package/docs/PLAN-DEV-KIND-CATALOG.md +186 -0
  18. package/docs/PLAN-PUBLICATION-KIND-CATALOG.md +80 -0
  19. package/docs/PLAN-SUIVI-MONITORING.md +66 -0
  20. package/docs/PLAN-TESTS.md +79 -0
  21. package/docs/SEO-KEYWORDS-KIND-CATALOG.md +46 -0
  22. package/docs/articles/01-ARTICLE-KIND-CATALOG.md +229 -0
  23. package/kinds/acces.kind.mjs +48 -0
  24. package/kinds/apprentissage.kind.mjs +25 -0
  25. package/kinds/chiffres.kind.mjs +80 -0
  26. package/kinds/decision.kind.mjs +88 -0
  27. package/kinds/donnees.kind.mjs +108 -0
  28. package/kinds/ecran-service.kind.mjs +71 -0
  29. package/kinds/integration.kind.mjs +83 -0
  30. package/kinds/metier-optimisation.kind.mjs +210 -0
  31. package/kinds/traces.kind.mjs +39 -0
  32. package/llms.txt +34 -0
  33. package/package.json +46 -0
  34. package/src/catalogue.js +98 -0
  35. package/src/index.js +4 -0
  36. package/src/instance.js +85 -0
  37. package/src/kind.js +152 -0
  38. package/src/projection.js +76 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * L'INSTANCIATION — une application reprend une fiche, telle quelle ou AFFINÉE.
3
+ *
4
+ * ⚠️ CE QUI FAIT VIVRE UN CATALOGUE, CE N'EST PAS LA RÉUTILISATION À L'IDENTIQUE : c'est
5
+ * **l'écart tracé**. L'agronomie adapte l'itinéraire technique au terroir et relève l'écart ; le
6
+ * bâtiment reprend le DTU et amende **par clause dérogatoire écrite** ; le raisonnement à partir de
7
+ * cas fait de la révision une étape du cycle, pas un accident. Les trois disent la même chose.
8
+ *
9
+ * Sans cette trace, « réutiliser » se dégrade en « recopier », et l'on revient à l'état d'avant le
10
+ * module : des plans qui se ressemblent sans jamais se parler.
11
+ *
12
+ * Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
13
+ */
14
+ import { validateKind } from './kind.js';
15
+
16
+ const propre = (v) => String(v ?? '').trim();
17
+
18
+ /** Les champs qu'une application peut affiner. Le reste appartient à la fiche. */
19
+ export const AFFINABLES = ['enonce', 'besoins', 'utilisation', 'succes', 'erreurs', 'test', 'seProsePose'];
20
+
21
+ /**
22
+ * Instancie une fiche pour une application.
23
+ *
24
+ * @param {object} kind
25
+ * @param {object} o
26
+ * @param {string} o.app l'application qui reprend
27
+ * @param {string} o.prefix préfixe de ses références
28
+ * @param {object} [o.affine] UNIQUEMENT les champs affinés, chacun avec son motif
29
+ * `{ succes: { valeur: [...], motif: '…' } }`
30
+ */
31
+ export function instantiate(kind, { app, prefix, affine = {} } = {}) {
32
+ if (!kind?.ref) throw new Error('instantiate: fiche requise');
33
+ if (!propre(app)) throw new Error('instantiate: `app` requise — une instance sans application ne se retrouve pas');
34
+
35
+ const affinages = [];
36
+ const resultat = { ...kind };
37
+ for (const [champ, val] of Object.entries(affine)) {
38
+ if (!AFFINABLES.includes(champ)) {
39
+ throw new Error(`instantiate(${kind.ref}) : « ${champ} » n’est pas affinable — affinables : ${AFFINABLES.join(', ')}`);
40
+ }
41
+ // ⚠️ LE MOTIF EST EXIGÉ. Un affinage sans motif est une recopie qui s'ignore : six mois plus
42
+ // tard, personne ne sait si l'écart était une nécessité du métier ou une facilité du moment —
43
+ // et c'est justement cet écart qui doit nourrir la version suivante de la fiche.
44
+ if (!propre(val?.motif)) {
45
+ throw new Error(`instantiate(${kind.ref}) : affinage de « ${champ} » sans \`motif\` — sans lui, « réutiliser » se dégrade en « recopier »`);
46
+ }
47
+ affinages.push({ champ, motif: propre(val.motif) });
48
+ resultat[champ] = val.valeur;
49
+ }
50
+
51
+ // L'instance AFFINÉE doit rester une fiche valide : on n'affine pas jusqu'à casser l'exigence.
52
+ const refus = validateKind(resultat);
53
+ if (refus.length) throw new Error(`instantiate(${kind.ref}) : l’affinage casse la fiche — ${refus.join(' · ')}`);
54
+
55
+ return Object.freeze({
56
+ kind: kind.ref,
57
+ kindVersion: kind.version,
58
+ app: propre(app),
59
+ prefix: propre(prefix) || propre(app).toUpperCase(),
60
+ affinages,
61
+ fiche: Object.freeze(resultat),
62
+ });
63
+ }
64
+
65
+ /** Ce que cette application a changé, et pourquoi — lisible d'un coup d'œil. */
66
+ export function diffInstance(instance) {
67
+ if (!instance?.affinages?.length) return [];
68
+ return instance.affinages.map((a) => `${a.champ} : ${a.motif}`);
69
+ }
70
+
71
+ /**
72
+ * QUI EMPLOIE QUOI — l'index inverse.
73
+ * C'est ce qui rend l'affinage sûr dans les deux sens : on sait qui prévenir quand une fiche
74
+ * change, et on lit, en face de chaque fiche, tout ce que le terrain a dû lui faire dire.
75
+ */
76
+ export function emplois(instances = []) {
77
+ const par = new Map();
78
+ for (const i of instances) {
79
+ const e = par.get(i.kind) || { kind: i.kind, apps: [], affinages: [] };
80
+ e.apps.push(i.app);
81
+ for (const a of i.affinages) e.affinages.push({ app: i.app, ...a });
82
+ par.set(i.kind, e);
83
+ }
84
+ return [...par.values()].sort((a, b) => b.apps.length - a.apps.length);
85
+ }
package/src/kind.js ADDED
@@ -0,0 +1,152 @@
1
+ /**
2
+ * @mostajs/kind-catalog — LA FICHE D'EXIGENCE RÉUTILISABLE.
3
+ *
4
+ * ── CE QU'EST UN KIND ──────────────────────────────────────────────────────
5
+ * Une **question qu'un métier se pose**, avec ce qu'il faut pour y répondre, comment on l'éprouve,
6
+ * à quoi on reconnaît une bonne réponse, et **les façons connues de se tromper**.
7
+ *
8
+ * ⚠️ UN KIND N'EST PAS UN ALGORITHME. `@mostajs/ro-pla` porte 23 dialectes pour 12 familles :
9
+ * plusieurs questions métier partagent le même moteur, et un moteur ne dit rien de la question.
10
+ * Ce qu'un exploitant active, c'est une question ; ce qu'un développeur choisit, c'est un moteur.
11
+ *
12
+ * ── CE QUE CE MODULE NE FAIT PAS ───────────────────────────────────────────
13
+ * Il **n'exécute rien** (ni solveur, ni essai, ni règle) et **ne stocke rien** (aucun schéma,
14
+ * aucun dépôt). Il décrit, valide et PROJETTE. Un essai le compte (`T-KC-12`).
15
+ *
16
+ * ── LA PROVENANCE, ET POURQUOI ELLE N'INTERDIT RIEN ────────────────────────
17
+ * Les grands catalogues du monde n'admettent que des faits : ATT&CK ne consigne que des techniques
18
+ * OBSERVÉES, CWE naît de vulnérabilités RÉELLES. Mais « fait » ne veut pas dire « incident chez
19
+ * nous » : un DTU, une fiche HACCP, un itinéraire technique sont des faits établis par un métier
20
+ * entier, et les ignorer laisserait le catalogue vide là où le savoir existe depuis des décennies.
21
+ *
22
+ * D'où la règle réelle : **toute fiche déclare d'où elle vient**, et son VERDICT dit ce qu'elle a
23
+ * traversé.
24
+ *
25
+ * `reference` → tirée d'un corpus établi (norme, guide de bonnes pratiques, littérature)
26
+ * `terrain` → tirée du dire d'un praticien, nommé et daté
27
+ * `incident` → tirée d'un défaut CONSTATÉ, avec sa date et son fichier
28
+ *
29
+ * Une fiche `propose` peut n'avoir qu'une `reference` — et elle a toute sa place au catalogue.
30
+ * Une fiche `eprouve` ou `retenu` exige au moins un `incident` ou un `terrain` : **on ne se
31
+ * décerne pas l'expérience**. La contrainte ne porte donc pas sur l'ENTRÉE au catalogue, elle
32
+ * porte sur le GALON qu'on s'y donne.
33
+ *
34
+ * Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
35
+ */
36
+
37
+ /**
38
+ * Les états d'une fiche.
39
+ * ⚠️ `ecarte` RESTE au catalogue, avec son motif : une fiche écartée qui disparaît est une fiche
40
+ * qui sera réinventée, avec les mêmes espoirs et le même échec.
41
+ */
42
+ export const VERDICTS = ['propose', 'eprouve', 'retenu', 'ecarte'];
43
+
44
+ /** Ce qui atteste qu'une fiche décrit quelque chose de réel. */
45
+ export const PROVENANCES = ['reference', 'terrain', 'incident'];
46
+
47
+ /** Les provenances qui autorisent à se dire ÉPROUVÉ : on ne se décerne pas l'expérience. */
48
+ const PROVENANCES_EPROUVANTES = new Set(['incident', 'terrain']);
49
+
50
+ /**
51
+ * Les domaines déclarés. Vocabulaire OUVERT — mais déclaré : un domaine surgi sans déclaration
52
+ * échappe à la revue, et le catalogue se fragmente en synonymes (`acces`, `access`, `droits`…).
53
+ * Tenu au §5.bis de `docs/PLAN-DEV-KIND-CATALOG.md`.
54
+ */
55
+ export const DOMAINES = [
56
+ // transverses — nés d'incidents de l'écosystème
57
+ 'acces', 'donnees', 'apprentissage', 'decision', 'integration',
58
+ // métier
59
+ 'btp', 'alimentaire', 'elevage', 'apiculture', 'agronomie', 'electronique',
60
+ ];
61
+
62
+ const propre = (v) => String(v ?? '').trim();
63
+ const liste = (v) => (Array.isArray(v) ? v : v == null ? [] : [v]);
64
+
65
+ /**
66
+ * Déclare une fiche. Refuse au montage ce qui la rendrait inutilisable, et NOMME tout ce qui
67
+ * manque d'un seul coup : corriger un manque pour en découvrir un autre fait abandonner.
68
+ */
69
+ export function defineKind(f = {}) {
70
+ const k = {
71
+ ref: propre(f.ref),
72
+ enonce: propre(f.enonce),
73
+ domaine: propre(f.domaine) || 'decision',
74
+ version: propre(f.version) || '1',
75
+ besoins: liste(f.besoins),
76
+ utilisation: propre(f.utilisation),
77
+ succes: liste(f.succes).map(propre).filter(Boolean),
78
+ erreurs: liste(f.erreurs),
79
+ test: liste(f.test),
80
+ // Renvois — jamais recalculés ici : l'efficacité appartient à @mostajs/skill-library, et
81
+ // deux mémoires d'efficacité finiraient par se contredire.
82
+ efficacite: f.efficacite ?? null,
83
+ evaluation: f.evaluation ?? null,
84
+ verdict: propre(f.verdict) || 'propose',
85
+ motif: propre(f.motif) || null,
86
+ origine: liste(f.origine),
87
+ // OÙ la question se pose, quand ce n'est pas là où elle se calcule. Le corpus transverse n'en
88
+ // avait pas besoin ; le BTP (sur chantier) et l'électronique (sur l'embarqué) l'exigent.
89
+ seProsePose: propre(f.seProsePose) || null,
90
+ };
91
+ const refus = validateKind(k);
92
+ if (refus.length) throw new Error(`defineKind(${k.ref || '?'}) : ${refus.join(' · ')}`);
93
+ return Object.freeze(k);
94
+ }
95
+
96
+ /**
97
+ * Tout ce qui cloche, en une fois.
98
+ *
99
+ * ⚠️ APPLIQUE LES MÊMES DÉFAUTS QUE `defineKind`. Sans cela, valider un objet BRUT — celui qu'on
100
+ * vient d'écrire, avant de le passer à `defineKind` — reprochait un `verdict` manquant alors que
101
+ * le champ est facultatif. L'auteur corrigeait un reproche qui n'en était pas un, et la fonction
102
+ * mentait sur son propre contrat (constat du 02/09/2026, par la démonstration `demo-epreuve.mjs`).
103
+ */
104
+ export function validateKind(brut = {}) {
105
+ const k = { verdict: 'propose', domaine: 'decision', ...brut };
106
+ if (!propre(k.verdict)) k.verdict = 'propose';
107
+ if (!propre(k.domaine)) k.domaine = 'decision';
108
+ const out = [];
109
+ if (!propre(k.ref)) out.push('`ref` requise');
110
+ else if (!/^KIND-[A-Z0-9-]+$/.test(k.ref)) out.push(`\`ref\` doit valoir KIND-… (reçu « ${k.ref} »)`);
111
+ if (!propre(k.enonce)) out.push('`enonce` requis — une fiche sans question n’est pas une exigence');
112
+
113
+ if (!DOMAINES.includes(k.domaine)) {
114
+ out.push(`\`domaine\` non déclaré : ${k.domaine} — déclarés : ${DOMAINES.join(', ')}`);
115
+ }
116
+
117
+ // ⚠️ `succes` et `erreurs` sont les deux seuls champs exigés en plus de l'énoncé, et c'est
118
+ // délibéré : ce sont ceux qu'on omet, et ce sont ceux qui servent.
119
+ if (!liste(k.succes).length) out.push('`succes` requis — sans critère écrit, tout résultat paraît bon');
120
+ if (!liste(k.erreurs).length) out.push('`erreurs` requis — une fiche sans façon connue de se tromper n’apprend rien, et c’est la seule raison d’en reprendre une');
121
+
122
+ for (const [i, e] of liste(k.erreurs).entries()) {
123
+ if (!propre(e?.titre)) out.push(`erreurs[${i}] : \`titre\` requis`);
124
+ // La CONSÉQUENCE distingue un avertissement d'une consigne : « ne pas oublier le périmètre »
125
+ // ne se retient pas ; « sans lui, tout parent lit le dossier de tous » se retient. CWE/OWASP.
126
+ if (!propre(e?.consequence)) out.push(`erreurs[${i}] : \`consequence\` requise — un avertissement sans sa conséquence ne se retient pas`);
127
+ }
128
+ for (const [i, t] of liste(k.test).entries()) {
129
+ if (!propre(t?.action)) out.push(`test[${i}] : \`action\` requise`);
130
+ if (!propre(t?.attendu)) out.push(`test[${i}] : \`attendu\` requis`);
131
+ }
132
+
133
+ if (!VERDICTS.includes(k.verdict)) out.push(`\`verdict\` inconnu : ${k.verdict} — attendus : ${VERDICTS.join(', ')}`);
134
+ if (k.verdict === 'ecarte' && !propre(k.motif)) {
135
+ out.push('`motif` requis pour un verdict `ecarte` — une fiche écartée sans motif sera réinventée');
136
+ }
137
+
138
+ // ── LA PROVENANCE ────────────────────────────────────────────────────────
139
+ const origines = liste(k.origine);
140
+ if (!origines.length) out.push('`origine` requise — une fiche sans provenance est une idée déguisée');
141
+ for (const [i, o] of origines.entries()) {
142
+ if (!PROVENANCES.includes(propre(o?.type))) {
143
+ out.push(`origine[${i}] : \`type\` inconnu — attendus : ${PROVENANCES.join(', ')}`);
144
+ }
145
+ if (!propre(o?.source)) out.push(`origine[${i}] : \`source\` requise — norme, praticien ou fichier`);
146
+ }
147
+ if (['eprouve', 'retenu'].includes(k.verdict)
148
+ && !origines.some((o) => PROVENANCES_EPROUVANTES.has(propre(o?.type)))) {
149
+ out.push(`verdict \`${k.verdict}\` exige une origine \`incident\` ou \`terrain\` — on ne se décerne pas l’expérience`);
150
+ }
151
+ return out;
152
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * LA PROJECTION — une fiche devient des EXIGENCES et des ESSAIS au format `mostajs-devtest/1`.
3
+ *
4
+ * ⚠️ AUCUN FORMAT NOUVEAU, et c'est la décision structurante du module. `@mostajs/qa-engine` parse
5
+ * déjà `mostajs-devtest/1`, et qatrax l'importe déjà. Inventer un second format aurait demandé un
6
+ * second validateur, un second import, un second écran — et le jour où les deux divergent, c'est
7
+ * le catalogue qui aurait raison contre les faits.
8
+ *
9
+ * ⚠️ C'EST ICI QUE LE CATALOGUE DEVIENT UTILE. L'état de l'art le montre : CWE est branché aux
10
+ * scanners et s'applique ; le catalogue de patrons d'exigences de Withall n'est branché à rien et
11
+ * est resté un livre. **Une fiche qui ne descend pas dans le plan de test du projet ne sera pas
12
+ * appliquée**, quelle que soit sa qualité.
13
+ *
14
+ * Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
15
+ */
16
+ export const PLAN_VERSION = 'mostajs-devtest/1';
17
+
18
+ const propre = (v) => String(v ?? '').trim();
19
+ /** `KIND-PERIMETRE-01` → `PERIMETRE-01` : la référence projetée porte le préfixe de l'application. */
20
+ const corps = (ref) => propre(ref).replace(/^KIND-/, '');
21
+
22
+ /**
23
+ * Projette des fiches en plan Dev+Test.
24
+ *
25
+ * @param {object[]} kinds
26
+ * @param {object} o
27
+ * @param {{key:string,name:string,description?:string}} o.project le projet, au sens qatrax
28
+ * @param {string} o.prefix préfixe des références — `ATC`, `RESTO`… ISOLE deux applications qui
29
+ * reprennent la même fiche : sans lui, leurs plans se marcheraient dessus dans le même qatrax.
30
+ */
31
+ export function toDevtest(kinds = [], { project, prefix = 'APP' } = {}) {
32
+ if (!project?.key || !project?.name) throw new Error('toDevtest: `project` { key, name } requis');
33
+ const p = propre(prefix).toUpperCase();
34
+ const plan = { plan: PLAN_VERSION, project: { ...project }, specs: [], realisations: [], tests: [] };
35
+
36
+ for (const k of kinds) {
37
+ const specRef = `SPEC-${p}-${corps(k.ref)}`;
38
+ plan.specs.push({
39
+ ref: specRef,
40
+ title: k.enonce,
41
+ priority: 'critical',
42
+ // Le verdict de la FICHE devient le statut de l'exigence : une fiche seulement `propose`
43
+ // ne doit pas apparaître comme vérifiée dans le suivi du projet.
44
+ status: k.verdict === 'eprouve' || k.verdict === 'retenu' ? 'verified' : 'draft',
45
+ // LA DESCRIPTION PORTE LES ERREURS CONNUES. C'est tout l'apport du catalogue : celui qui lit
46
+ // l'exigence dans qatrax reçoit AUSSI ce qui a mal tourné ailleurs, avec la conséquence.
47
+ description: [
48
+ k.utilisation ? `Emploi : ${k.utilisation}` : null,
49
+ k.succes.length ? `Réussi si : ${k.succes.join(' · ')}` : null,
50
+ ...k.erreurs.map((e) => `⚠ ${e.titre} — ${e.consequence}`),
51
+ `Fiche ${k.ref} v${k.version} (${k.verdict})`,
52
+ ].filter(Boolean).join('\n'),
53
+ });
54
+
55
+ plan.realisations.push({
56
+ ref: `REL-${p}-${corps(k.ref)}`,
57
+ specRef,
58
+ kind: 'feature',
59
+ title: `Reprise de la fiche ${k.ref}`,
60
+ progress: 0,
61
+ status: 'todo',
62
+ });
63
+
64
+ k.test.forEach((t, i) => {
65
+ plan.tests.push({
66
+ ref: `T-${p}-${corps(k.ref)}-${i + 1}`,
67
+ specRef,
68
+ title: `T-${p}-${corps(k.ref)}-${i + 1} — ${t.action}`,
69
+ type: 'automated',
70
+ priority: 'critical',
71
+ steps: [{ action: t.action, expected: t.attendu }],
72
+ });
73
+ });
74
+ }
75
+ return plan;
76
+ }