Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .claude/commands/add-tool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
description: Scaffolder un nouvel outil pédagogique (front + back) selon ADD-A-TOOL.md
argument-hint: <slug> "<Nom affiché>"
---

Tu vas ajouter un nouvel outil pédagogique au projet CraftCode en suivant
**exactement** la procédure de @ADD-A-TOOL.md. Respecte @NAMING-CONVENTIONS.md,
@DESIGN-SYSTEM.md, @CONTENT-STYLE.md et @STATE-AND-DATA.md.

Arguments fournis : `$ARGUMENTS`
(format attendu : `slug "Nom affiché"` ; si le slug ou le nom manque, demande-le
avant de commencer.)

Déroule les étapes dans cet ordre, sans en sauter :

1. **Modèle** — `frontend/src/app/core/models/<slug>.model.ts` : interface `PascalCase`,
champs techniques en anglais, contenu en français.
2. **Données** — `frontend/src/app/core/data/<slug>.ts` : constante `SCREAMING_SNAKE_CASE`
typée par le modèle, chaque entrée avec un `slug` stable. Contenu rédigé selon le ton
« atelier » (@CONTENT-STYLE.md).
3. **Hub** — `frontend/src/app/features/<slug>/<slug>.component.{ts,html,scss}` :
`selector: 'app-<slug>'`, grille de cartes, breadcrumb. SCSS via tokens `--cc-*` uniquement.
4. **Détail** (si le contenu s'y prête) — `features/<slug>-detail/` avec route paramétrée
`<slug>/:param`, résolution réactive du param et redirection si inconnu.
5. **Routes** — ajoute les routes lazy dans `frontend/src/app/app.routes.ts`
(garde `{ path: '**' }` en dernier).
6. **Navigation** — ajoute une entrée `navLinks` dans `frontend/src/app/app.component.ts`.
7. **Seed** — ajoute l'entrée dans le tableau `tools` de `backend/seed/data.js`
(incrémente `order`, slug kebab-case, `available: true`).

Puis **vérifie** :
- `cd frontend && npm run lint` → vert
- `cd backend && npm test` → vert (les invariants du seed doivent passer)
- `cd frontend && npm run build` → build OK

Termine par un résumé des fichiers créés/modifiés. Ne committe rien sans demande explicite.
80 changes: 80 additions & 0 deletions .claude/hooks/format-edited-file.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env node
/**
* Hook PostToolUse (Edit|Write|MultiEdit) — CraftCode.
*
* 1. Formate automatiquement le fichier édité avec le Prettier du bon package
* (frontend/ ou backend/), en s'appuyant sur la config racine `.prettierrc.json`.
* 2. Garde-fou design system : signale (sans bloquer dur) toute couleur hexadécimale
* écrite dans un `.scss` de composant — la source unique des couleurs est
* `frontend/src/styles.scss` (tokens `--cc-*`). Cf. DESIGN-SYSTEM.md.
*
* Ne lève jamais d'exception : un hook qui plante ne doit pas bloquer le travail.
*/
import { execFileSync } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';

const PRETTIER_EXT = /\.(ts|js|mjs|cjs|html|scss|css|json|md)$/;
const ROOT = '/Users/julien/Dev/CraftCode';

function readStdin() {
try {
return readFileSync(0, 'utf8');
} catch {
return '';
}
}

function prettierBinFor(filePath) {
const pkg = filePath.includes('/frontend/')
? 'frontend'
: filePath.includes('/backend/')
? 'backend'
: null;
if (!pkg) return null;
const bin = join(ROOT, pkg, 'node_modules', '.bin', 'prettier');
return existsSync(bin) ? bin : null;
}

function main() {
let payload;
try {
payload = JSON.parse(readStdin());
} catch {
process.exit(0);
}

const filePath = payload?.tool_input?.file_path;
if (!filePath || !PRETTIER_EXT.test(filePath)) process.exit(0);

// 1. Formatage
const bin = prettierBinFor(filePath);
if (bin) {
try {
execFileSync(bin, ['--write', '--ignore-unknown', filePath], {
stdio: 'ignore',
});
} catch {
/* fichier ignoré par .prettierignore ou non parsable : on n'échoue pas */
}
}

// 2. Garde-fou couleurs (avertissement non bloquant)
const isComponentScss =
/\/frontend\/src\/.*\.scss$/.test(filePath) &&
!filePath.endsWith('/styles.scss');
if (isComponentScss && existsSync(filePath)) {
const content = readFileSync(filePath, 'utf8');
if (/#[0-9a-fA-F]{3,8}\b/.test(content)) {
console.error(
`⚠️ ${filePath} contient une couleur hexadécimale. ` +
`Utilise un token --cc-* (cf. DESIGN-SYSTEM.md), jamais une valeur en dur.`
);
process.exit(2); // remonte le message à Claude sans interrompre la session
}
}

process.exit(0);
}

main();
39 changes: 39 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"permissions": {
"allow": [
"Bash(npm run lint)",
"Bash(npm run lint:fix)",
"Bash(npm run format)",
"Bash(npm run format:check)",
"Bash(npm test)",
"Bash(npm run test)",
"Bash(npm run build)",
"Bash(npm run seed)",
"Bash(npx ng lint)",
"Bash(npx ng build*)",
"Bash(npx prettier*)",
"Bash(npx eslint*)",
"Bash(npx vitest*)",
"Bash(git status*)",
"Bash(git diff*)",
"Bash(git log*)",
"Bash(git add*)",
"Bash(git branch*)",
"Bash(curl -s http://localhost:3000/*)",
"Bash(curl -s http://localhost:4200/*)"
]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/format-edited-file.mjs\""
}
]
}
]
}
}
17 changes: 17 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# EditorConfig — https://editorconfig.org
# Cohérence d'édition entre frontend (Angular) et backend (Node).
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.{json,yml,yaml}]
indent_size = 2
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
backend:
name: Backend (lint + test)
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: backend/package-lock.json
- run: npm ci
- run: npm run lint
- run: npm test

