@ogygie/cli 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.
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/dist/cli.js +15154 -0
- package/package.json +32 -0
- package/templates/feature/README.md +9 -0
- package/templates/project/AGENTS.md +483 -0
- package/templates/project/AGENTS.product.md +48 -0
- package/templates/project/CLAUDE.md +29 -0
- package/templates/project/DECISIONS.md +18 -0
- package/templates/project/DECISIONS.product.md +11 -0
- package/templates/project/FAILURES.md +16 -0
- package/templates/project/ci/ogygie-gates.yml +42 -0
- package/templates/project/design/patterns.md +28 -0
- package/templates/project/design/tokens.json +50 -0
- package/templates/project/gitignore +5 -0
- package/templates/project/ogygie.manifest.json +15 -0
- package/templates/project/package.json +31 -0
- package/templates/project/pnpm-workspace.yaml +8 -0
- package/templates/project/scripts/lint-interdits.ts +119 -0
- package/templates/project/scripts/ogygie-design-brief.ts +192 -0
- package/templates/project/scripts/ogygie-impact.ts +301 -0
- package/templates/project/scripts/verify-contracts.ts +86 -0
- package/templates/project/scripts/verify-manifest.ts +306 -0
- package/templates/project/scripts/verify-specs.ts +222 -0
- package/templates/project/specs/_TEMPLATE.spec.md +37 -0
- package/templates/project/tsconfig.base.json +14 -0
- package/templates/project/tsconfig.json +9 -0
- package/templates/project/vitest.config.ts +23 -0
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ogygie/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Ogygie framework CLI: deterministic scaffolding (init), governed-file upgrades (upgrade) and lockstep verification (check) for Ogygie products.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ogygie": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"templates",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
21
|
+
"build": "tsx scripts/build.ts",
|
|
22
|
+
"record-checksums": "tsx scripts/record-governed-checksums.ts"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@ogygie/core": "workspace:*",
|
|
26
|
+
"@types/node": "^24.0.0",
|
|
27
|
+
"esbuild": "^0.28.0",
|
|
28
|
+
"tsx": "^4.20.0",
|
|
29
|
+
"typescript": "^5.8.0",
|
|
30
|
+
"zod": "^4.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# templates/feature — réservé (non câblé)
|
|
2
|
+
|
|
3
|
+
Ce dossier est POSÉ par la spec `ogygie-standalone-repo` mais volontairement
|
|
4
|
+
vide : le scaffolding de feature (`ogygie generate feature`) fera l'objet d'une
|
|
5
|
+
spec ultérieure. En attendant, la forme canonique d'une feature est décrite
|
|
6
|
+
dans `AGENTS.md` §Structure d'une feature, et l'exemple vivant est la feature
|
|
7
|
+
`notes` du repo framework.
|
|
8
|
+
|
|
9
|
+
Rien ici n'est lu par `ogygie init`, `upgrade` ni `check`.
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# AGENTS.md — Ogygie v1 (conventions GÉNÉRIQUES du framework)
|
|
2
|
+
|
|
3
|
+
> **Tu es un agent qui travaille sur ce repo. Lis ce fichier EN ENTIER, puis
|
|
4
|
+
> `AGENTS.product.md` (les liaisons produit : noms réels, alias, listes fermées),
|
|
5
|
+
> puis `ogygie.manifest.json`. Ne lis rien d'autre avant d'avoir une tâche précise.**
|
|
6
|
+
>
|
|
7
|
+
> Ce fichier est GÉNÉRIQUE — aucune règle ici ne nomme le produit ni son
|
|
8
|
+
> domaine ; ses exemples utilisent un domaine neutre (`orders`). Tout ce qui
|
|
9
|
+
> lie ces règles au produit réel vit dans `AGENTS.product.md`. Les décisions
|
|
10
|
+
> structurantes du repo se consignent dans `DECISIONS.md` (framework) et
|
|
11
|
+
> `DECISIONS.product.md` (produit) — numérotation `D<n>` globale unique aux
|
|
12
|
+
> deux fichiers, vérifiée par verify-specs. Ils naissent vierges : ce produit
|
|
13
|
+
> écrira ses propres décisions, en commençant au premier numéro.
|
|
14
|
+
|
|
15
|
+
## Ordre de lecture obligatoire pour toute tâche
|
|
16
|
+
|
|
17
|
+
1. `AGENTS.md` (ce fichier) — les règles.
|
|
18
|
+
2. `AGENTS.product.md` — l'extension produit (alias, colonnes tenant, listes fermées).
|
|
19
|
+
3. `ogygie.manifest.json` — la carte du repo (features, routes, dépendances).
|
|
20
|
+
4. `specs/<feature>.spec.md` — la spec de la feature concernée (si elle existe).
|
|
21
|
+
5. Le dossier `features/<feature>/` concerné — et LUI SEUL.
|
|
22
|
+
|
|
23
|
+
Interdiction d'explorer le repo au-delà. Si le manifeste ne suffit pas à localiser ce dont tu as besoin, c'est un bug du manifeste : le signaler dans `FAILURES.md`.
|
|
24
|
+
|
|
25
|
+
## Les 8 principes (version opérationnelle)
|
|
26
|
+
|
|
27
|
+
1. **Localité radicale** — une feature = un dossier `features/<nom>/` autonome (route + schémas + logique + tests). Aucun fichier magique, aucun comportement par convention de nommage. Tout module chargé l'est par un import explicite.
|
|
28
|
+
2. **Contrats aux frontières** — toute entrée/sortie (HTTP, DB, job, événement) passe par un schéma Zod. Le schéma est LA source de vérité : validation runtime + type TS inféré (`z.infer`) + doc.
|
|
29
|
+
3. **Manifeste machine-lisible** — `ogygie.manifest.json` indexe tout. Toute PR qui ajoute/modifie une feature met à jour le manifeste dans le même commit. La CI casse si le manifeste ment.
|
|
30
|
+
4. **Une seule façon idiomatique** — pour chaque besoin, copie le template canonique ci-dessous. N'invente pas de variante. Si le template est inadapté, ne contourne pas : propose un amendement de ce fichier.
|
|
31
|
+
5. **Effets déclarés** — toute fonction qui touche DB/réseau/fs/horloge reçoit ses dépendances en paramètre (injection). Jamais d'import global de client au fond d'un helper.
|
|
32
|
+
6. **Erreurs pour agents** — toute erreur suit le format `OgygieError` (voir `packages/ogygie-core/src/error.schema.ts`) : code stable, `file`, `cause`, `suggestedFix`.
|
|
33
|
+
7. **Testabilité en secondes** — `pnpm test -- features/<nom>` doit passer en < 10 s, sans réseau ni DB réelle (voir template test).
|
|
34
|
+
8. **Verbeux plutôt qu'ambigu** — noms longs et descriptifs, bloc `@intent` en tête de chaque fichier, imports complets. On écrit pour un lecteur sans contexte. **Le code est en ANGLAIS MAXIMAL** : identifiants, tables/colonnes BDD, routes HTTP, codes d'erreur, valeurs d'enum, noms de schémas et de patterns, et TOUS les commentaires (dont `@intent`) — sans exception. Le français reste réservé aux documents (specs, décisions, PR) et aux textes UI via clés ICU.
|
|
35
|
+
|
|
36
|
+
## Structure d'une feature
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
features/orders/
|
|
40
|
+
├── index.ts # SEUL point d'export public de la feature
|
|
41
|
+
├── routes.ts # Enregistrement Fastify des endpoints
|
|
42
|
+
├── schemas.ts # Contrats Zod (input/output/erreurs métier)
|
|
43
|
+
├── service.ts # Logique métier PURE (dépendances injectées)
|
|
44
|
+
├── repository.ts # Accès DB (seul fichier autorisé à parler à Drizzle)
|
|
45
|
+
├── service.test.ts # Tests de la logique (rapides, sans I/O)
|
|
46
|
+
└── routes.test.ts # Tests des endpoints (fastify.inject, DB mémoire)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Règle de dépendance : `routes → service → repository`. Jamais l'inverse, jamais de saut (une route n'appelle pas le repository directement). Une feature importe une autre feature UNIQUEMENT via son `index.ts`, et cette dépendance est déclarée dans le manifeste.
|
|
50
|
+
|
|
51
|
+
**Exception unique et bornée — les CONTRATS (amendement 2026-08).**
|
|
52
|
+
`schemas.ts` est le seul fichier d'une feature qui n'importe QUE des contrats :
|
|
53
|
+
zod, un contrat produit partagé (la liste fermée est dans `AGENTS.product.md`),
|
|
54
|
+
et — quand une dépendance déclarée au manifeste l'exige — le `schemas.ts` d'une
|
|
55
|
+
autre feature. JAMAIS son `index.ts`. Motif : un `index.ts` porte la surface
|
|
56
|
+
SERVEUR (routes, service, chargeurs de données) ; or `schemas.ts` est importé
|
|
57
|
+
par le FRONT (`@<produit>/api/features/<nom>/schemas` — c'est le zéro-drift de
|
|
58
|
+
la stack front). Un contrat qui traîne du code serveur casse le build du front (constaté :
|
|
59
|
+
un `schemas.ts` → l'`index.ts` d'une feature voisine → son loader → `node:fs`
|
|
60
|
+
dans le bundle navigateur, FAILURES.md) ou y embarque Drizzle. Un `schemas.ts`
|
|
61
|
+
reste donc importable par un navigateur, et ce n'est pas une promesse : le gate
|
|
62
|
+
`pnpm ogygie:verify-contracts` parcourt tout le graphe d'imports depuis
|
|
63
|
+
chaque `schemas.ts` — la TRANSITIVITÉ est le cœur du contrôle, c'est par un
|
|
64
|
+
intermédiaire que le serveur revient — et casse la CI sur tout paquet hors
|
|
65
|
+
liste (`zod` seul), tout `index.ts` atteint, et tout module illisible.
|
|
66
|
+
|
|
67
|
+
## Templates canoniques
|
|
68
|
+
|
|
69
|
+
### 1. Endpoint HTTP
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// features/<nom>/routes.ts
|
|
73
|
+
/**
|
|
74
|
+
* @intent HTTP endpoints of the <nom> feature.
|
|
75
|
+
* No business logic here: schema validation + delegation to the service.
|
|
76
|
+
*/
|
|
77
|
+
import type { FastifyInstance } from "fastify";
|
|
78
|
+
import {
|
|
79
|
+
CreateOrderInputSchema,
|
|
80
|
+
CreateOrderOutputSchema,
|
|
81
|
+
} from "./schemas.js";
|
|
82
|
+
import { createOrder } from "./service.js";
|
|
83
|
+
|
|
84
|
+
export async function registerOrderRoutes(app: FastifyInstance): Promise<void> {
|
|
85
|
+
app.post("/api/orders", {
|
|
86
|
+
schema: {
|
|
87
|
+
body: CreateOrderInputSchema,
|
|
88
|
+
response: { 201: CreateOrderOutputSchema },
|
|
89
|
+
},
|
|
90
|
+
handler: async (request, reply) => {
|
|
91
|
+
const result = await createOrder(
|
|
92
|
+
{ db: app.db, clock: app.clock },
|
|
93
|
+
request.body,
|
|
94
|
+
);
|
|
95
|
+
return reply.code(201).send(result);
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 2. Contrat Zod
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// features/<nom>/schemas.ts
|
|
105
|
+
/**
|
|
106
|
+
* @intent Data contracts of the <nom> feature.
|
|
107
|
+
* Single source of truth: runtime validation + TS types + documentation.
|
|
108
|
+
*/
|
|
109
|
+
import { z } from "zod";
|
|
110
|
+
|
|
111
|
+
export const CreateOrderInputSchema = z.object({
|
|
112
|
+
customerId: z.uuid(),
|
|
113
|
+
reference: z.string().min(1).max(120),
|
|
114
|
+
quantity: z.number().int().positive().max(1_000_000),
|
|
115
|
+
});
|
|
116
|
+
export type CreateOrderInput = z.infer<typeof CreateOrderInputSchema>;
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Règles : suffixe `Schema` obligatoire, type inféré exporté juste en dessous, jamais de type TS écrit à la main pour une donnée qui traverse une frontière. Union discriminée persistée = colonnes nullables par branche + contrainte CHECK miroir de la forme de l'union dans la migration.
|
|
120
|
+
|
|
121
|
+
### 3. Service (logique pure, effets déclarés)
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// features/<nom>/service.ts
|
|
125
|
+
/**
|
|
126
|
+
* @intent Business logic of the <nom> feature. Pure functions:
|
|
127
|
+
* every effectful dependency (db, clock, network) is injected.
|
|
128
|
+
*/
|
|
129
|
+
import type { Database } from "@ogygie/core/db";
|
|
130
|
+
import type { Clock } from "@ogygie/core/clock";
|
|
131
|
+
import type { CreateOrderInput, CreateOrderOutput } from "./schemas.js";
|
|
132
|
+
import { insertOrder } from "./repository.js";
|
|
133
|
+
|
|
134
|
+
export interface CreateOrderDeps {
|
|
135
|
+
db: Database;
|
|
136
|
+
clock: Clock;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function createOrder(
|
|
140
|
+
deps: CreateOrderDeps,
|
|
141
|
+
input: CreateOrderInput,
|
|
142
|
+
): Promise<CreateOrderOutput> {
|
|
143
|
+
// ... business logic ...
|
|
144
|
+
return insertOrder(deps.db, /* ... */);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 4. Accès DB
|
|
149
|
+
|
|
150
|
+
Voir le template repository du §Multi-tenant (il REMPLACE tout accès direct) :
|
|
151
|
+
tout repository reçoit le contexte tenant en premier paramètre et passe par
|
|
152
|
+
`tenantDb`.
|
|
153
|
+
|
|
154
|
+
### 5. Test
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
// features/<nom>/service.test.ts
|
|
158
|
+
/**
|
|
159
|
+
* @intent Proves the behaviors described in specs/<nom>.spec.md.
|
|
160
|
+
* Each Given/When/Then block of the spec = one identically named test.
|
|
161
|
+
*/
|
|
162
|
+
import { describe, expect, it } from "vitest";
|
|
163
|
+
import { createOrder } from "./service.js";
|
|
164
|
+
import { createInMemoryDb, fixedClock } from "@ogygie/core/test-kit";
|
|
165
|
+
|
|
166
|
+
describe("orders", () => {
|
|
167
|
+
it("ÉTANT DONNÉ un client connu QUAND on crée une commande ALORS elle est numérotée séquentiellement", async () => {
|
|
168
|
+
const deps = { db: await createInMemoryDb(migrations), clock: fixedClock("2026-07-01") };
|
|
169
|
+
const result = await createOrder(deps, /* fixture */); // fixture from the spec
|
|
170
|
+
expect(result.sequenceNumber).toBe(1);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 6. Erreur métier
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { ogygieError } from "@ogygie/core/errors";
|
|
179
|
+
|
|
180
|
+
throw ogygieError({
|
|
181
|
+
code: "ORDER_CUSTOMER_NOT_FOUND",
|
|
182
|
+
message: "The targeted customer does not exist for this tenant.",
|
|
183
|
+
file: "src/features/orders/service.ts",
|
|
184
|
+
cause: `customerId=${input.customerId} absent from the customers table`,
|
|
185
|
+
suggestedFix: "Create the customer via POST /api/customers before creating an order.",
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Workflow de livraison d'une feature
|
|
190
|
+
|
|
191
|
+
1. Lire la spec (`specs/<nom>.spec.md`, status `validated`). Pas de spec validée = pas de code.
|
|
192
|
+
Amender une spec en cours de session = commit séparé dédié, signalé dans la PR et validé en revue.
|
|
193
|
+
2. Écrire `schemas.ts` d'abord (les contrats), puis les tests (depuis les Given/When/Then), puis l'implémentation.
|
|
194
|
+
3. Mettre à jour `ogygie.manifest.json` (feature, routes, dépendances).
|
|
195
|
+
4. `pnpm test -- features/<nom>` vert en < 10 s.
|
|
196
|
+
5. Si un pattern se répète pour la 3e fois → le signaler comme candidat à l'extraction `@ogygie/core`.
|
|
197
|
+
Exception : l'outillage et l'infrastructure GÉNÉRIQUES peuvent naître ou
|
|
198
|
+
monter dans le core sans attendre 3 répétitions ; le code métier, jamais. Critère : le
|
|
199
|
+
module contient-il une seule décision propre au produit ? Si oui, il reste dans `apps/`.
|
|
200
|
+
6. Si tu as halluciné une API, cassé une feature ou tourné en rond → consigner dans `FAILURES.md` (date, symptôme, cause racine, convention qui l'aurait empêché).
|
|
201
|
+
7. Tout gate CI (verify-*, lint) naît et se durcit avec son test négatif : un test de mutation prouve qu'une violation connue est détectée.
|
|
202
|
+
8. Git : force-push, réécriture d'historique et isolation des sessions
|
|
203
|
+
(un worktree par session) → voir §Évolution d'une feature livrée,
|
|
204
|
+
« Règles git ». Une seule formulation dans ce fichier, pas deux.
|
|
205
|
+
|
|
206
|
+
## Interdits absolus
|
|
207
|
+
|
|
208
|
+
- `any`, `as unknown as`, `@ts-ignore` (utiliser `@ts-expect-error` avec justification si inévitable).
|
|
209
|
+
- Import d'un fichier interne d'une autre feature (passer par son `index.ts`).
|
|
210
|
+
- Client DB/HTTP importé globalement dans un helper.
|
|
211
|
+
- Type écrit à la main pour une donnée qui a un schéma Zod.
|
|
212
|
+
- Commit qui modifie une feature sans mettre à jour manifeste + tests.
|
|
213
|
+
- **Garde de nullité écrite `=== null` / `!== null` sur un champ OPTIONNEL ou
|
|
214
|
+
ÉLARGI.** Elle s'écrit `== null` / `!= null` — qui attrape `null` ET
|
|
215
|
+
`undefined` — ou la donnée passe par son schéma Zod (dont le `.default(null)`
|
|
216
|
+
normalise). Motif, constaté (FAILURES.md 2026-07-31) : une donnée AU REPOS
|
|
217
|
+
écrite avant l'ajout du champ ne porte pas la clé, donc vaut `undefined` et
|
|
218
|
+
non `null` ; la garde stricte y lit « présent » et le code croit à une valeur
|
|
219
|
+
saisie qui n'existe pas. Le contrat porte bien le défaut, mais rien n'oblige
|
|
220
|
+
un appelant à parser : fixtures, tests et scripts lisent le JSON brut.
|
|
221
|
+
|
|
222
|
+
## Internationalisation (règles worldwide — non négociables)
|
|
223
|
+
|
|
224
|
+
1. **Domaine en unités SI canoniques.** La base et les services stockent et calculent
|
|
225
|
+
TOUJOURS dans les unités SI du domaine (mètres, m², kg, litres…). Jamais une unité
|
|
226
|
+
locale (impériale…) dans un calcul. La conversion est un problème de PRÉSENTATION,
|
|
227
|
+
résolu une fois dans le front via `preferences.unitSystem` du tenant.
|
|
228
|
+
2. **Argent = jamais un float.** Tout montant est `{ amountCents: int, currency: ISO4217 }`.
|
|
229
|
+
Les calculs monétaires passent par les helpers de `@ogygie/core/money` (à créer).
|
|
230
|
+
Un tenant a UNE devise de travail ; pas de conversion de change en v1.
|
|
231
|
+
3. **Aucune chaîne UI en dur.** Tout texte visible passe par une clé de traduction ICU
|
|
232
|
+
(`t("orders.create.title")` — clés de traduction en anglais). Les libellés saisis
|
|
233
|
+
par l'utilisateur sont des DONNÉES, jamais traduits.
|
|
234
|
+
4. **Les messages d'OgygieError ont deux canaux** : `message` (anglais technique, pour
|
|
235
|
+
les logs et les agents) + `userMessageKey` (clé ICU pour l'affichage).
|
|
236
|
+
`suggestedFix` reste en anglais technique.
|
|
237
|
+
5. **Temps** : dates stockées en UTC ISO 8601 ; chaque tenant a un fuseau IANA.
|
|
238
|
+
Les semaines sont des semaines ISO 8601 (lundi = premier jour) partout — l'affichage
|
|
239
|
+
local peut différer, pas le calcul.
|
|
240
|
+
6. **Identifiants code en anglais, contenu en langue de l'utilisateur.** Les enums du
|
|
241
|
+
domaine sont en anglais ; leur affichage est traduit.
|
|
242
|
+
|
|
243
|
+
## Front (apps/web) — règles et templates canoniques
|
|
244
|
+
|
|
245
|
+
### Stack imposée (toute autre lib = amendement DECISIONS.md d'abord)
|
|
246
|
+
Vite + React 19 + TanStack Router (routing EN CODE) + TanStack Query (état serveur) ;
|
|
247
|
+
PAS de lib d'état client (useState suffit) ; Tailwind v4 thémé depuis `design/tokens.json`
|
|
248
|
+
(valeurs arbitraires `p-[13px]` interdites par lint) ; primitives UI copiées dans
|
|
249
|
+
`apps/web/src/patterns/` (jamais consommées directement depuis node_modules par une
|
|
250
|
+
feature) ; react-hook-form UNIQUEMENT via le pattern `ContractForm` ; TanStack
|
|
251
|
+
Table UNIQUEMENT via `DataTable` ; Recharts UNIQUEMENT via `VarianceChart` ;
|
|
252
|
+
date-fns via les helpers `@ogygie/core` (jamais de `new Date()` nu) ; i18n Lingui
|
|
253
|
+
(ICU, extraction vérifiée en CI). Noms de patterns en anglais.
|
|
254
|
+
|
|
255
|
+
### Structure d'une feature front (miroir de l'API)
|
|
256
|
+
```
|
|
257
|
+
apps/web/features/orders/
|
|
258
|
+
├── index.ts # seul export public
|
|
259
|
+
├── routes.tsx # définition TanStack Router de la feature
|
|
260
|
+
├── queries.ts # hooks TanStack Query (fetch + invalidations)
|
|
261
|
+
├── OrdersList.tsx # écrans = composition de patterns, rien d'autre
|
|
262
|
+
├── OrderForm.tsx
|
|
263
|
+
└── OrdersList.test.tsx # un test par état UI de la spec
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Template canonique — hook de requête
|
|
267
|
+
```ts
|
|
268
|
+
// apps/web/features/orders/queries.ts
|
|
269
|
+
/** @intent Data access of the orders feature. Re-validates contracts in dev. */
|
|
270
|
+
import { useQuery } from "@tanstack/react-query";
|
|
271
|
+
import { ListOrdersOutputSchema } from "@product/api/features/orders/schemas";
|
|
272
|
+
import { fetchContract } from "../../lib/fetchContract"; // Zod parse in dev, OgygieError on broken contract
|
|
273
|
+
|
|
274
|
+
export function useOrders() {
|
|
275
|
+
return useQuery({
|
|
276
|
+
queryKey: ["orders"],
|
|
277
|
+
queryFn: () => fetchContract("/api/orders", ListOrdersOutputSchema),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Template canonique — écran
|
|
283
|
+
```tsx
|
|
284
|
+
// apps/web/features/orders/OrdersList.tsx
|
|
285
|
+
/** @intent Orders list screen: composition of patterns, zero business logic. */
|
|
286
|
+
import { Trans } from "@lingui/react/macro";
|
|
287
|
+
import { DataTable } from "../../patterns/DataTable";
|
|
288
|
+
import { EmptyState } from "../../patterns/EmptyState";
|
|
289
|
+
import { ErrorBanner } from "../../patterns/ErrorBanner";
|
|
290
|
+
import { useOrders } from "./queries";
|
|
291
|
+
|
|
292
|
+
export function OrdersList() {
|
|
293
|
+
const { data, isPending, error } = useOrders();
|
|
294
|
+
if (error) return <ErrorBanner error={error} />;
|
|
295
|
+
if (isPending) return <DataTable.Skeleton />;
|
|
296
|
+
if (data.orders.length === 0)
|
|
297
|
+
return <EmptyState title={<Trans>Ajoutez votre première commande</Trans>} />;
|
|
298
|
+
return <DataTable rows={data.orders} columns={orderColumns} />;
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Règles front absolues
|
|
303
|
+
- Un écran est une COMPOSITION de patterns : jamais de `<table>`, `<form>` ou fetch nus
|
|
304
|
+
dans une feature. Si un pattern manque, on le crée dans `patterns/` (et on met à jour
|
|
305
|
+
`design/patterns.md`), on ne bricole pas dans la feature.
|
|
306
|
+
- Chaque état UI listé dans la spec (vide/chargement/nominal/erreur) a son test.
|
|
307
|
+
- Toute chaîne visible passe par Lingui (`<Trans>`/`t`) — la CI casse sinon.
|
|
308
|
+
- Chiffres financiers : `tabular-nums`, alignés à droite, unité explicite.
|
|
309
|
+
|
|
310
|
+
### La boucle design (specs/ogygie-design-brief.spec.md)
|
|
311
|
+
Avant de maquetter un écran, générer le brief DÉRIVÉ de la feature :
|
|
312
|
+
```
|
|
313
|
+
pnpm ogygie:design-brief <feature> → design/briefs/<feature>.md
|
|
314
|
+
```
|
|
315
|
+
Le brief tire tout des sources de vérité : tokens (`design/tokens.json`),
|
|
316
|
+
patterns (`design/patterns.md` + les modules des apps front), champs des
|
|
317
|
+
contrats Zod des routes, actions par méthode, états UI de la spec, codes
|
|
318
|
+
d'erreur réels, volumétries des fixtures. L'outil de design (humain ou IA)
|
|
319
|
+
produit une mise en page — jamais du code ; la décision humaine revient dans
|
|
320
|
+
la spec de la feature (section « # Design »), puis l'implémentation suit la
|
|
321
|
+
spec. Règle non négociable : le design ne crée jamais de token ni de
|
|
322
|
+
composant one-off — une couleur ou un espacement absent est un amendement de
|
|
323
|
+
`design/tokens.json`, un pattern manquant naît dans le catalogue.
|
|
324
|
+
|
|
325
|
+
## Multi-tenant (SaaS) — sécurité structurelle, non optionnelle
|
|
326
|
+
|
|
327
|
+
Le produit est un SaaS multi-tenant. La fuite inter-tenants est le risque n°1
|
|
328
|
+
d'un code écrit par agents : elle doit être IMPOSSIBLE par construction, pas évitée
|
|
329
|
+
par vigilance.
|
|
330
|
+
|
|
331
|
+
1. **Toute fonction de repository reçoit un contexte tenant en premier paramètre**,
|
|
332
|
+
fourni par le middleware d'auth — jamais par le body ou la query string du client.
|
|
333
|
+
Le core expose le type GÉNÉRIQUE `TenantContext` (`{ tenantId: uuid, userId: uuid }`) ;
|
|
334
|
+
le produit l'aliase sous son propre nom (déclaré dans `AGENTS.product.md`) — le core
|
|
335
|
+
ne connaît AUCUN mot du domaine du produit (règle de séparation, le journal des décisions).
|
|
336
|
+
2. **Tout accès Drizzle passe par `tenantDb(ctx)`** (`@ogygie/core/tenant`) qui
|
|
337
|
+
applique systématiquement le filtre de la colonne tenant (nom déclaré dans
|
|
338
|
+
`AGENTS.product.md`). Accès Drizzle direct hors de ce helper = interdit
|
|
339
|
+
(règle lint `ogygie/no-raw-db`).
|
|
340
|
+
3. **Row-Level Security PostgreSQL activée sur toute table portant la colonne
|
|
341
|
+
tenant** (second rempart : même une requête buggée ne peut pas lire un
|
|
342
|
+
autre tenant). Toute migration créant une table tenant inclut sa policy RLS —
|
|
343
|
+
le template de migration l'impose.
|
|
344
|
+
4. Les tables de référentiel PARTAGÉ — connaissance universelle qu'aucun tenant
|
|
345
|
+
ne possède, chargée par un script d'extraction depuis une source externe
|
|
346
|
+
versionnée — sont déclarées explicitement au manifeste sous la forme objet
|
|
347
|
+
`{ "name": "<table>", "shared": true }` (clé anglaise : le manifeste est
|
|
348
|
+
machine-lisible). Elles échappent alors à la règle 3 (pas de colonne
|
|
349
|
+
tenant, pas de policy RLS tenant) ET à la règle 2 : n'ayant aucune colonne
|
|
350
|
+
sur laquelle filtrer, elles se lisent hors de `tenantDb`. Ce droit se paie
|
|
351
|
+
comptant, et les gates le vérifient au lieu de faire confiance :
|
|
352
|
+
- une feature ne gagne l'accès brut à la base que si TOUTES ses tables sont
|
|
353
|
+
partagées (lint-interdits, dérivé du manifeste) — une seule table tenant
|
|
354
|
+
dans la même feature le lui retire ;
|
|
355
|
+
- une table déclarée partagée qui porte encore la colonne tenant casse
|
|
356
|
+
verify-manifest (`SHARED_TABLE_WITH_TENANT_COLUMN`) ;
|
|
357
|
+
- côté API ces tables sont en LECTURE SEULE : seul le script d'extraction
|
|
358
|
+
les alimente.
|
|
359
|
+
La liste des tables partagées du produit est dans `AGENTS.product.md`.
|
|
360
|
+
|
|
361
|
+
### Template repository (le SEUL accès DB autorisé)
|
|
362
|
+
```ts
|
|
363
|
+
// features/<nom>/repository.ts
|
|
364
|
+
/** @intent Only file of the feature allowed to touch the DB, always through tenantDb. */
|
|
365
|
+
import type { ProductContext } from "../../context.js"; // the product's alias of TenantContext (real name: AGENTS.product.md)
|
|
366
|
+
import { tenantDb } from "@ogygie/core/tenant";
|
|
367
|
+
import { orders } from "./db.schema.js";
|
|
368
|
+
|
|
369
|
+
export async function insertOrder(ctx: ProductContext, values: NewOrder) {
|
|
370
|
+
const [row] = await tenantDb(ctx).insert(orders).values(values).returning();
|
|
371
|
+
return row; // tenant column injected and filtered by tenantDb, never by hand
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
Les services propagent `ctx` tel quel (routes → service → repository) sans jamais le
|
|
375
|
+
reconstruire ni le modifier.
|
|
376
|
+
|
|
377
|
+
### Piège connu de la façade tenantDb (TS2353)
|
|
378
|
+
L'excess-property-check TypeScript bute sur les littéraux inline passés au
|
|
379
|
+
`set()` générique de `TenantDb` (TS2353 alors que la propriété existe).
|
|
380
|
+
Convention : passer des VARIABLES TYPÉES sur les types concrets
|
|
381
|
+
(`Partial<Omit<NewX, ...>>`), jamais des littéraux inline. Plus lisible, et
|
|
382
|
+
le compilateur est d'accord.
|
|
383
|
+
|
|
384
|
+
## Sécurité — interdits vérifiables
|
|
385
|
+
|
|
386
|
+
La sécurité est structurelle : un agent suit des rails, il ne « fait pas attention ».
|
|
387
|
+
S'ajoutent au multi-tenant (§ précédent) et aux contrats Zod :
|
|
388
|
+
|
|
389
|
+
1. **Secrets** : uniquement via variables d'environnement, validées au démarrage par
|
|
390
|
+
le schéma de `apps/api/src/env.ts` (boot refusé si un secret manque). Aucun secret
|
|
391
|
+
en dur nulle part — gitleaks casse la CI sinon.
|
|
392
|
+
2. **Tout identifiant venant du client est hostile.** L'appartenance fait foi via
|
|
393
|
+
le contexte tenant ; jamais d'identifiant tenant lu depuis body/query/params
|
|
394
|
+
(seule exception : la route de bascule du tenant actif, qui vérifie l'appartenance).
|
|
395
|
+
3. **Front** : `dangerouslySetInnerHTML` interdit (lint). On ne débranche jamais
|
|
396
|
+
l'échappement React.
|
|
397
|
+
4. **Bootstrap Fastify** : `@fastify/helmet` + `@fastify/rate-limit` posés une fois
|
|
398
|
+
dans `apps/api/src/server.ts` — jamais feature par feature.
|
|
399
|
+
5. **Sessions** : cookies `httpOnly + secure + sameSite=lax` (vérifier la config
|
|
400
|
+
Better Auth, ne pas présumer).
|
|
401
|
+
6. **Dépendances** : ajouter un paquet npm est un événement RARE. Justification
|
|
402
|
+
obligatoire dans la PR, nom du paquet vérifié sur le registre (anti-slopsquatting),
|
|
403
|
+
`--frozen-lockfile` en CI. En cas de doute sur un nom de paquet : STOP et demander.
|
|
404
|
+
7. **Erreurs en production** : seuls les champs d'OgygieError sortent vers le client ;
|
|
405
|
+
jamais de stack trace ni de détail interne.
|
|
406
|
+
8. **URL d'API en dur côté front : interdit.** Le front appelle `/api/...`
|
|
407
|
+
en relatif — il ne connaît ni l'hôte ni le port de l'API. Toute
|
|
408
|
+
URL absolue vers l'API dans apps/web est une violation (lint).
|
|
409
|
+
9. **La passerelle n'autorise jamais.** Aucune règle d'accès, aucun filtre
|
|
410
|
+
métier dans apps/gateway : l'autorisation vit dans l'API (session →
|
|
411
|
+
contexte tenant → tenantDb). Un relais qui autorise devient un confused
|
|
412
|
+
deputy.
|
|
413
|
+
10. **Toute route publique passe par le manifeste.** Une route API absente
|
|
414
|
+
du manifeste est inatteignable depuis l'extérieur (la passerelle ne la
|
|
415
|
+
monte pas) — le manifeste est aussi une surface de sécurité.
|
|
416
|
+
|
|
417
|
+
## Évolution d'une feature livrée
|
|
418
|
+
|
|
419
|
+
Modifier une feature existante suit un protocole aussi canonique que sa création.
|
|
420
|
+
Le contexte complet d'une feature vit dans TROIS artefacts : son entrée au
|
|
421
|
+
manifeste, sa spec, son dossier — jamais dans d'anciennes PR ni de mémoire de
|
|
422
|
+
session. Si ces trois-là ne suffisent pas à comprendre la feature : STOP,
|
|
423
|
+
FAILURES.md.
|
|
424
|
+
|
|
425
|
+
### Cycle de vie de la spec
|
|
426
|
+
- Une évolution ROUVRE la spec : commit « spec amendment » qui modifie les
|
|
427
|
+
comportements ET repasse `status: implemented → validated`.
|
|
428
|
+
- La spec décrit toujours L'ÉTAT COURANT VOULU (jamais un empilement de deltas) ;
|
|
429
|
+
l'historique vit dans une section `# Changelog` en fin de spec :
|
|
430
|
+
`## v2 — AAAA-MM — <ajouts / retraits / motif>` + le git log.
|
|
431
|
+
- Retour à `implemented` : chaque nouveau comportement a son test, les anciens
|
|
432
|
+
tests restent verts, SAUF ceux d'un comportement explicitement retiré au
|
|
433
|
+
Changelog (le retrait d'un test sans ligne de Changelog = violation).
|
|
434
|
+
- Gate verify-specs : feature `delivered` + spec `validated` = « évolution en
|
|
435
|
+
cours », état légitime et visible ; jamais un état permanent (> 1 PR = signal).
|
|
436
|
+
|
|
437
|
+
### Première commande obligatoire : le rayon d'impact
|
|
438
|
+
Avant toute modification d'une feature livrée, lancer :
|
|
439
|
+
```
|
|
440
|
+
pnpm ogygie:impact <feature>
|
|
441
|
+
```
|
|
442
|
+
Tout dépendant listé doit rester vert à la fin de la session. Un dépendant
|
|
443
|
+
cassé sciemment (contrat qui change) est migré DANS LA MÊME PR.
|
|
444
|
+
|
|
445
|
+
### Contrats exportés : élargir libre, restreindre = événement
|
|
446
|
+
- ÉLARGIR un schéma exporté (champ optionnel en plus, nouvelle valeur d'enum
|
|
447
|
+
en sortie) : libre.
|
|
448
|
+
- RESTREINDRE ou RENOMMER (champ retiré/renommé, contrainte durcie, valeur
|
|
449
|
+
d'enum supprimée, route déplacée) : exige `ogygie:impact`, migration de tous
|
|
450
|
+
les consommateurs dans la même PR, et mention explicite dans la description
|
|
451
|
+
de PR. C'est notre semver à l'échelle du monorepo : tout bouge ensemble ou
|
|
452
|
+
rien ne bouge.
|
|
453
|
+
|
|
454
|
+
### Migrations en évolution : additives d'abord (expand → migrate → contract)
|
|
455
|
+
À la création d'une feature, une migration est libre. En évolution, il y a des
|
|
456
|
+
données réelles :
|
|
457
|
+
1. EXPAND : ajouter (colonne nullable, nouvelle table, nouvel index) — jamais
|
|
458
|
+
casser l'existant dans ce pas.
|
|
459
|
+
2. MIGRATE : remplir/backfiller, basculer le code sur le nouveau chemin.
|
|
460
|
+
3. CONTRACT : supprimer l'ancien (colonne, contrainte) — dans une release
|
|
461
|
+
ULTÉRIEURE, jamais le même lot que le code qui l'exploite.
|
|
462
|
+
- Interdits en un seul lot : RENAME de colonne/table exploitée, DROP d'une
|
|
463
|
+
colonne encore lue, changement de type en place. Toute migration destructive
|
|
464
|
+
= section dédiée dans la description de PR.
|
|
465
|
+
- Rappel : toute nouvelle table tenant garde sa policy RLS.
|
|
466
|
+
|
|
467
|
+
### Règles git
|
|
468
|
+
- **Force-push : sur une branche de feature avant revue, OUI ; sur `main`,
|
|
469
|
+
JAMAIS** (la protection de branche doit l'interdire côté GitHub).
|
|
470
|
+
- Toute réécriture d'historique (rebase, cherry-pick, reconstruction depuis
|
|
471
|
+
le reflog) se conclut par DEUX preuves avant push : `git diff` vide contre
|
|
472
|
+
l'état validé, et boucle complète re-passée verte. Sans ces deux preuves,
|
|
473
|
+
ne pas pousser.
|
|
474
|
+
- **`git checkout/restore -- <chemin>` ne vise JAMAIS un répertoire** tant que
|
|
475
|
+
le travail n'est pas committé : il écrase l'arbre sans confirmation et sans
|
|
476
|
+
rien laisser au reflog (constaté, FAILURES.md 2026-07-31 — dix fichiers de
|
|
477
|
+
session effacés par une commande de commodité). Pour comparer avec HEAD,
|
|
478
|
+
extraire une COPIE (`git show HEAD:<fichier>`), ne pas remettre l'arbre à
|
|
479
|
+
HEAD.
|
|
480
|
+
- **Une session = un worktree** : ne bascule JAMAIS la branche d'un
|
|
481
|
+
répertoire que tu ne sais pas exclusivement tien. Si `git status` montre
|
|
482
|
+
des modifications qui ne sont pas les tiennes : STOP, signale-le, ne
|
|
483
|
+
stash pas.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# AGENTS.product.md — Extension produit : __PRODUCT_NAME__
|
|
2
|
+
|
|
3
|
+
> Lu APRÈS `AGENTS.md` (les règles génériques du framework Ogygie), qu'il ne
|
|
4
|
+
> répète jamais : ce fichier ne contient que les LIAISONS — les noms réels que
|
|
5
|
+
> les règles génériques laissent en placeholder, et les règles qui n'existent
|
|
6
|
+
> que pour ce produit. C'est LE premier fichier à remplir après `ogygie init` :
|
|
7
|
+
> tant qu'une section dit « à définir », la règle générique correspondante n'a
|
|
8
|
+
> pas de prise sur ce produit.
|
|
9
|
+
|
|
10
|
+
## Identité
|
|
11
|
+
|
|
12
|
+
- Produit : **__PRODUCT_NAME__** — à décrire : le job-to-be-done du produit en
|
|
13
|
+
une phrase (reporter aussi la description dans `ogygie.manifest.json`).
|
|
14
|
+
- Les ids de features/specs sont en FRANÇAIS : ils nomment des documents, pas
|
|
15
|
+
du code.
|
|
16
|
+
|
|
17
|
+
## Liaisons multi-tenant
|
|
18
|
+
|
|
19
|
+
- Tenant du produit : **à définir** (le mot du domaine qui possède les
|
|
20
|
+
données). L'alias produit de `TenantContext` est **`<Tenant>Context`**
|
|
21
|
+
(`apps/api/src/context.ts`) : clé TS de colonne `<tenant>Id`, colonne SQL
|
|
22
|
+
**`<tenant>_id`** — à déclarer aussi dans `scripts/verify-manifest.ts`
|
|
23
|
+
(`TENANT_COLUMN_NAMES`).
|
|
24
|
+
- Liste fermée du socle autorisé à toucher la base brute : **à définir** (la
|
|
25
|
+
feature qui CONSTRUIT les contextes tenant) — déclarée dans
|
|
26
|
+
`scripts/lint-interdits.ts` (`RAW_DB_SOCLE_ALLOWLIST`). Toute autre feature
|
|
27
|
+
passe par `tenantDb`, sauf les features à tables 100 % partagées (dérivé du
|
|
28
|
+
manifeste).
|
|
29
|
+
- Seule route autorisée à recevoir un identifiant de tenant côté client : la
|
|
30
|
+
route de bascule du tenant actif (appartenance vérifiée, 403 opaque sinon) —
|
|
31
|
+
l'exception prévue par AGENTS.md §Sécurité 2. À nommer ici quand elle existe.
|
|
32
|
+
|
|
33
|
+
## Contrats produit partagés (exception §Structure)
|
|
34
|
+
|
|
35
|
+
- Aucun pour l'instant : la liste fermée est VIDE. Le jour où deux features
|
|
36
|
+
partagent un contrat transverse, il naît sous `apps/api/src/` et s'ajoute
|
|
37
|
+
ici.
|
|
38
|
+
|
|
39
|
+
## Tables partagées du produit (§Multi-tenant règle 4)
|
|
40
|
+
|
|
41
|
+
- Aucune pour l'instant. Une table de référentiel partagé se déclare au
|
|
42
|
+
manifeste (`{ "name": "...", "shared": true }`) et s'ajoute ici avec sa
|
|
43
|
+
source et son script d'extraction.
|
|
44
|
+
|
|
45
|
+
## Règles produit
|
|
46
|
+
|
|
47
|
+
- (aucune pour l'instant — toute règle qui ne vaut que pour ce produit se
|
|
48
|
+
consigne ici, jamais dans AGENTS.md, qui est gouverné par Ogygie)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Instructions pour Claude Code
|
|
2
|
+
|
|
3
|
+
Avant TOUTE tâche, lis dans cet ordre :
|
|
4
|
+
1. `AGENTS.md` — les règles et templates canoniques GÉNÉRIQUES (obligatoire, en entier)
|
|
5
|
+
2. `AGENTS.product.md` — l'extension produit : alias, colonnes tenant, listes fermées (obligatoire, en entier)
|
|
6
|
+
3. `ogygie.manifest.json` — la carte du repo
|
|
7
|
+
4. `DECISIONS.md` (framework) et `DECISIONS.product.md` (produit) — les décisions actées (ne jamais les rouvrir ; numérotation D<n> globale sur les deux fichiers)
|
|
8
|
+
5. La spec de la feature concernée dans `specs/` (une spec `status: validated` = droit d'implémenter)
|
|
9
|
+
|
|
10
|
+
N'explore pas le repo au-delà du dossier de la feature concernée.
|
|
11
|
+
Si les fichiers ci-dessus ne suffisent pas pour accomplir la tâche : STOP,
|
|
12
|
+
consigne le manque dans `FAILURES.md` et demande, plutôt que de deviner.
|
|
13
|
+
|
|
14
|
+
Communication en français. Frontière linguistique : tout ce qui est
|
|
15
|
+
MACHINE-LISIBLE est en ANGLAIS — identifiants, fonctions, tables et colonnes
|
|
16
|
+
BDD, routes HTTP, codes d'erreur, valeurs d'enum, noms de schémas/contrats et
|
|
17
|
+
de patterns, clés du manifeste, champs OgygieError, et TOUS les commentaires
|
|
18
|
+
(y compris `@intent`) — aucune exception. Tout ce qui est destiné aux HUMAINS
|
|
19
|
+
du projet reste en FRANÇAIS : specs (prose et titres de sections),
|
|
20
|
+
DECISIONS.md, DECISIONS.product.md, FAILURES.md, PR, textes UI via clés ICU,
|
|
21
|
+
ids de features/specs.
|
|
22
|
+
|
|
23
|
+
La boucle verte du repo est `pnpm verify` (gates → typecheck → lint → tests).
|
|
24
|
+
Elle doit être verte avant tout commit.
|
|
25
|
+
|
|
26
|
+
N'utilise AUCUN skill ou workflow de processus externe (superpowers, brainstorming,
|
|
27
|
+
plans multi-étapes génériques) : le processus de ce repo est défini par AGENTS.md
|
|
28
|
+
et les specs, rien d'autre. Si ce processus te semble insuffisant pour une tâche,
|
|
29
|
+
consigne le manque dans FAILURES.md au lieu de compenser par un skill.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# DECISIONS.md — Décisions structurantes du FRAMEWORK
|
|
2
|
+
|
|
3
|
+
> Chaque décision est datée, motivée, et cite ce qu'elle exclut. Un agent ne
|
|
4
|
+
> rouvre pas une décision actée : il la lit ici et s'y conforme (ou propose un
|
|
5
|
+
> amendement). Ce fichier porte les décisions FRAMEWORK (conventions,
|
|
6
|
+
> outillage, gates) ; les décisions propres au produit vivent dans
|
|
7
|
+
> `DECISIONS.product.md`. La numérotation `D<n>` est GLOBALE aux deux fichiers
|
|
8
|
+
> — un même numéro ne peut exister qu'à un seul endroit (gate
|
|
9
|
+
> DECISION_DECLARED_TWICE).
|
|
10
|
+
>
|
|
11
|
+
> Format d'une décision :
|
|
12
|
+
>
|
|
13
|
+
> ## D<n> — AAAA-MM-JJ — <titre>
|
|
14
|
+
> - **Décision** : …
|
|
15
|
+
> - **Motivation** : …
|
|
16
|
+
> - **Exclut** : …
|
|
17
|
+
|
|
18
|
+
(aucune décision pour l'instant — la première sera D1)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# DECISIONS.product.md — Décisions structurantes du PRODUIT
|
|
2
|
+
|
|
3
|
+
> Chaque décision est datée, motivée, et cite ce qu'elle exclut. Un agent ne
|
|
4
|
+
> rouvre pas une décision actée : il la lit ici et s'y conforme (ou propose un
|
|
5
|
+
> amendement). Ce fichier porte les décisions propres au PRODUIT (périmètre,
|
|
6
|
+
> domaine, arbitrages métier) ; les décisions du framework vivent dans
|
|
7
|
+
> `DECISIONS.md`. La numérotation `D<n>` est GLOBALE aux deux fichiers — un
|
|
8
|
+
> même numéro ne peut exister qu'à un seul endroit (gate
|
|
9
|
+
> DECISION_DECLARED_TWICE).
|
|
10
|
+
|
|
11
|
+
(aucune décision pour l'instant)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# FAILURES.md — Journal des échecs
|
|
2
|
+
|
|
3
|
+
> Spec vivante du framework : chaque fois qu'un agent hallucine une API, casse
|
|
4
|
+
> une feature ou tourne en rond, l'échec se consigne ICI — date, symptôme,
|
|
5
|
+
> cause racine, et la convention qui l'aurait empêché. Un échec non consigné
|
|
6
|
+
> se reproduira.
|
|
7
|
+
>
|
|
8
|
+
> Format d'une entrée :
|
|
9
|
+
>
|
|
10
|
+
> ## AAAA-MM-JJ — <symptôme en une ligne>
|
|
11
|
+
> - **Symptôme** : …
|
|
12
|
+
> - **Cause racine** : …
|
|
13
|
+
> - **Convention qui l'aurait empêché** : … (et si elle n'existe pas : la
|
|
14
|
+
> proposer en amendement d'AGENTS.md ou en décision)
|
|
15
|
+
|
|
16
|
+
(aucun échec consigné pour l'instant)
|