@ogygie/cli 0.16.0 → 0.18.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/dist/cli.js CHANGED
@@ -15940,6 +15940,30 @@ var governedChecksumsByVersion = {
15940
15940
  "scripts/verify-specs.ts": "03d58baa11822ad7ae67080f990f7a82f4f94d223b12777728b8b9ddd7ebd843",
15941
15941
  "tsconfig.base.json": "0f8919e9ed8bbc88e0308fa2fa91471dabb18d1df721bd78c04076597eba89a2"
15942
15942
  },
15943
+ "0.17.0": {
15944
+ ".github/workflows/ogygie-gates.yml": "48c40efe9fe66dd0f9c3b94287026d80eae9afd6782b5833fdf8846d8662babf",
15945
+ "AGENTS.md": "e058a54ff4750abf19f7c4b026e8be661d8ce25419596d22c20de2c387469370",
15946
+ "CLAUDE.md": "785ee8d90e368806d8cf0b4d84a15f29d1dc961168bda86cf1a6c60a923c8c9b",
15947
+ "scripts/lint-interdits.ts": "4644e1aaabc1d19ed42a0bdf8e2e55b9451f789d87d59754bce3432de7c357e7",
15948
+ "scripts/ogygie-design-brief.ts": "aba1585717b30808dd0bc8930a37a1ad2d83ee6869f969dbd06ca595e37a4a89",
15949
+ "scripts/ogygie-impact.ts": "f06cc8b4a46ddfbcfb32ba747c2454548ca49918123d04ab13d210b4a89c1fb9",
15950
+ "scripts/verify-contracts.ts": "9cd64c88dcc3b54b65bf1e1b7a68eb00353ee16f2a9ce8bca3c49ba063c2a08b",
15951
+ "scripts/verify-manifest.ts": "6c448fa3a19b87666f686ca57c6ca834d9e169b1380e4fe6e4c19b995bf94f3c",
15952
+ "scripts/verify-specs.ts": "03d58baa11822ad7ae67080f990f7a82f4f94d223b12777728b8b9ddd7ebd843",
15953
+ "tsconfig.base.json": "0f8919e9ed8bbc88e0308fa2fa91471dabb18d1df721bd78c04076597eba89a2"
15954
+ },
15955
+ "0.18.0": {
15956
+ ".github/workflows/ogygie-gates.yml": "48c40efe9fe66dd0f9c3b94287026d80eae9afd6782b5833fdf8846d8662babf",
15957
+ "AGENTS.md": "8356696ef27c53cad164ab0086163454348b18852b121eca9fb050fb73e625f0",
15958
+ "CLAUDE.md": "785ee8d90e368806d8cf0b4d84a15f29d1dc961168bda86cf1a6c60a923c8c9b",
15959
+ "scripts/lint-interdits.ts": "4644e1aaabc1d19ed42a0bdf8e2e55b9451f789d87d59754bce3432de7c357e7",
15960
+ "scripts/ogygie-design-brief.ts": "aba1585717b30808dd0bc8930a37a1ad2d83ee6869f969dbd06ca595e37a4a89",
15961
+ "scripts/ogygie-impact.ts": "f06cc8b4a46ddfbcfb32ba747c2454548ca49918123d04ab13d210b4a89c1fb9",
15962
+ "scripts/verify-contracts.ts": "9cd64c88dcc3b54b65bf1e1b7a68eb00353ee16f2a9ce8bca3c49ba063c2a08b",
15963
+ "scripts/verify-manifest.ts": "6c448fa3a19b87666f686ca57c6ca834d9e169b1380e4fe6e4c19b995bf94f3c",
15964
+ "scripts/verify-specs.ts": "92d54aa5111fe0753aa9d6eb3b4ac89a7f24fa06578ea3369ccb0dc9d4c3dd88",
15965
+ "tsconfig.base.json": "0f8919e9ed8bbc88e0308fa2fa91471dabb18d1df721bd78c04076597eba89a2"
15966
+ },
15943
15967
  "0.2.0": {
15944
15968
  ".github/workflows/ogygie-gates.yml": "48c40efe9fe66dd0f9c3b94287026d80eae9afd6782b5833fdf8846d8662babf",
15945
15969
  "AGENTS.md": "ca73ab619d8c55fac11858d815bf5fbab6537ecebe7ae4123cbdeb3e3e36bf1a",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogygie/cli",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Ogygie framework CLI: deterministic scaffolding (init), governed-file upgrades (upgrade) and lockstep verification (check) for Ogygie products.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  "access": "public"
18
18
  },