frontend:
name: Frontend (lint + build)
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm run lint
- run: npm run build
13 changes: 13 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"comment": "Serveurs MCP partagés du projet. Opt-in : Claude Code demande confirmation avant d'activer un serveur la première fois. Le serveur mongodb permet à Claude d'inspecter la base réelle (collections tools/checklist) au lieu de raisonner à l'aveugle. Adapter MDB_MCP_CONNECTION_STRING à votre instance locale (défaut: mongodb://127.0.0.1:27017/craftcode, cf. backend/.env).",
"mcpServers": {
"mongodb": {
"command": "npx",
"args": ["-y", "mongodb-mcp-server"],
"env": {
"MDB_MCP_CONNECTION_STRING": "mongodb://127.0.0.1:27017/craftcode",
"MDB_MCP_READ_ONLY": "true"
}
}
}
}
11 changes: 11 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Dépendances et artefacts de build
node_modules/
frontend/dist/
frontend/.angular/
backend/node_modules/

# Lockfiles
package-lock.json

# Données / env
backend/.env
14 changes: 14 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"semi": true,
"trailingComma": "es5",
"endOfLine": "lf",
"overrides": [
{
"files": "*.html",
"options": { "parser": "angular" }
}
]
}
57 changes: 57 additions & 0 deletions ADD-A-TOOL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Ajouter un outil — CraftCode

Chaque outil pédagogique (Code Review, SOLID, Design Patterns…) suit **la même
séquence**. La respecter garantit la cohérence et évite tout refactor du cœur.

Pattern type : un **hub** (`/x`, grille de cartes) + une page de **détail**
(`/x/:slug`). Source de vérité du contenu : un fichier statique dans `core/data/`.

## Séquence (frontend)

1. **Modèle** — `frontend/src/app/core/models/<x>.model.ts`
Interface `PascalCase`, champs techniques en anglais (`slug`, `icon`), contenu en
français (`titre`, `definition`…). Cf. [solid-principle.model.ts](frontend/src/app/core/models/solid-principle.model.ts).

2. **Données** — `frontend/src/app/core/data/<x>.ts`
Constante `SCREAMING_SNAKE_CASE` typée par le modèle, chaque entrée porte un `slug`
stable. Cf. [solid-principles.ts](frontend/src/app/core/data/solid-principles.ts) :
`export const SOLID_PRINCIPLES: SolidPrinciple[] = [...]`.

3. **Composant hub** — `frontend/src/app/features/<x>/<x>.component.{ts,html,scss}`
`selector: 'app-<x>'`, importe le `BreadcrumbComponent`, consomme la constante de data.

4. **Composant détail** (si applicable) — `features/<x>-detail/<x>-detail.component.*`
Résout l'entrée via le param de route (`signal` + `ActivatedRoute.paramMap`),
redirige vers le hub si le slug est inconnu. Cf. [phase-guide.component.ts](frontend/src/app/features/phase-guide/phase-guide.component.ts).

5. **Routes** — `frontend/src/app/app.routes.ts`
Deux routes lazy : le hub puis le détail paramétré.
```ts
{ path: 'x', loadComponent: () => import('./features/x/x.component').then(m => m.XComponent) },
{ path: 'x/:slug', loadComponent: () => import('./features/x-detail/x-detail.component').then(m => m.XDetailComponent) },
```
⚠️ Garder `{ path: '**', redirectTo: '' }` en **dernier**.

6. **Navigation** — `frontend/src/app/app.component.ts`
Ajouter une entrée dans `navLinks` : `{ label: 'Mon outil', link: '/x' }`.

