@nodefony/devkit 10.0.0-beta.3 → 10.0.0-beta.4
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 +146 -4
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
- package/dist/index.js +24 -11
- package/dist/nodefony/config/config.js +16 -137
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/controllers/DevkitController.js +7 -14
- package/dist/nodefony/controllers/McpController.js +25 -226
- package/dist/nodefony/controllers/ProfileSessionController.js +47 -0
- package/dist/nodefony/mcp/observe.js +235 -0
- package/dist/nodefony/mcp/profileReport.js +242 -0
- package/dist/nodefony/mcp/profileRoute.js +521 -0
- package/dist/nodefony/mcp/shotClient.js +84 -0
- package/dist/nodefony/mcp/shutdown.js +44 -0
- package/dist/nodefony/mcp/tools.js +696 -0
- package/dist/nodefony/service/DevkitService.js +93 -29
- package/dist/nodefony/src/errors/DevkitError.js +1 -2
- package/dist/nodefony/src/profileSession.js +14 -0
- package/dist/types/index.d.ts +15 -1
- package/dist/types/nodefony/config/config.d.ts +1 -1
- package/dist/types/nodefony/controllers/McpController.d.ts +11 -26
- package/dist/types/nodefony/controllers/ProfileSessionController.d.ts +31 -0
- package/dist/types/nodefony/interfaces/IDevkitService.d.ts +22 -1
- package/dist/types/nodefony/mcp/observe.d.ts +83 -0
- package/dist/types/nodefony/mcp/profileReport.d.ts +138 -0
- package/dist/types/nodefony/mcp/profileRoute.d.ts +87 -0
- package/dist/types/nodefony/mcp/shotClient.d.ts +53 -0
- package/dist/types/nodefony/mcp/shutdown.d.ts +25 -0
- package/dist/types/nodefony/mcp/tools.d.ts +109 -0
- package/dist/types/nodefony/service/DevkitService.d.ts +41 -4
- package/dist/types/nodefony/src/profileSession.d.ts +12 -0
- package/docs/index.md +160 -11
- package/package.json +16 -11
- package/skills/nodefony-add-crud/SKILL.md +5 -0
- package/skills/nodefony-add-service/SKILL.md +3 -3
- package/skills/nodefony-authentication/SKILL.md +5 -5
- package/skills/nodefony-browser/SKILL.md +20 -0
- package/skills/nodefony-browser/references/socket.md +13 -4
- package/skills/nodefony-browser/scripts/inspect.mjs +1 -1
- package/skills/nodefony-browser/scripts/lib/probes.mjs +53 -0
- package/skills/nodefony-browser/scripts/socket.mjs +70 -4
- package/skills/nodefony-browser/scripts/watch.mjs +101 -0
- package/skills/nodefony-dev/SKILL.md +31 -1
- package/skills/nodefony-dev/scripts/docs.mjs +3 -3
- package/skills/nodefony-devops/SKILL.md +85 -64
- package/skills/nodefony-devops/references/dimensionnement.md +122 -0
- package/skills/nodefony-devops/references/frontal.md +277 -47
- package/skills/nodefony-devops/references/kubernetes.md +41 -14
- package/skills/nodefony-devops/references/secrets.md +13 -2
- package/skills/nodefony-devops/references/variables.md +14 -12
- package/skills/nodefony-protect-route/SKILL.md +6 -4
- package/skills/nodefony-security-review/scripts/review-diff.mjs +4 -4
- package/skills/nodefony-troubleshoot/SKILL.md +86 -12
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type IMcpCaller, type IMcpTool, type IRolesWithin } from "nodefony";
|
|
2
|
+
import { type IProfilerReader } from "@nodefony/http";
|
|
3
|
+
import { type IObserveProbe } from "./observe.js";
|
|
4
|
+
import { type RequestLogs } from "./profileReport.js";
|
|
5
|
+
import { type IShotClient, type IShotTarget } from "./shotClient.js";
|
|
6
|
+
import { type OnShutdown } from "./shutdown.js";
|
|
7
|
+
/** Requêtes mesurées par défaut. */
|
|
8
|
+
export declare const PROFILE_ROUTE_DEFAULT_COUNT = 50;
|
|
9
|
+
/**
|
|
10
|
+
* Requêtes mesurées au plus. Avec la chauffe, sous les 500 profils que garde le
|
|
11
|
+
* profileur : au-delà, les premiers seraient évincés avant d'être lus.
|
|
12
|
+
*/
|
|
13
|
+
export declare const PROFILE_ROUTE_MAX_COUNT = 200;
|
|
14
|
+
/** Requêtes simultanées par défaut. */
|
|
15
|
+
export declare const PROFILE_ROUTE_DEFAULT_CONCURRENCY = 4;
|
|
16
|
+
/** Requêtes simultanées au plus : mesurer une route, pas saturer le process. */
|
|
17
|
+
export declare const PROFILE_ROUTE_MAX_CONCURRENCY = 8;
|
|
18
|
+
/** Requêtes de chauffe par défaut, tirées AVANT et hors des mesures. */
|
|
19
|
+
export declare const PROFILE_ROUTE_DEFAULT_WARMUP = 3;
|
|
20
|
+
/** Requêtes de chauffe au plus. */
|
|
21
|
+
export declare const PROFILE_ROUTE_MAX_WARMUP = 20;
|
|
22
|
+
/**
|
|
23
|
+
* Budget de temps d'un tir, en ms. Sous les 60 s qu'un client MCP attend par
|
|
24
|
+
* défaut : au-delà, l'agent couperait l'appel juste avant le rapport.
|
|
25
|
+
*/
|
|
26
|
+
export declare const PROFILE_ROUTE_BUDGET_MS = 45000;
|
|
27
|
+
/**
|
|
28
|
+
* Attente maximale, en ms, d'un profil après la réponse : le profileur le
|
|
29
|
+
* collecte au `close` de la réponse, émis en différé — rien ne garantit qu'il
|
|
30
|
+
* précède la fin de lecture côté client, dans le même process.
|
|
31
|
+
*/
|
|
32
|
+
export declare const PROFILE_COLLECT_WAIT_MS = 500;
|
|
33
|
+
/** Ce dont l'outil a besoin — injecté, pour l'éprouver sans application. */
|
|
34
|
+
export interface IProfileRouteDeps {
|
|
35
|
+
/** Le profileur de l'application, ou `undefined` hors développement. */
|
|
36
|
+
getProfiler: () => IProfilerReader | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Ce process sert-il des requêtes ? Rend `false` sous la porte stdio :
|
|
39
|
+
* l'outil le dit ({@link NOT_SERVING_MESSAGE}). Absent : supposé servir.
|
|
40
|
+
*/
|
|
41
|
+
servesTraffic?: (() => boolean) | undefined;
|
|
42
|
+
/** L'adresse d'écoute HTTP en clair de l'application, ou `null`. */
|
|
43
|
+
getTarget: () => IShotTarget | null;
|
|
44
|
+
/** Fabrique du client du tir (défaut : `node:http`, cf {@link createShotClient}). */
|
|
45
|
+
createClient?: ((target: IShotTarget, concurrency: number) => IShotClient) | undefined;
|
|
46
|
+
/** Fabrique de la sonde du process (défaut : `ProcessProbe` du cœur). */
|
|
47
|
+
createProbe?: (() => IObserveProbe) | undefined;
|
|
48
|
+
/** Émet le ticket à usage unique qui ouvrira la session de `identifier`. */
|
|
49
|
+
issueTicket?: ((identifier: string) => string) | undefined;
|
|
50
|
+
/** Détruit la session de profilage désignée par son pseudonyme. */
|
|
51
|
+
closeSession?: ((ref: string) => Promise<unknown>) | undefined;
|
|
52
|
+
/** Les rôles de `target` sont-ils inclus dans ceux de `owner` (hiérarchie comprise) ? */
|
|
53
|
+
rolesWithin?: IRolesWithin | undefined;
|
|
54
|
+
/** Lit les lignes de journal d'une requête en erreur. */
|
|
55
|
+
readRequestLogs?: ((requestId: string, caller: IMcpCaller) => Promise<RequestLogs>) | undefined;
|
|
56
|
+
/** S'abonne à l'arrêt du serveur pour la durée d'un appel. */
|
|
57
|
+
onShutdown?: OnShutdown | undefined;
|
|
58
|
+
/** Attente d'un profil après sa réponse (défaut {@link PROFILE_COLLECT_WAIT_MS}). */
|
|
59
|
+
collectWaitMs?: number | undefined;
|
|
60
|
+
/** Budget du tir (défaut {@link PROFILE_ROUTE_BUDGET_MS}). */
|
|
61
|
+
budgetMs?: number | undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Contrôle le chemin à profiler : un CHEMIN de l'application, jamais une URL —
|
|
65
|
+
* l'outil ne doit pouvoir viser qu'elle.
|
|
66
|
+
*
|
|
67
|
+
* @param raw - l'argument `path`, non fiable.
|
|
68
|
+
* @returns le chemin normalisé (fragment retiré), ou la raison du refus.
|
|
69
|
+
*/
|
|
70
|
+
export declare function checkProfilePath(raw: unknown): {
|
|
71
|
+
ok: true;
|
|
72
|
+
path: string;
|
|
73
|
+
} | {
|
|
74
|
+
ok: false;
|
|
75
|
+
reason: string;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* L'outil `nodefony_profile_route` : envoie lui-même N requêtes à une route de
|
|
79
|
+
* l'application, au nom d'un compte s'il le faut, et rend leur profil agrégé.
|
|
80
|
+
*
|
|
81
|
+
* Rien n'est armé hors d'un appel : le client du tir (ses connexions), la
|
|
82
|
+
* session de profilage et l'écoute de l'arrêt du serveur naissent avec l'appel
|
|
83
|
+
* et sont libérés à sa sortie, quelle qu'elle soit.
|
|
84
|
+
*
|
|
85
|
+
* @param deps - profileur, adresse d'écoute, ouverture de session, journal.
|
|
86
|
+
*/
|
|
87
|
+
export declare function profileRouteTool(deps: IProfileRouteDeps): IMcpTool;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Délai d'une requête du tir, en ms : au-delà, elle compte « sans réponse ». */
|
|
2
|
+
export declare const SHOT_REQUEST_TIMEOUT_MS = 15000;
|
|
3
|
+
/** Où l'application écoute, vue de son propre process. */
|
|
4
|
+
export interface IShotTarget {
|
|
5
|
+
/** Adresse de connexion (boucle locale). */
|
|
6
|
+
host: string;
|
|
7
|
+
port: number;
|
|
8
|
+
/** En-tête `Host` envoyé : le domaine que l'application attend. */
|
|
9
|
+
hostHeader: string;
|
|
10
|
+
}
|
|
11
|
+
/** Une requête du tir. */
|
|
12
|
+
export interface IShotRequest {
|
|
13
|
+
method: "GET" | "HEAD" | "POST";
|
|
14
|
+
path: string;
|
|
15
|
+
headers: Readonly<Record<string, string>>;
|
|
16
|
+
/** Lire le corps de la réponse (connexion seulement ; un tir le jette). */
|
|
17
|
+
readBody?: boolean | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** Ce que le client retient d'une réponse. */
|
|
20
|
+
export interface IShotResponse {
|
|
21
|
+
status: number;
|
|
22
|
+
location: string | null;
|
|
23
|
+
setCookie: readonly string[];
|
|
24
|
+
body: string | null;
|
|
25
|
+
}
|
|
26
|
+
/** Le client d'UN tir : ses connexions vivent le temps de l'appel. */
|
|
27
|
+
export interface IShotClient {
|
|
28
|
+
send(request: IShotRequest, signal: AbortSignal): Promise<IShotResponse>;
|
|
29
|
+
/** Ferme les connexions gardées ouvertes ; les requêtes en vol échouent. */
|
|
30
|
+
close(): void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Client `node:http` d'un tir : connexions gardées ouvertes entre deux requêtes,
|
|
34
|
+
* comme un navigateur, au plus `concurrency` à la fois, toutes fermées par
|
|
35
|
+
* `close()`. Un `fetch` passerait par un pool global, qui survivrait à l'appel.
|
|
36
|
+
*
|
|
37
|
+
* @param target - l'adresse d'écoute de l'application.
|
|
38
|
+
* @param concurrency - connexions simultanées au plus.
|
|
39
|
+
* @returns le client, à fermer en fin de tir.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createShotClient(target: IShotTarget, concurrency: number): IShotClient;
|
|
42
|
+
/**
|
|
43
|
+
* Lit l'adresse d'écoute du serveur HTTP en clair de l'application (service
|
|
44
|
+
* `server-http`), telle qu'il l'a réellement obtenue — port de repli compris.
|
|
45
|
+
*
|
|
46
|
+
* Une écoute sur toutes les interfaces (`0.0.0.0`, `::`) se joint par la boucle
|
|
47
|
+
* locale : c'est aussi ce qui rend l'appel LOCAL pour la route de connexion du
|
|
48
|
+
* profilage.
|
|
49
|
+
*
|
|
50
|
+
* @param server - ce que le conteneur rend pour `server-http`.
|
|
51
|
+
* @returns la cible, ou `null` si aucun serveur n'est prêt.
|
|
52
|
+
*/
|
|
53
|
+
export declare function readShotTarget(server: unknown): IShotTarget | null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IMcpToolRun } from "nodefony";
|
|
2
|
+
/** Abonnement à l'arrêt du serveur, pour la durée d'UN appel d'outil. */
|
|
3
|
+
export type OnShutdown = (listener: () => Promise<void>) => () => void;
|
|
4
|
+
/** Ce qu'un outil long lit pour savoir s'il doit s'arrêter, et pourquoi. */
|
|
5
|
+
export interface IToolStop {
|
|
6
|
+
/** Avorté quand l'agent abandonne l'appel OU que le serveur s'arrête. */
|
|
7
|
+
readonly signal: AbortSignal;
|
|
8
|
+
/** `true` si c'est l'arrêt du serveur qui a clos l'appel. */
|
|
9
|
+
readonly interrupted: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Le rapport est rendu : l'arrêt du serveur, s'il attendait, reprend, et
|
|
12
|
+
* l'écouteur quitte le kernel. À appeler sur TOUS les chemins de sortie.
|
|
13
|
+
*/
|
|
14
|
+
release(): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Arme l'arrêt d'un outil long : l'agent qui abandonne, ou le serveur qui
|
|
18
|
+
* s'arrête (rechargement après une modification de fichier). Dans le second
|
|
19
|
+
* cas, l'arrêt attend — borné — que le rapport partiel soit parti.
|
|
20
|
+
*
|
|
21
|
+
* @param run - l'appel en cours, porteur du signal d'abandon de l'agent.
|
|
22
|
+
* @param onShutdown - abonnement à l'arrêt du serveur ; absent hors application.
|
|
23
|
+
* @returns le signal combiné, la cause, et la libération.
|
|
24
|
+
*/
|
|
25
|
+
export declare function watchToolStop(run: IMcpToolRun | undefined, onShutdown: OnShutdown | undefined): IToolStop;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { skippedChecks } from "nodefony";
|
|
2
|
+
import type { IDoctorReport, IAdminBrokerLike, IMcpTool } from "nodefony";
|
|
3
|
+
import type { IProfilerReader } from "@nodefony/http";
|
|
4
|
+
import { type IProfileRouteDeps } from "./profileRoute.js";
|
|
5
|
+
/** Ce dont les outils INTÉGRÉS ont besoin — injecté, jamais lu ici. */
|
|
6
|
+
export interface IMcpToolDeps {
|
|
7
|
+
/** Le service `adminBroker` du conteneur, ou `undefined` s'il manque. */
|
|
8
|
+
broker: IAdminBrokerLike | undefined;
|
|
9
|
+
/** Compose la carte de visite de l'application. */
|
|
10
|
+
getCard: () => unknown;
|
|
11
|
+
/**
|
|
12
|
+
* Racine depuis laquelle lire le disque (graphe symbolique, diagnostic).
|
|
13
|
+
*
|
|
14
|
+
* Injectée plutôt que lue par `process.cwd()` : le serveur répond dans le
|
|
15
|
+
* process de l'application, dont le dossier courant n'est pas garanti être
|
|
16
|
+
* celui du projet — et un outil qui diagnostiquerait le mauvais dossier
|
|
17
|
+
* conclurait « rien à signaler » avec aplomb.
|
|
18
|
+
*/
|
|
19
|
+
projectRoot: string;
|
|
20
|
+
/**
|
|
21
|
+
* Le profileur de développement (`"profiler"` du conteneur), ou `undefined`
|
|
22
|
+
* hors développement : `nodefony_observe` le dit au lieu de planter.
|
|
23
|
+
*/
|
|
24
|
+
getProfiler: () => IProfilerReader | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Ce process sert-il des requêtes ? `false` sous la porte stdio, où les
|
|
27
|
+
* outils de MESURE refusent en le disant ; absent : supposé servir.
|
|
28
|
+
*/
|
|
29
|
+
servesTraffic?: (() => boolean) | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* S'abonne à l'arrêt du serveur pour la durée d'un appel (rapport partiel de
|
|
32
|
+
* `nodefony_observe`) ; absent hors application.
|
|
33
|
+
*/
|
|
34
|
+
onShutdown?: ((listener: () => Promise<void>) => () => void) | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Ce que `nodefony_profile_route` demande à l'application : son adresse
|
|
37
|
+
* d'écoute, l'ouverture et la fermeture de la session de profilage, la
|
|
38
|
+
* comparaison des rôles de deux comptes. Absent hors application : l'outil
|
|
39
|
+
* dit qu'il n'a rien à viser.
|
|
40
|
+
*/
|
|
41
|
+
profileRoute?: Pick<IProfileRouteDeps, "getTarget" | "issueTicket" | "closeSession" | "rolesWithin"> | undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Clés des outils intégrés — ce que l'allowlist de configuration nomme.
|
|
45
|
+
*
|
|
46
|
+
* Publiée pour qu'un schéma de configuration puisse la valider au lieu de la
|
|
47
|
+
* retaper : une clé inconnue en config est silencieusement sans effet, et
|
|
48
|
+
* l'utilisateur croit avoir activé quelque chose.
|
|
49
|
+
*/
|
|
50
|
+
export declare const BUILTIN_MCP_TOOL_KEYS: readonly ["inspect", "card", "check", "symbols", "docs", "admin_list", "admin_call", "observe", "profile_route"];
|
|
51
|
+
/** Clé d'un outil intégré. */
|
|
52
|
+
export type BuiltinMcpToolKey = (typeof BUILTIN_MCP_TOOL_KEYS)[number];
|
|
53
|
+
/** Contrôles sautés pour la MÊME raison : la raison une fois, ses familles ensuite. */
|
|
54
|
+
export interface ISkippedCheckGroup {
|
|
55
|
+
/** Famille → titre, dans l'ordre de lecture du rapport. */
|
|
56
|
+
families: Record<string, string>;
|
|
57
|
+
reason: string;
|
|
58
|
+
unlock?: string | undefined;
|
|
59
|
+
onDemand?: boolean | undefined;
|
|
60
|
+
notApplicable?: boolean | undefined;
|
|
61
|
+
advisory?: boolean | undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Regroupe les contrôles sautés qui disent exactement la même chose.
|
|
65
|
+
*
|
|
66
|
+
* Sur l'application de développement, sept familles sautées partagent trois
|
|
67
|
+
* raisons : les répéter famille par famille coûtait ~1 800 caractères à chaque
|
|
68
|
+
* appel, relus ensuite à chaque tour de l'agent.
|
|
69
|
+
*
|
|
70
|
+
* @param skipped - les contrôles sautés, dans l'ordre du rapport
|
|
71
|
+
* @returns un groupe par raison distincte, dans l'ordre de première apparition
|
|
72
|
+
*/
|
|
73
|
+
export declare function groupSkippedChecks(skipped: ReturnType<typeof skippedChecks>): ISkippedCheckGroup[];
|
|
74
|
+
/**
|
|
75
|
+
* Taille le rapport de diagnostic pour un AGENT : les mêmes faits, sans redite.
|
|
76
|
+
*
|
|
77
|
+
* Le `--json` de `nodefony doctor` reste complet ; seule la porte MCP taille,
|
|
78
|
+
* parce que ce qu'elle rend se relit à chaque tour suivant de l'agent. Trois
|
|
79
|
+
* coupes : `execution` (que `skipped` résume déjà), les raisons de `skipped`
|
|
80
|
+
* regroupées ({@link groupSkippedChecks}), les liens `open` des derniers
|
|
81
|
+
* démarrages (faits pour un humain qui clique).
|
|
82
|
+
*
|
|
83
|
+
* @param report - le rapport de `collectDoctorReport`
|
|
84
|
+
* @returns le verdict, son compte, puis le rapport sans redite
|
|
85
|
+
*/
|
|
86
|
+
export declare function compactDoctorReport(report: IDoctorReport): Record<string, unknown>;
|
|
87
|
+
/**
|
|
88
|
+
* Catalogue intégré du framework, indexé par clé d'allowlist.
|
|
89
|
+
*
|
|
90
|
+
* Chaque handler capture ses dépendances par fermeture : c'est ce qui permet à
|
|
91
|
+
* ces outils d'être des {@link IMcpTool} ordinaires, du même type que
|
|
92
|
+
* ceux d'une application — le serveur n'a plus aucun cas particulier à traiter.
|
|
93
|
+
*
|
|
94
|
+
* @param deps - briques qui répondent réellement
|
|
95
|
+
*/
|
|
96
|
+
export declare function builtinMcpTools(deps: IMcpToolDeps): Record<BuiltinMcpToolKey, IMcpTool>;
|
|
97
|
+
/**
|
|
98
|
+
* Les outils du catalogue que l'allowlist de configuration retient.
|
|
99
|
+
*
|
|
100
|
+
* C'est la forme sous laquelle `@nodefony/devkit` les déclare (`getMcpTools()`) :
|
|
101
|
+
* des {@link IMcpTool} ordinaires, collectés par `@nodefony/ai` comme ceux de
|
|
102
|
+
* n'importe quel module — seul le préfixe `nodefony_`, réservé aux modules
|
|
103
|
+
* `@nodefony/*`, les distingue.
|
|
104
|
+
*
|
|
105
|
+
* @param allowlist - clés courtes retenues (cf {@link BUILTIN_MCP_TOOL_KEYS})
|
|
106
|
+
* @param deps - briques qui répondent réellement
|
|
107
|
+
* @returns les outils retenus, dans l'ordre de l'allowlist
|
|
108
|
+
*/
|
|
109
|
+
export declare function selectBuiltinMcpTools(allowlist: readonly string[], deps: IMcpToolDeps): IMcpTool[];
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Service, Module } from "nodefony";
|
|
2
|
-
import type {
|
|
2
|
+
import type { IMcpTool, IProtectedResourceInput } from "nodefony";
|
|
3
|
+
import { type IMcpGate, type ITicketSessionGate } from "@nodefony/ai";
|
|
4
|
+
import { type IMcpToolDeps } from "../mcp/tools.js";
|
|
3
5
|
import type { IDevkitCard, IDevkitService } from "../interfaces/IDevkitService.js";
|
|
4
6
|
import { type IDevkitConfig } from "../config/config.js";
|
|
5
7
|
/**
|
|
@@ -12,7 +14,7 @@ import { type IDevkitConfig } from "../config/config.js";
|
|
|
12
14
|
*
|
|
13
15
|
* Un service porte DEUX noms, et c'est normal :
|
|
14
16
|
* `@injectable()` → nomme la CLASSE (`DevkitService`),
|
|
15
|
-
* c'est ce qu'on écrit dans `@
|
|
17
|
+
* c'est ce qu'on écrit dans `@Inject("…")`
|
|
16
18
|
* `super("devkit", …)` → nomme l'INSTANCE, sa clé dans le conteneur,
|
|
17
19
|
* c'est ce qu'on écrit dans `kernel.get("…")`
|
|
18
20
|
* Les deux mènent à la MÊME instance : le conteneur les réconcilie via la classe.
|
|
@@ -28,13 +30,13 @@ import { type IDevkitConfig } from "../config/config.js";
|
|
|
28
30
|
* ```ts
|
|
29
31
|
* // 1. INJECTION par le constructeur — la dépendance est DÉCLARÉE, donc le
|
|
30
32
|
* // conteneur l'ordonnance et elle se voit dans la signature (nom de CLASSE).
|
|
31
|
-
* import {
|
|
33
|
+
* import { Inject, injectable, Service, Module } from "nodefony";
|
|
32
34
|
*
|
|
33
35
|
* @injectable()
|
|
34
36
|
* class ReportService extends Service {
|
|
35
37
|
* constructor(
|
|
36
38
|
* module: Module,
|
|
37
|
-
* @
|
|
39
|
+
* @Inject("DevkitService") private devkit: DevkitService,
|
|
38
40
|
* ) {
|
|
39
41
|
* super("report", module.container, module.notificationsCenter);
|
|
40
42
|
* }
|
|
@@ -46,6 +48,7 @@ import { type IDevkitConfig } from "../config/config.js";
|
|
|
46
48
|
* ```
|
|
47
49
|
*/
|
|
48
50
|
declare class DevkitService extends Service implements IDevkitService {
|
|
51
|
+
#private;
|
|
49
52
|
module: Module;
|
|
50
53
|
private readonly cfg;
|
|
51
54
|
constructor(module: Module);
|
|
@@ -97,6 +100,16 @@ declare class DevkitService extends Service implements IDevkitService {
|
|
|
97
100
|
* n'est pas garanti être celui du projet).
|
|
98
101
|
*/
|
|
99
102
|
mcpToolDeps(): IMcpToolDeps;
|
|
103
|
+
/**
|
|
104
|
+
* Les outils `nodefony_*` retenus par l'allowlist `mcp.tools`.
|
|
105
|
+
*
|
|
106
|
+
* Recomposés à chaque appel, jamais mémorisés : la collecte a lieu à la
|
|
107
|
+
* requête MCP, sur une porte qui n'existe qu'en développement — un cache
|
|
108
|
+
* serait une structure de plus à invalider pour un gain nul.
|
|
109
|
+
*
|
|
110
|
+
* @returns les outils, dans l'ordre de l'allowlist
|
|
111
|
+
*/
|
|
112
|
+
mcpTools(): IMcpTool[];
|
|
100
113
|
/**
|
|
101
114
|
* Les scopes que la porte MCP EXIGE — dérivés des outils qu'elle déclare.
|
|
102
115
|
*
|
|
@@ -136,6 +149,30 @@ declare class DevkitService extends Service implements IDevkitService {
|
|
|
136
149
|
* @returns une entrée pour la porte MCP, ou aucune
|
|
137
150
|
*/
|
|
138
151
|
publishedProtectedResources(): readonly IProtectedResourceInput[];
|
|
152
|
+
/**
|
|
153
|
+
* La porte MCP de développement — définie au premier usage, une fois.
|
|
154
|
+
*
|
|
155
|
+
* Bâtie sur `defineMcpGate` comme celle de n'importe quelle application : ses
|
|
156
|
+
* réglages viennent de `devkit.mcp.*`, elle sert les outils de TOUS les modules
|
|
157
|
+
* chargés (`tools: "modules"` — l'outillage éprouve aussi ceux de l'app), et
|
|
158
|
+
* accorde le rôle d'opérateur à qui l'atteint sans jeton. Ce rôle n'est sûr que
|
|
159
|
+
* parce que son PÉRIMÈTRE la protège (module `policy: "dev"`, absent en
|
|
160
|
+
* production) ; une porte protégée par un serveur d'autorisation le tire, elle,
|
|
161
|
+
* des scopes du jeton.
|
|
162
|
+
*
|
|
163
|
+
* @returns la porte, prête à servir.
|
|
164
|
+
*/
|
|
165
|
+
mcpGate(): IMcpGate;
|
|
166
|
+
/**
|
|
167
|
+
* La porte de session du profilage — définie au premier usage, une fois.
|
|
168
|
+
*
|
|
169
|
+
* Bâtie sur `defineTicketSessionGate` comme le ferait une application pour
|
|
170
|
+
* ses propres outils : locale seulement (défaut de la porte), facteur d'audit
|
|
171
|
+
* {@link PROFILE_SESSION_FACTOR}.
|
|
172
|
+
*
|
|
173
|
+
* @returns la porte, prête à émettre et à servir.
|
|
174
|
+
*/
|
|
175
|
+
profileSessionGate(): ITicketSessionGate;
|
|
139
176
|
status(): {
|
|
140
177
|
ready: boolean;
|
|
141
178
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Facteur d'authentification déclaré au flux de session : l'audit écrit
|
|
3
|
+
* `login.success` avec cette raison, ce qui distingue une session de profilage
|
|
4
|
+
* d'une vraie connexion.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PROFILE_SESSION_FACTOR = "devkit";
|
|
7
|
+
/**
|
|
8
|
+
* Chemin de la route de connexion du profilage, que l'outil appelle. Hors du
|
|
9
|
+
* motif de la zone d'administration (`^/nodefony/[^/]+/api`). Le contrôleur
|
|
10
|
+
* l'écrit en littéraux ; un test tient l'égalité.
|
|
11
|
+
*/
|
|
12
|
+
export declare const PROFILE_SESSION_PATH = "/nodefony/devkit/profile-session";
|
package/docs/index.md
CHANGED
|
@@ -106,13 +106,13 @@ la rend éprouvable sans serveur, et ce qui l'empêche d'inventer quoi que ce so
|
|
|
106
106
|
npx nodefony ai:mcp # écrit .mcp.json ; --dry-run pour voir sans écrire
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
Neuf outils. Quatre sont les commandes que vous connaissez déjà :
|
|
110
110
|
`nodefony_inspect` (ce qui est monté), `nodefony_check` (ce qui manque),
|
|
111
111
|
`nodefony_symbols` (ce qu'une API signifie), `nodefony_card` (par où commencer).
|
|
112
112
|
`nodefony_docs` sert la documentation des modules. `nodefony_admin_list` et
|
|
113
113
|
`nodefony_admin_call` ouvrent les lectures du data plane d'administration — le
|
|
114
114
|
catalogue, puis l'appel — et exigent un jeton porteur du scope `admin:read` :
|
|
115
|
-
sans lui, ils n'apparaissent pas dans la liste.
|
|
115
|
+
sans lui, ils n'apparaissent pas dans la liste. `nodefony_observe` mesure ce que l'application fait réellement de ses requêtes, pendant qu'elle tourne ; `nodefony_profile_route` mesure une route en lui envoyant lui-même ses requêtes (sections suivantes).
|
|
116
116
|
|
|
117
117
|
**Il n'y a pas de process à lancer.** Depuis la révision `2026-07-28` du
|
|
118
118
|
transport, un serveur MCP est un endpoint `POST` sans session : c'est donc une
|
|
@@ -143,13 +143,145 @@ Trois choses à savoir avant de s'étonner d'un refus :
|
|
|
143
143
|
|
|
144
144
|
Les outils intégrés sont en lecture seule, et `mcp.tools` est une allowlist.
|
|
145
145
|
|
|
146
|
-
###
|
|
146
|
+
### Mesurer l'application qui tourne — `nodefony_observe`
|
|
147
|
+
|
|
148
|
+
Un agent de code qui vient de modifier un contrôleur ne voit pas l'effet de son
|
|
149
|
+
changement : la barre de debug et la console d'administration affichent les
|
|
150
|
+
mesures du profileur, mais il ne les lit pas. `nodefony_observe` les lui rend.
|
|
151
|
+
Pendant une fenêtre de 1 à 50 secondes (10 par défaut), l'outil relève le
|
|
152
|
+
profileur de développement toutes les deux secondes et rend un rapport :
|
|
153
|
+
|
|
154
|
+
1. un **verdict** — `ok`, `anomalies (N)`, et `INCOMPLÈTE` quand les chiffres ne
|
|
155
|
+
portent pas sur tout (profils perdus, aucun trafic, appel interrompu), avec
|
|
156
|
+
la raison ;
|
|
157
|
+
2. les **anomalies**, nommées par route et par contrôleur : une requête SQL
|
|
158
|
+
répétée dans une boucle (« N+1 »), les erreurs `5xx` avec leur `requestId` et
|
|
159
|
+
leurs lignes de journal, les routes dont le p95 dépasse 500 ms avec ce qui
|
|
160
|
+
coûte par requête, un blocage de la boucle d'événements de 100 ms ou plus ;
|
|
161
|
+
3. un tableau par route, les requêtes SQL les plus coûteuses, et un bloc JSON
|
|
162
|
+
pour comparer deux appels — avant et après une correction.
|
|
163
|
+
|
|
164
|
+
```jsonc
|
|
165
|
+
{ "name": "nodefony_observe", "arguments": { "durationSec": 10 } }
|
|
166
|
+
// "route": "<nom de route>" pour n'en observer qu'une ; "includeTooling": true
|
|
167
|
+
// pour compter aussi les routes /nodefony/… de l'outillage
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**L'outil ne génère aucun trafic.** On ouvre la page, on joue les tests ou on
|
|
171
|
+
envoie des requêtes pendant que la fenêtre court ; sans trafic, il n'a rien à
|
|
172
|
+
juger, et il le dit.
|
|
173
|
+
|
|
174
|
+
Ce que les chiffres veulent dire :
|
|
175
|
+
|
|
176
|
+
- **durées et temps « par requête »** portent sur les requêtes **servies**
|
|
177
|
+
(statut inférieur à 400, sans exception). Un refus `401`/`403` rendu en
|
|
178
|
+
quelques millisecondes ne mesure pas la route : il est compté à part ;
|
|
179
|
+
- **`action.self`** est le temps de l'action et des services qu'elle appelle,
|
|
180
|
+
une fois retirés les phases imbriquées (initialisation, rendu, envoi) et le
|
|
181
|
+
SQL — les intervalles sont fusionnés, une requête lancée pendant le rendu
|
|
182
|
+
n'est pas comptée deux fois. **« SQL (cumul) »** additionne les durées des
|
|
183
|
+
requêtes, même lancées en parallèle ;
|
|
184
|
+
- **la boucle d'événements** est rapportée en retard moyen et en **pire
|
|
185
|
+
blocage** : un gel unique de 200 ms ne déplace la moyenne d'un intervalle de
|
|
186
|
+
deux secondes que de quelques millisecondes ;
|
|
187
|
+
- **la forme d'une requête SQL** remplace chaque valeur par `?` : le rapport ne
|
|
188
|
+
contient jamais une donnée. Le journal d'une erreur n'en garde que les lignes
|
|
189
|
+
WARNING et plus graves ;
|
|
190
|
+
- la **variation du tas** est mesurée sans GC forcé : elle est indicative,
|
|
191
|
+
jamais une anomalie.
|
|
192
|
+
|
|
193
|
+
Ce qui encadre l'outil :
|
|
194
|
+
|
|
195
|
+
- c'est un **outil long** : chaque relevé part en progression, et un client qui l'annule l'arrête au relevé suivant, qu'il ferme le flux de la réponse (révision courante de MCP) ou envoie une notification d'annulation (révision précédente, celle du SDK officiel) ;
|
|
196
|
+
- un **rechargement du serveur** pendant la fenêtre (un fichier modifié) ne fait
|
|
197
|
+
pas perdre l'observation : l'outil rend un rapport partiel qui le dit, avant
|
|
198
|
+
que le serveur ne ferme ses connexions ;
|
|
199
|
+
- le **journal des erreurs** est lu au relevé qui voit la requête — le syslog de
|
|
200
|
+
développement ne garde que ses dernières lignes — et seulement pour un appel
|
|
201
|
+
**authentifié** (scope `admin:read`), par la même voie que
|
|
202
|
+
`nodefony_admin_call` ;
|
|
203
|
+
- le profileur garde les 500 dernières requêtes : au-delà de ce débit entre deux
|
|
204
|
+
relevés, les profils perdus sont comptés, et le verdict le dit.
|
|
205
|
+
|
|
206
|
+
La lecture de chaque anomalie et le geste qui la corrige sont dans le skill
|
|
207
|
+
d'agent `nodefony-troubleshoot`, section « Mesurer l'application qui tourne ».
|
|
208
|
+
|
|
209
|
+
### Mesurer une route — `nodefony_profile_route`
|
|
210
|
+
|
|
211
|
+
Là où `nodefony_observe` attend le trafic, `nodefony_profile_route` le **produit** :
|
|
212
|
+
il envoie lui-même N requêtes `GET` (ou `HEAD`) sur un chemin de l'application,
|
|
213
|
+
retrouve leurs profils par l'identifiant qu'il pose (`x-request-id`) et rend le
|
|
214
|
+
même rapport. C'est la boucle « je modifie → je mesure » en un appel.
|
|
215
|
+
|
|
216
|
+
```jsonc
|
|
217
|
+
// tools/call
|
|
218
|
+
{
|
|
219
|
+
"name": "nodefony_profile_route",
|
|
220
|
+
"arguments": { "path": "/orders?page=2", "count": 50 },
|
|
221
|
+
}
|
|
222
|
+
// optionnel : "concurrency" (défaut 4, max 8), "warmup" (défaut 3, hors mesures),
|
|
223
|
+
// "method": "HEAD", "as": "<identifiant>", "anonymous": true
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`path` est un **chemin**, jamais une URL : hôte, schéma et `//` sont refusés,
|
|
227
|
+
l'outil ne vise que l'application elle-même. Au plus 200 requêtes par appel —
|
|
228
|
+
sous les 500 profils que garde le profileur — et 45 secondes de tir.
|
|
229
|
+
|
|
230
|
+
**Au nom de qui.** Une page protégée vue sans compte ne rend que des refus.
|
|
231
|
+
Quand le jeton MCP porte le scope `admin:write`, l'outil joue son
|
|
232
|
+
**propriétaire** : il ouvre une vraie session à son nom par le flux de connexion
|
|
233
|
+
de l'application (un ticket à usage unique, présenté en boucle locale à
|
|
234
|
+
`POST /nodefony/devkit/profile-session`), tire avec, puis la détruit — l'audit
|
|
235
|
+
l'écrit sous la raison `devkit`. `as` joue un autre compte, à condition que ses
|
|
236
|
+
rôles soient inclus dans ceux du propriétaire, hiérarchie comprise : jouer un
|
|
237
|
+
compte ne monte jamais en droits. Sans ce scope, l'outil joue l'anonyme et le
|
|
238
|
+
rapport donne la commande :
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
npx nodefony security:mcp:token --scope "admin:read admin:write" --write
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Extrait de deux appels réels, avant puis après la correction d'un N+1 (la même
|
|
245
|
+
lecture, une requête par compte, puis une seule requête `$in`) :
|
|
246
|
+
|
|
247
|
+
```text
|
|
248
|
+
Verdict : anomalies (1) — observation complète
|
|
249
|
+
Tir : 30/30 requête(s) GET /nodefony/test/db/n-plus-one · concurrence 4 · 106 ms · chauffe de 3 requête(s) hors mesures (p50 1.5 ms, max 2.5 ms)
|
|
250
|
+
Joué en tant que : `admin` (ROLE_ADMIN, ROLE_NODEFONY_ADMIN, ROLE_DEV, ROLE_SUPERVISOR)
|
|
251
|
+
Reçu par le client : 2xx 30 · 3xx 0 · 401 0 · 403 0 · autres 4xx 0 · 5xx 0 · sans réponse 0
|
|
252
|
+
|
|
253
|
+
Anomalies (les plus coûteuses d'abord) :
|
|
254
|
+
1. N+1 — route `DbController::nPlusOne` (DbController.nPlusOne) : `select … from "User" where "User"."id" = ? …` répétée ×16 dans une requête (30 requête(s) touchée(s), 14 ms cumulés). […] Remède habituel : charger la relation en une requête (`relations`, `$in`).
|
|
255
|
+
|
|
256
|
+
route | type | n | p50 | p95 | max | 2xx/3xx/4xx/5xx | SQL par requête moy/max | temps par requête
|
|
257
|
+
`DbController::nPlusOne` (DbController.nPlusOne) | http | 30 | 1.2 ms | 1.7 ms | 1.7 ms | 30/0/0/0 | 18.0/18 | SQL (cumul) 0.7 ms · action.self 0.2 ms · send 0.1 ms
|
|
258
|
+
|
|
259
|
+
— après correction —
|
|
260
|
+
Verdict : ok — observation complète
|
|
261
|
+
`DbController::nPlusOneFixed` (DbController.nPlusOneFixed) | http | 30 | 0.7 ms | 0.9 ms | 2.1 ms | 30/0/0/0 | 3.0/3 | SQL (cumul) 0.4 ms · send 0.1 ms · action.self 0.1 ms
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Ce que le rapport dit **en tête**, avant tout chiffre : les requêtes refusées
|
|
265
|
+
(`401`/`403` — verdict « rien à juger »), redirigées (vers la page de
|
|
266
|
+
connexion), restées sans réponse, et les profils non retrouvés. Une mesure qui ne
|
|
267
|
+
porte pas sur ce qui était demandé le dit d'abord.
|
|
268
|
+
|
|
269
|
+
À savoir :
|
|
270
|
+
|
|
271
|
+
- ce sont de **vraies requêtes** : une page qui écrit en `GET` écrit N fois — un
|
|
272
|
+
compteur, ou une session créée pour chaque visiteur anonyme ;
|
|
273
|
+
- les zones à jeton (`jwt`, `external-jwt`) ne sont pas jouées : leurs requêtes
|
|
274
|
+
rendent `401`, et le rapport le dit ;
|
|
275
|
+
- un tir plus court que cinq périodes de la sonde laisse la boucle d'événements
|
|
276
|
+
« non mesurée » plutôt que d'afficher un zéro trompeur.
|
|
277
|
+
|
|
278
|
+
### Obtenir le porteur — `security:mcp:token`
|
|
147
279
|
|
|
148
280
|
Dès que la porte demande un jeton, il faut en produire un. L'application **le signe elle-même**, en
|
|
149
281
|
ligne de commande, sans qu'aucun serveur n'écoute :
|
|
150
282
|
|
|
151
283
|
```bash
|
|
152
|
-
npx nodefony security:token --write # pose NF_MCP_TOKEN chez les agents présents
|
|
284
|
+
npx nodefony security:mcp:token --write # pose NF_MCP_TOKEN chez les agents présents
|
|
153
285
|
npx nodefony ai:mcp --auth # l'en-tête porte ${NF_MCP_TOKEN}, jamais le jeton
|
|
154
286
|
```
|
|
155
287
|
|
|
@@ -203,7 +335,8 @@ métier — et c'est précisément ce qu'un agent invente le plus mal. N'importe
|
|
|
203
335
|
module de votre application peut donc en publier :
|
|
204
336
|
|
|
205
337
|
```ts
|
|
206
|
-
import { Module,
|
|
338
|
+
import { Module, type IMcpTool } from "nodefony";
|
|
339
|
+
import { mcpText } from "@nodefony/ai";
|
|
207
340
|
|
|
208
341
|
class Shop extends Module {
|
|
209
342
|
getMcpTools(): IMcpTool[] {
|
|
@@ -221,6 +354,10 @@ class Shop extends Module {
|
|
|
221
354
|
properties: { sku: { type: "string", description: "Référence" } },
|
|
222
355
|
required: ["sku"],
|
|
223
356
|
},
|
|
357
|
+
// Il LIT la base et ne sort pas de l'application : il le dit, sinon la
|
|
358
|
+
// spec le tient pour destructif et l'agent demande confirmation à
|
|
359
|
+
// chaque lecture.
|
|
360
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
224
361
|
handler: async (args) => mcpText(await this.stock(String(args.sku))),
|
|
225
362
|
},
|
|
226
363
|
];
|
|
@@ -228,8 +365,14 @@ class Shop extends Module {
|
|
|
228
365
|
}
|
|
229
366
|
```
|
|
230
367
|
|
|
231
|
-
|
|
368
|
+
Quatre choses à savoir :
|
|
232
369
|
|
|
370
|
+
- **un outil dit ce qu'il fait** (`annotations`, indices `ToolAnnotations` de la
|
|
371
|
+
spec MCP) : sans annotation, la spec le tient pour un outil qui écrit, détruit
|
|
372
|
+
et sort de l'application. Un outil qui lit se déclare `readOnlyHint: true` ;
|
|
373
|
+
un outil qui modifie, `readOnlyHint: false`. Un indice n'est jamais une
|
|
374
|
+
autorisation — le détail des cinq indices et de leurs défauts :
|
|
375
|
+
[écrire un outil](../../ai/docs/mcp.md#dire-ce-que-fait-loutil--les-annotations) ;
|
|
233
376
|
- **rien ne s'enregistre au démarrage** : la liste est relue à chaque requête,
|
|
234
377
|
donc un module ajouté apparaît sans redémarrer quoi que ce soit ;
|
|
235
378
|
- **`mcp.tools` ne filtre que les outils intégrés** — le vôtre est publié dès
|
|
@@ -280,7 +423,9 @@ est absent de `tools/list` **et** inappelable en le nommant, et le refus dit
|
|
|
280
423
|
### Un outil LONG — progression et annulation
|
|
281
424
|
|
|
282
425
|
Le troisième paramètre du handler sert l'outil qui dure : `run.signal` est
|
|
283
|
-
abattu quand
|
|
426
|
+
abattu quand le client annule — qu'il ferme le flux de la réponse (révision courante
|
|
427
|
+
de MCP) ou envoie `notifications/cancelled` (révision précédente) —,
|
|
428
|
+
`run.progress()` lui donne signe de vie.
|
|
284
429
|
Un outil court l'ignore ; un appel direct (un test) ne le fournit pas, d'où le
|
|
285
430
|
`run?.` :
|
|
286
431
|
|
|
@@ -303,10 +448,14 @@ Un outil court l'ignore ; un appel direct (un test) ne le fournit pas, d'où le
|
|
|
303
448
|
|
|
304
449
|
Ce que la porte fait du reste :
|
|
305
450
|
|
|
306
|
-
- **le flux
|
|
307
|
-
`progressToken` et accepte `text/event-stream` reçoit
|
|
308
|
-
`notifications/progress`, puis la réponse, qui clôt le flux.
|
|
309
|
-
|
|
451
|
+
- **le flux s'ouvre à la première progression, ou au bout de cinq secondes.**
|
|
452
|
+
Un agent qui envoie un `progressToken` et accepte `text/event-stream` reçoit
|
|
453
|
+
des `notifications/progress`, puis la réponse, qui clôt le flux. Un outil
|
|
454
|
+
encore en cours après le délai de grâce (`MCP_STREAM_GRACE_MS`, 5 s) ouvre le
|
|
455
|
+
flux même sans jeton : la norme oblige le client à accepter une réponse en
|
|
456
|
+
flux, et c'est ce qui désarme le délai d'inactivité de la requête (30 s par
|
|
457
|
+
défaut) — sans cela, un outil long répondant en JSON recevrait un `408`. Un
|
|
458
|
+
outil qui répond avant reste en JSON ;
|
|
310
459
|
- **la norme est tenue pour vous** : une valeur qui ne croît pas est ignorée, la
|
|
311
460
|
cadence est bornée (une notification par 100 ms au plus), et plus rien ne
|
|
312
461
|
part une fois l'outil terminé ;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodefony/devkit",
|
|
3
|
-
"version": "10.0.0-beta.
|
|
3
|
+
"version": "10.0.0-beta.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Outillage de développement d'une application Nodefony : carte de visite du projet et portes de découverte pour un agent de développement",
|
|
6
6
|
"author": "Christophe CAMENSULI <ccamensuli@gmail.com>",
|
|
@@ -20,22 +20,27 @@
|
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "rolldown -c rolldown.config.ts && tsgo -p tsconfig.declarations.json",
|
|
23
|
+
"clean": "node --eval \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
23
24
|
"typecheck": "tsgo --noEmit -p tsconfig.json && tsgo --noEmit -p tsconfig.tests.json",
|
|
24
25
|
"test": "vitest run",
|
|
25
|
-
"coverage": "vitest run --coverage"
|
|
26
|
+
"coverage": "vitest run --coverage",
|
|
27
|
+
"test:integration": "vitest run --config vitest.integration.config.ts"
|
|
26
28
|
},
|
|
27
29
|
"peerDependencies": {
|
|
28
|
-
"@nodefony/
|
|
29
|
-
"@nodefony/
|
|
30
|
-
"nodefony": "^10.0.0-beta.
|
|
31
|
-
"
|
|
30
|
+
"@nodefony/ai": "^10.0.0-beta.4",
|
|
31
|
+
"@nodefony/framework": "^10.0.0-beta.4",
|
|
32
|
+
"@nodefony/http": "^10.0.0-beta.4",
|
|
33
|
+
"lighthouse": "^13.0.0",
|
|
34
|
+
"nodefony": "^10.0.0-beta.4",
|
|
32
35
|
"playwright": "^1.50.0",
|
|
33
|
-
"
|
|
36
|
+
"zod": "^4.6.7"
|
|
34
37
|
},
|
|
35
38
|
"devDependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"@nodefony/
|
|
38
|
-
"nodefony": "^10.0.0-beta.
|
|
39
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
40
|
+
"@nodefony/ai": "^10.0.0-beta.4",
|
|
41
|
+
"@nodefony/framework": "^10.0.0-beta.4",
|
|
42
|
+
"@nodefony/http": "^10.0.0-beta.4",
|
|
43
|
+
"nodefony": "^10.0.0-beta.4"
|
|
39
44
|
},
|
|
40
45
|
"dependencies": {
|
|
41
46
|
"axe-core": "4.14.0",
|
|
@@ -72,6 +77,6 @@
|
|
|
72
77
|
"esm"
|
|
73
78
|
],
|
|
74
79
|
"engines": {
|
|
75
|
-
"node": ">=24.
|
|
80
|
+
"node": ">=24.14.0"
|
|
76
81
|
}
|
|
77
82
|
}
|