19
19
  "devDependencies": {
20
- "@ogygie/core": "0.16.0",
20
+ "@ogygie/core": "0.18.0",
21
21
  "@types/node": "^24.0.0",
22
22
  "esbuild": "^0.28.0",
23
23
  "tsx": "^4.20.0",
@@ -295,23 +295,37 @@ dérogation qui s'oublie. Elle ne couvre JAMAIS le code du produit lui-même.
295
295
  ### Stack imposée (toute autre lib = amendement DECISIONS.md d'abord)
296
296
  Vite + React 19 + TanStack Router (routing EN CODE) + TanStack Query (état serveur) ;
297
297
  PAS de lib d'état client (useState suffit) ; Tailwind v4 thémé depuis `design/tokens.json`
298
- (valeurs arbitraires `p-[13px]` interdites par lint) ; primitives UI copiées dans
299
- `apps/web/src/patterns/` (jamais consommées directement depuis node_modules par une
300
- feature) ; react-hook-form UNIQUEMENT via le pattern `ContractForm` ; TanStack
301
- Table UNIQUEMENT via `DataTable` ; Recharts UNIQUEMENT via `VarianceChart` ;
298
+ (valeurs arbitraires `p-[13px]` interdites par lint) ; le socle de patterns vient du
299
+ paquet `@ogygie/ui` — `ScreenState`, `EmptyState`, `ErrorBanner`, `EditableText`,
300
+ `fetchContract`, et derrière leur propre point d'entrée `DataTable`
301
+ (`@ogygie/ui/data-table`), `ContractForm` (`@ogygie/ui/contract-form`),
302
+ `VarianceChart` (`@ogygie/ui/variance-chart`) — IMPORTÉ, jamais recopié ; les
303
+ patterns PRODUIT vivent dans `apps/web/src/patterns/` (jamais consommés directement
304
+ depuis node_modules par une feature) ; react-hook-form UNIQUEMENT via `ContractForm` ;
305
+ TanStack Table UNIQUEMENT via `DataTable` ; Recharts UNIQUEMENT via `VarianceChart` ;
302
306
  date-fns via les helpers `@ogygie/core` (jamais de `new Date()` nu) ; i18n Lingui
303
- (ICU, extraction vérifiée en CI). Noms de patterns en anglais.
307
+ (ICU, extraction vérifiée en CI) — `@ogygie/ui` ne porte AUCUNE chaîne : tout texte
308
+ lui arrive en prop, traduit par la feature. Noms de patterns en anglais. La feuille
309
+ Tailwind du produit déclare le paquet comme source
310
+ (`@source "../node_modules/@ogygie/ui/src";`), sinon ses classes ne sont pas générées.
304
311
 
305
312
  ### Structure d'une feature front (miroir de l'API)
306
313
  ```
307
- apps/web/features/orders/
308
- ├── index.ts # seul export public
309
- ├── routes.tsx # définition TanStack Router de la feature
310
- ├── queries.ts # hooks TanStack Query (fetch + invalidations)
311
- ├── OrdersList.tsx # écrans = composition de patterns, rien d'autre
312
- ├── OrderForm.tsx
313
- └── OrdersList.test.tsx # un test par état UI de la spec
314
+ apps/web/src/features/orders/
315
+ ├── index.ts # seul export public
316
+ ├── routes.tsx # fabrique createRoute de la feature, que le routeur de l'app ajoute à ses enfants
317
+ ├── queries.ts # hooks TanStack Query de LECTURE (clés de cache dérivées du manifeste)
318
+ ├── writes.ts # hooks de MUTATION (invalidations dérivées du manifeste)
319
+ ├── OrdersScreen.container.tsx # le CONTENEUR : lit les hooks, déroule les états par ScreenState, passe au corps des données servies
320
+ ├── OrdersScreen.tsx # le CORPS : reçoit data, QueryLike et MutationLike — jamais un hook ; compose les vues
321
+ ├── views/OrdersList.tsx # une vue = un pattern (DataTable, ContractForm, VarianceChart…) sur UNE opération
322
+ ├── views/OrderForm.tsx
323
+ └── OrdersScreen.test.tsx # un test par état UI de la spec : le conteneur sous un fetch simulé, ou le corps sur des QueryLike
314
324
  ```
325
+ Une feature générée porte les mêmes fichiers à l'infixe `.gen` près
326
+ (`queries.gen.ts`, `writes.gen.ts`, `OrdersScreen.container.gen.tsx`,
327
+ `views/OrdersList.gen.tsx`, `routes.gen.tsx`) ; son corps `OrdersScreen.tsx`
328
+ et ses vues `custom` restent écrits.
315
329
 
316
330
  ### Template canonique — hook de requête
317
331
  ```ts
@@ -319,7 +333,7 @@ apps/web/features/orders/
319
333
  /** @intent Data access of the orders feature. Re-validates contracts in dev. */
320
334
  import { useQuery } from "@tanstack/react-query";
321
335
  import { ListOrdersOutputSchema } from "@product/api/features/orders/schemas";
322
- import { fetchContract } from "../../lib/fetchContract"; // Zod parse in dev, OgygieError on broken contract
336
+ import { fetchContract } from "@ogygie/ui"; // Zod parse of the answer; an OgygieFailure on a refused call or a broken contract
323
337
 
324
338
  export function useOrders() {
325
339
  return useQuery({
@@ -334,18 +348,25 @@ export function useOrders() {
334
348
  // apps/web/features/orders/OrdersList.tsx
335
349
  /** @intent Orders list screen: composition of patterns, zero business logic. */
336
350
  import { Trans } from "@lingui/react/macro";
337
- import { DataTable } from "../../patterns/DataTable";
338
- import { EmptyState } from "../../patterns/EmptyState";
339
- import { ErrorBanner } from "../../patterns/ErrorBanner";
351
+ import { ScreenState } from "@ogygie/ui";
352
+ import { DataTable } from "@ogygie/ui/data-table";
340
353
  import { useOrders } from "./queries";
341
354
 
342
355
  export function OrdersList() {
343
- const { data, isPending, error } = useOrders();
344
- if (error) return <ErrorBanner error={error} />;
345
- if (isPending) return <DataTable.Skeleton />;
346
- if (data.orders.length === 0)
347
- return <EmptyState title={<Trans>Ajoutez votre première commande</Trans>} />;
348
- return <DataTable rows={data.orders} columns={orderColumns} />;
356
+ return (
357
+ <ScreenState
358
+ query={useOrders()}
359
+ errorTitle={<Trans>Cette vue n'a pas pu être chargée</Trans>}
360
+ loading={<DataTable.Skeleton />}
361
+ isEmpty={(data) => data.orders.length === 0}
362
+ emptyState={{
363
+ title: <Trans>Ajoutez votre première commande</Trans>,
364
+ explanation: <Trans>Les commandes enregistrées apparaissent ici.</Trans>,
365
+ }}
366
+ >
367
+ {(data) => <DataTable rows={data.orders} columns={orderColumns} rowKey={(order) => order.id} />}
368
+ </ScreenState>
369
+ );
349
370
  }
350
371
  ```
351
372
 
@@ -353,6 +374,11 @@ export function OrdersList() {
353
374
  - Un écran est une COMPOSITION de patterns : jamais de `<table>`, `<form>` ou fetch nus
354
375
  dans une feature. Si un pattern manque, on le crée dans `patterns/` (et on met à jour
355
376
  `design/patterns.md`), on ne bricole pas dans la feature.
377
+ - Un écran est un CONTENEUR et un CORPS. Le conteneur lit les hooks (route, lectures,
378
+ écritures) et déroule les états — accès refusé, erreur, chargement — par
379
+ `ScreenState`, jamais par une cascade de `if` ; le corps reçoit la lecture
380
+ principale SERVIE, les autres lectures en `QueryLike` et les écritures en
381
+ `MutationLike`, jamais un hook : il se teste sans client de requêtes ni réseau.
356
382
  - Chaque état UI listé dans la spec (vide/chargement/nominal/erreur) a son test.
357
383
  - Toute chaîne visible passe par Lingui (`<Trans>`/`t`) — la CI casse sinon.
358
384
  - Chiffres financiers : `tabular-nums`, alignés à droite, unité explicite.
@@ -2,27 +2,35 @@
2
2
 
3
3
  > Les patterns sont les SEULES briques d'écran autorisées (AGENTS.md §Front) :
4
4
  > un écran est une COMPOSITION de patterns, jamais de `<table>`, `<form>` ni
5
- > fetch nus. Le starter n'a pas encore d'app front produit : à l'ouverture du
6
- > premier front, chaque pattern retenu est COPIÉ dans `apps/web/src/patterns/`
7
- > et sa fiche ci-dessous devient son contrat. Un pattern manquant se crée ICI
8
- > d'abord (fiche + décision), jamais en one-off dans un écran.
5
+ > fetch nus. Les patterns GÉNÉRIQUES ci-dessous sont LIVRÉS par le paquet
6
+ > `@ogygie/ui` : un produit les importe, il ne les recopie pas, et leur fiche
7
+ > ci-dessous est leur contrat. Un pattern PRODUIT — propre au domaine — naît
8
+ > dans `apps/web/src/patterns/` avec sa fiche ICI d'abord, jamais en one-off
9
+ > dans un écran. Tout texte lu par un humain arrive à un pattern générique en
10
+ > prop, traduit par la feature (Lingui) : le paquet ne porte aucune chaîne.
9
11
  >
10
12
  > Ce catalogue est une source de vérité du brief de design
11
13
  > (`pnpm ogygie:design-brief <feature>`) : chaque fiche `## <Name>` devient
12
14
  > une entrée du vocabulaire de composition. Noms en anglais (machine-lisible),
13
15
  > fiches en français (document humain).
14
16
 
17
+ ## ScreenState
18
+ Le composant d'état UNIQUE d'un écran (`@ogygie/ui`). Reçoit la lecture (`QueryLike`, sous-ensemble d'un résultat TanStack Query) et le corps en fonction de rendu, et décide dans cet ordre : accès refusé (`denied` de l'appelant, ou une réponse 401/403 quand une copie `accessDenied` est donnée), erreur (`ErrorBanner`, titré par `errorTitle`), chargement (`loading`, à défaut `Skeleton`), vide (`isEmpty` + `emptyState`, un `EmptyState` avec son action), puis le corps avec des données servies. Un corps ne branche jamais sur `undefined`.
19
+
15
20
  ## DataTable
16
- Tableau de données (TanStack Table, seul usage autorisé). États : squelette de chargement (`DataTable.Skeleton`), vide (déléguer à EmptyState), nominal trié/paginé, erreur (déléguer à ErrorBanner). Chiffres en `tabular-nums` alignés à droite, unité explicite.
21
+ Tableau de données (`@ogygie/ui/data-table`, TanStack Table — seul usage autorisé). Colonnes typées `{ id, header, cell, sortValue?, align? }` ; tri par clic d'en-tête (`aria-sort`), chiffres `align: "end"` en `tabular-nums` alignés à droite, unité explicite dans la cellule. États : squelette de chargement (`DataTable.Skeleton`), vide DÉLÉGUÉ (`emptyState` : un `EmptyState` qui dit pourquoi), nominal trié ; l'erreur appartient à `ScreenState`. La liste de colonnes reste stable (module ou `useMemo`).
17
22
 
18
23
  ## EmptyState
19
- État vide intentionnel : un titre, une explication courte, UNE action de création. Jamais de tableau ou de liste vides nus — l'écran vide est le premier écran que voit chaque nouvel utilisateur.
24
+ État vide intentionnel (`@ogygie/ui`) : un titre qui nomme la CAUSE, une explication qui dit ce qui le remplirait, UNE action de création au plus. Jamais de tableau ou de liste vides nus — l'écran vide est le premier écran que voit chaque nouvel utilisateur. Deux causes, deux formulations : rien d'enregistré, ou un filtre qui ne trouve rien.
20
25
 
21
26
  ## ErrorBanner
22
- Bandeau d'erreur qui affiche une OgygieError : message traduit via `userMessageKey` (clé ICU), code stable et `suggestedFix` repliables. Tout état d'erreur d'écran passe par lui — jamais de message d'erreur improvisé.
27
+ Bandeau d'erreur (`@ogygie/ui`) : affiche une `OgygieFailure` en entier — le titre du produit, le message, le code stable avec le statut et la cause, la correction suggérée — et dégrade toute autre erreur à son message. `kind: "save"` marque une écriture refusée (le produit titre autrement) ; `retry` ajoute le bouton que le produit libelle. Tout état d'erreur d'écran passe par lui — jamais de message improvisé.
23
28
 
24
29
  ## ContractForm
25
- Formulaire DÉRIVÉ d'un contrat Zod (react-hook-form, seul usage autorisé) : les contraintes du schéma (min/max/regex, obligatoire) sont les validations des champs — rien n'est redéclaré à la main. États : saisie, soumission en cours (bouton désactivé), erreurs de champ, erreur serveur (ErrorBanner).
30
+ Formulaire DÉRIVÉ d'un contrat Zod (`@ogygie/ui/contract-form`, react-hook-form — seul usage autorisé) : les champs viennent de la forme du schéma, leurs contrôles de leur genre (texte, nombre, date, sélection d'enum, case à cocher), leurs contraintes des bornes du schéma — rien n'est redéclaré à la main — et le schéma valide chaque soumission (`safeParse`). Libellés, aides, libellés d'options, `submitLabel` et `cancel` sont des props traduites ; `renderIssue` traduit la phrase d'un refus. États : saisie, soumission en cours (bouton inactif, `pending`), erreur de champ (une phrase sous le champ), erreur de formulaire (un `refine`), erreur serveur (`error` : un `ErrorBanner` save au-dessus des actions). `onSubmit` reçoit la SORTIE parsée du contrat.
26
31
 
27
32
  ## VarianceChart
28
- Graphique d'écart (Recharts, seul usage autorisé) : compare une valeur constatée à une référence. États : chargement, vide, nominal, erreur. Montants en `tabular-nums`, axe et unité toujours étiquetés.
33
+ Graphique d'écart (`@ogygie/ui/variance-chart`, Recharts — seul usage autorisé) : une valeur constatée (`measured`) contre une référence (`reference`), par libellé, en deux barres groupées ; l'écart signé se lit dans l'infobulle ; une ligne zéro quand les valeurs la traversent. Couleurs tirées des tokens du produit par variables CSS, noms de séries et unité en props traduites. États : vide DÉLÉGUÉ (`emptyState`), squelette (`VarianceChart.Skeleton`) ; chargement et erreur appartiennent à `ScreenState`. Montants en `tabular-nums`, axe et unité toujours étiquetés.
34
+
35
+ ## EditableText
36
+ Texte qui s'écrit là où il se lit (`@ogygie/ui`) : un clic le transforme en champ, quitter le champ ou Entrée enregistre, Échap abandonne. Un lecteur sans droit voit le texte nu, jamais un contrôle inactif. Un brouillon blanc est refusé sans écriture (sauf `allowEmpty`) ; une borne (`maxLength`) arrête le champ et affiche le compteur ; `pending` rend le contrôle inactif le temps d'une écriture. `placeholder` et `ariaLabel` sont traduits par la feature.
@@ -131,8 +131,8 @@ for (const fileName of specFiles) {
131
131
 
132
132
  if (frontmatter.status === "draft") {
133
133
  // What counts as an implementation is the core's rule, not this script's:
134
- // a package's folder is REQUIRED by the manifest gate, so its existence
135
- // proves nothing (see hasImplementation).
134
+ // the STATUS the map declares, never a folder (see hasImplementation) — a
135
+ // generated folder waits on its branch while the spec awaits its signature.
136
136
  const implemented =
137
137
  owner !== undefined && hasImplementation(owner, ownerDir !== null && existsSync(ownerDir));
138
138
  if (implemented) {
@@ -140,9 +140,9 @@ for (const fileName of specFiles) {
140
140
  code: "SPEC_DRAFT_ALREADY_IMPLEMENTED",
141
141
  message: `Spec ${fileName} is draft but an implementation exists.`,
142
142
  file: specPath,
143
- cause: "status=draft with code or a non-skeleton manifest entry",
143
+ cause: "status=draft while the manifest entry is in-progress or delivered",
144
144
  suggestedFix:
145
- "Move the spec to validated/implemented, or remove the premature implementation.",
145
+ "Move the spec to validated/implemented, or declare the entry skeleton while the spec awaits its validation — a folder on a branch is a proposal, not a delivery.",
146
146
  });
147
147
  }
148
148
  }