## Séquence (backend)

7. **Seed** — `backend/seed/data.js`
Ajouter une entrée au tableau `tools` (incrémenter `order`) :
```js
{ slug: 'x', name: 'Mon outil', description: '…', icon: 'material_icon', route: '/x', available: true, order: 5 }
```
`slug` en `kebab-case` (anglais technique, français pour le contenu : `bonnes-pratiques`).

8. **Re-seed** — `cd backend && npm run seed`
Script idempotent : il vide puis réinsère. Cf. @COMMANDS.md.

> Un outil pas encore prêt : `available: false` → carte « Bientôt disponible », pas de route.

## Conventions transverses

- Nommage : voir @NAMING-CONVENTIONS.md.
- UI / tokens : voir @DESIGN-SYSTEM.md.
- Une route ≠ un endpoint API : le contenu pédagogique reste **statique côté front**,
seul le registre d'outils passe par l'API.
59 changes: 59 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Architecture — CraftCode

Boîte à outils pédagogique pour développeurs : un **registre d'outils** (Code Review,
SOLID, Design Patterns…) servi par une API, affiché par un front Angular.

## Stack

| Côté | Techno | Version |
|---|---|---|
| Frontend | Angular standalone + signals, Angular Material (M3) | 19.2 |
| Backend | Node + Express + Mongoose (CommonJS) | Express 4.21 / Mongoose 8.9 |
| Base | MongoDB | — |

## Arborescence

```
frontend/src/app/
├─ core/ # Cœur non-visuel, sans état d'UI
│ ├─ models/ # Interfaces TS (*.model.ts)
│ ├─ data/ # Contenu pédagogique statique (constantes SCREAMING_SNAKE)
│ └─ services/ # Accès API (*.service.ts)
├─ features/ # 1 page = 1 dossier (hub + détail)
│ ├─ home/ code-review/ best-practices/ phase-guide/
│ ├─ solid/ solid-detail/
│ └─ design-patterns/ design-pattern-detail/
├─ shared/ # Composants transverses (ex. breadcrumb)
├─ app.routes.ts # Table de routes lazy
└─ app.component.ts # Shell : toolbar + navLinks

backend/
├─ server.js # Bootstrap Express, /api + /health
├─ config/db.js # Connexion Mongo
├─ models/ # Schémas Mongoose (PascalCase singulier)
├─ controllers/ # Logique des endpoints (*Controller.js)
├─ routes/ # Définition des routes (*Routes.js), montées dans index.js
└─ seed/ # data.js (contenu) + seed.js (script idempotent)
```

## Flux de données

```
backend/seed/data.js ──seed──▶ MongoDB ──Mongoose──▶ GET /api/tools
(contenu des outils) │
ToolService (HttpClient) ──▶ HomeComponent (grille)
```

- Le **contenu pédagogique** (SOLID, patterns, pratiques) vit en **statique dans
`core/data/*.ts`** côté front — il n'est PAS en base.
- La **base** ne stocke que le registre d'outils (`Tool`) et la checklist (`ChecklistItem`).
- L'**état utilisateur** (cases cochées) vit dans le **localStorage**, jamais en base
(clé `craftcode.phase.<slug>.checked`).

## Règle d'or : extensibilité par addition

Le cœur est conçu pour qu'ajouter un outil **n'exige aucun refactor**. Voir les
commentaires de [app.routes.ts](frontend/src/app/app.routes.ts) et
[backend/routes/index.js](backend/routes/index.js) : on ajoute une feature + une route,
on monte le routeur, c'est tout. → procédure détaillée dans @ADD-A-TOOL.md.
16 changes: 16 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# CraftCode

Frontend Angular (standalone components + signals) dans `frontend/` · backend Node/Express + Mongoose dans `backend/`.

**IMPORTANT : avant de travailler sur le code, lire les documents ci-dessous. La source de vérité reste le code existant.**

## Documentation projet

- @ARCHITECTURE.md — carte du projet, stack, flux de données, règle d'extensibilité.
- @ADD-A-TOOL.md — procédure pas-à-pas pour ajouter un outil pédagogique (front + back).
- @STATE-AND-DATA.md — où vit chaque donnée, patterns signals / route / localStorage / API.
- @CONTENT-STYLE.md — guide éditorial : ton « atelier », typographie FR, structure du contenu.
- @DESIGN-SYSTEM.md — tokens `--cc-*`, BEM, accessibilité (aucune valeur en dur).
- @NAMING-CONVENTIONS.md — conventions de nommage (Angular, Node, Git, TS/JS).
- @GIT-CONVENTIONS.md — Conventional Commits : types, scope, branches, PR.
- @COMMANDS.md — lancer, seed, tester, pièges connus.
Loading
Loading