designwithai-mcp 0.10.0 → 0.13.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/README.md +27 -0
- package/dist/cli.js +135 -0
- package/dist/craft-measures.js +744 -0
- package/dist/craft-report.js +298 -0
- package/dist/craft-rules.js +200 -0
- package/dist/craft-store.js +76 -0
- package/dist/ds-inventory.js +275 -1
- package/dist/pr-delta.js +107 -0
- package/dist/registry-events.js +291 -0
- package/dist/registry-git.js +53 -0
- package/dist/registry-read.js +70 -0
- package/dist/server.js +306 -2
- package/dist/source-of-truth.js +29 -0
- package/dist/status-page.js +325 -0
- package/dist/status-update.js +174 -0
- package/dist/truth-store.js +149 -2
- package/package.json +3 -2
- package/skills/_shared/mesures.md +48 -0
- package/skills/better-consistency/SKILL.md +15 -0
- package/skills/better-consistency/evals/cases.json +19 -0
- package/skills/better-interface/SKILL.md +15 -0
- package/skills/better-layout/SKILL.md +15 -0
- package/skills/better-layout/evals/cases.json +44 -0
- package/skills/better-responsive/SKILL.md +15 -0
- package/templates/designwithai-status.yml +71 -0
package/README.md
CHANGED
|
@@ -119,6 +119,9 @@ règles, anti-patterns, prompts, checklists, scorecards), en lecture.
|
|
|
119
119
|
| `save_framing` | Écrit un dossier de cadrage dans le projet, au format que `framing_criteria` relit | refuse la FORME, jamais le contenu ; le **verdict** ne s'y pose qu'après passage devant le juge |
|
|
120
120
|
| `list_framings` | Les cadrages du projet et leur verdict — ou son absence, dite telle quelle | `judged_only` ne rend que ceux qui ont passé leur juge |
|
|
121
121
|
| `submit_annotations` | Reçoit un lot d'éléments POINTÉS dans une app qui tourne (route, sélecteur, note) | normaliseur tolérant ; ce qui n'est pas compris est GARDÉ, ce qui n'est pas rangeable est NOMMÉ |
|
|
122
|
+
| `craft_report` | Mesure les règles de l'art : espacements, tailles, couleurs, rayons, bascules, cibles | **déterministe, zéro modèle** ; dit toujours contre quoi il a mesuré |
|
|
123
|
+
| `list_craft_rules` | Le référentiel : chaque règle, sa mesure, sa tolérance, ses exceptions, sa source | une règle non calculable n'y entre pas |
|
|
124
|
+
| `declare_source_of_truth` *(étendu)* | Déclare aussi le SYSTÈME : base d'espacement, ratio typo, bascules, plancher de cible | ce qui est déclaré fait foi — la convention ne passe jamais devant |
|
|
122
125
|
| `framing_criteria` *(étendu)* | Les critères d'acceptation à vérifier, **projet d'abord**, compte ensuite | la source lue est nommée ; un dossier jamais jugé le dit |
|
|
123
126
|
|
|
124
127
|
**Recherche sémantique** (optionnelle, sans clé) : `npm run embed` pré-calcule des embeddings locaux (modèle multilingue, ONNX) → `handbook-embeddings.json`. Présent, `search_handbook` devient hybride (mots-clés + sens) ; absent, il retombe sur les mots-clés seuls. À relancer après chaque régénération de `handbook.json`.
|
|
@@ -196,6 +199,30 @@ ni nouveaux.
|
|
|
196
199
|
Les lots vivent dans `.designwithai/annotations/` (`DWAI_ANNOTATIONS_PATH`).
|
|
197
200
|
Un constat sans ancre suit exactement le chemin d'avant.
|
|
198
201
|
|
|
202
|
+
### Les règles de l'art, mesurées et non récitées
|
|
203
|
+
|
|
204
|
+
Les conventions du métier — base 8, échelles à ratio, plages de bascule — entrent
|
|
205
|
+
dans le produit **codifiées**, pas en dogme dans un prompt. Chaque règle est un
|
|
206
|
+
objet (`craft-rules.ts`) : un identifiant stable, la fonction qui la calcule
|
|
207
|
+
(`craft-measures.ts`, pur et sans modèle), sa tolérance, ses exceptions, et le
|
|
208
|
+
chapitre du manuel qui la fonde — vérifié contre le manuel par un test.
|
|
209
|
+
|
|
210
|
+
**Une règle qu'aucune fonction ne peut calculer n'entre pas dans le
|
|
211
|
+
référentiel.** Elle reste dans le `SKILL.md` du juge concerné, où elle a
|
|
212
|
+
toujours vécu.
|
|
213
|
+
|
|
214
|
+
Et la préséance, qui empêche le référentiel de devenir un dogme :
|
|
215
|
+
|
|
216
|
+
1. **le système DÉCLARÉ fait foi.** Une base de 5 pixels déclarée et tenue n'est
|
|
217
|
+
pas un défaut de grille ;
|
|
218
|
+
2. à défaut, **la cohérence interne** mesurée sur le code ;
|
|
219
|
+
3. à défaut de tout, **la convention** — annoncée comme telle dans la phrase.
|
|
220
|
+
|
|
221
|
+
Un rapport dit toujours contre quoi il a mesuré. `better-layout`,
|
|
222
|
+
`better-responsive`, `better-interface` et `better-consistency` appellent
|
|
223
|
+
`craft_report` en premier : ils interprètent et priorisent des chiffres, ils ne
|
|
224
|
+
les refont pas à l'œil.
|
|
225
|
+
|
|
199
226
|
**Prompts** — les 10 skills de `skills/` exposés comme prompts MCP (`/better-layout`,
|
|
200
227
|
`/better-interface`, `/review-ai-product`…). Chaque prompt est *self-contained* :
|
|
201
228
|
socle commun + corps du skill + invocation renseignée par les arguments
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LE MÊME MOTEUR, EN LIGNE DE COMMANDE.
|
|
3
|
+
*
|
|
4
|
+
* Une page d'état qui n'existe que dans un IDE ne sert pas l'équipe : elle sert
|
|
5
|
+
* la personne qui a l'IDE. La CI, elle, n'a pas de client MCP — elle a un shell.
|
|
6
|
+
* Ces sous-commandes exposent EXACTEMENT les mêmes fonctions de rendu que les
|
|
7
|
+
* outils MCP, sans en dupliquer une ligne. Une définition, un endroit : si la
|
|
8
|
+
* page change, elle change aux deux endroits parce qu'il n'y en a qu'un.
|
|
9
|
+
*
|
|
10
|
+
* Tout va sur la sortie standard, pour que `>` et `|` fassent leur travail.
|
|
11
|
+
*/
|
|
12
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { workspaceRoot } from "./paths.js";
|
|
15
|
+
import { lireRegistre } from "./registry-read.js";
|
|
16
|
+
import { rendreStatusPage } from "./status-page.js";
|
|
17
|
+
import { dernierUpdate, enregistrerUpdate, rendreStatusUpdate } from "./status-update.js";
|
|
18
|
+
import { delta, rendreDelta } from "./pr-delta.js";
|
|
19
|
+
import { lireReviewsA } from "./registry-git.js";
|
|
20
|
+
import { constats } from "./pr-delta.js";
|
|
21
|
+
export const SOUS_COMMANDES = ["status-page", "status-update", "pr-delta", "--help", "-h"];
|
|
22
|
+
const AIDE = `designwithai-mcp — le serveur MCP du Handbook, et trois rendus en ligne de commande.
|
|
23
|
+
|
|
24
|
+
designwithai-mcp démarre le serveur MCP (stdio)
|
|
25
|
+
|
|
26
|
+
designwithai-mcp status-page [--out F] [--product N]
|
|
27
|
+
Écrit la page d'état du produit, dérivée du registre.
|
|
28
|
+
Défaut : docs/etat-produit/index.html. --out - écrit sur la sortie standard.
|
|
29
|
+
|
|
30
|
+
designwithai-mcp status-update [--since ISO] [--product N] [--no-save]
|
|
31
|
+
Écrit le point d'avancement en Markdown sur la sortie standard.
|
|
32
|
+
Sans --since : depuis le dernier point enregistré.
|
|
33
|
+
|
|
34
|
+
designwithai-mcp pr-delta --base <ref> [--head <ref>] [--branch N]
|
|
35
|
+
Compare le registre de la base à celui du répertoire courant (ou de --head)
|
|
36
|
+
et rend le commentaire de PR. Suppose que .designwithai/ est VERSIONNÉ.
|
|
37
|
+
|
|
38
|
+
Le registre vit dans .designwithai/ du projet courant (DWAI_WORKSPACE pour l'imposer).`;
|
|
39
|
+
function option(argv, nom) {
|
|
40
|
+
const i = argv.indexOf(`--${nom}`);
|
|
41
|
+
if (i === -1)
|
|
42
|
+
return undefined;
|
|
43
|
+
const v = argv[i + 1];
|
|
44
|
+
return v && !v.startsWith("--") ? v : undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Rend vrai quand une sous-commande a été traitée — le serveur ne doit alors
|
|
48
|
+
* PAS ouvrir stdio : un client MCP qui reçoit du Markdown sur stdout croit
|
|
49
|
+
* lire du protocole et se coupe sans message lisible.
|
|
50
|
+
*/
|
|
51
|
+
export async function traiterCli(argv) {
|
|
52
|
+
const cmd = argv[0];
|
|
53
|
+
if (!cmd)
|
|
54
|
+
return false;
|
|
55
|
+
if (cmd === "--help" || cmd === "-h") {
|
|
56
|
+
process.stdout.write(`${AIDE}\n`);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if (cmd === "status-page") {
|
|
60
|
+
const r = lireRegistre();
|
|
61
|
+
const produit = option(argv, "product");
|
|
62
|
+
const html = rendreStatusPage(r, {
|
|
63
|
+
maintenant: new Date().toISOString(),
|
|
64
|
+
...(produit ? { produit } : {}),
|
|
65
|
+
});
|
|
66
|
+
const out = option(argv, "out") ?? "docs/etat-produit/index.html";
|
|
67
|
+
if (out === "-") {
|
|
68
|
+
process.stdout.write(html);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
const cible = path.resolve(workspaceRoot(), out);
|
|
72
|
+
mkdirSync(path.dirname(cible), { recursive: true });
|
|
73
|
+
writeFileSync(cible, html, "utf8");
|
|
74
|
+
process.stderr.write(`Page écrite : ${path.relative(workspaceRoot(), cible)}\n`);
|
|
75
|
+
/* Les fichiers illisibles vont sur stderr ET dans la page. Un registre à
|
|
76
|
+
moitié lu qui rendrait une page sereine serait le pire de cette surface. */
|
|
77
|
+
for (const i of r.illisibles)
|
|
78
|
+
process.stderr.write(`⚠ illisible : ${i.chemin} — ${i.raison}\n`);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (cmd === "status-update") {
|
|
82
|
+
const r = lireRegistre();
|
|
83
|
+
const maintenant = new Date();
|
|
84
|
+
const depuis = option(argv, "since") ??
|
|
85
|
+
dernierUpdate() ??
|
|
86
|
+
new Date(maintenant.getTime() - 30 * 24 * 3600 * 1000).toISOString();
|
|
87
|
+
const produit = option(argv, "product");
|
|
88
|
+
const md = rendreStatusUpdate(r, {
|
|
89
|
+
depuis,
|
|
90
|
+
maintenant: maintenant.toISOString(),
|
|
91
|
+
...(produit ? { produit } : {}),
|
|
92
|
+
});
|
|
93
|
+
process.stdout.write(`${md}\n`);
|
|
94
|
+
if (!argv.includes("--no-save")) {
|
|
95
|
+
const chemin = enregistrerUpdate(md, maintenant);
|
|
96
|
+
process.stderr.write(`Point enregistré : ${path.relative(workspaceRoot(), chemin)}\n`);
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
if (cmd === "pr-delta") {
|
|
101
|
+
const base = option(argv, "base");
|
|
102
|
+
if (!base) {
|
|
103
|
+
process.stderr.write("pr-delta a besoin de --base <ref>.\n");
|
|
104
|
+
process.exitCode = 2;
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const cwd = workspaceRoot();
|
|
108
|
+
let avant;
|
|
109
|
+
try {
|
|
110
|
+
avant = lireReviewsA(base, cwd);
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
process.stderr.write(`${e instanceof Error ? e.message : String(e)}\n`);
|
|
114
|
+
process.exitCode = 2;
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
const tete = option(argv, "head");
|
|
118
|
+
const apres = tete ? lireReviewsA(tete, cwd).reviews : lireRegistre().reviews;
|
|
119
|
+
if (avant.absent) {
|
|
120
|
+
process.stdout.write(`${rendreDelta({ reglesProuves: [], declaresCorriges: [], nouveaux: [], deplaces: [], persistants: [] }, { base, baseSansRegistre: true })}\n`);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
const d = delta(avant.reviews, apres);
|
|
124
|
+
const branche = option(argv, "branch");
|
|
125
|
+
process.stdout.write(`${rendreDelta(d, { ...(branche ? { branche } : {}), base })}\n`);
|
|
126
|
+
if (avant.illisibles.length > 0) {
|
|
127
|
+
process.stderr.write(`⚠ ${avant.illisibles.length} fichier(s) illisible(s) côté base : la comparaison est incomplète.\n`);
|
|
128
|
+
}
|
|
129
|
+
void constats;
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
process.stderr.write(`Sous-commande inconnue : ${cmd}\n\n${AIDE}\n`);
|
|
133
|
+
process.exitCode = 2;
|
|
134
|
+
return true;
|
|
135
|
+
}
|