create-go-plate 0.0.0-stage → 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.
Files changed (43) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +103 -2
  4. package/bin/create.mjs +198 -0
  5. package/modules/ai/docs/AI.md +20 -0
  6. package/modules/ai/evals/README.md +10 -0
  7. package/modules/ci-github/.github/dependabot.yml +8 -0
  8. package/modules/ci-github/.github/workflows/ci.yml +38 -0
  9. package/modules/claude/.claude/hooks/format.mjs +37 -0
  10. package/modules/claude/.claude/hooks/lib.mjs +32 -0
  11. package/modules/claude/.claude/hooks/protect-files.mjs +69 -0
  12. package/modules/claude/.claude/hooks/session-start.mjs +50 -0
  13. package/modules/claude/.claude/settings.json +61 -0
  14. package/modules/claude/.mcp.json +12 -0
  15. package/modules/claude/CLAUDE.md +13 -0
  16. package/package.json +32 -4
  17. package/template/.claude/agents/reviewer.md +25 -0
  18. package/template/.claude/agents/test-writer.md +19 -0
  19. package/template/.claude/skills/audit-securite/SKILL.md +23 -0
  20. package/template/.claude/skills/cadrage/SKILL.md +81 -0
  21. package/template/.claude/skills/fin-de-session/SKILL.md +28 -0
  22. package/template/.claude/skills/spec/SKILL.md +36 -0
  23. package/template/.claude/skills/stress-test/SKILL.md +25 -0
  24. package/template/.editorconfig +18 -0
  25. package/template/.env.example +7 -0
  26. package/template/AGENTS.md +98 -0
  27. package/template/README.md +62 -0
  28. package/template/TASKS.md +16 -0
  29. package/template/docs/ARCHITECTURE.md +38 -0
  30. package/template/docs/DESIGN.md +37 -0
  31. package/template/docs/PRD.md +38 -0
  32. package/template/docs/RULES.md +31 -0
  33. package/template/docs/SECURITY.md +43 -0
  34. package/template/docs/SESSION-LOG.md +15 -0
  35. package/template/docs/TESTING.md +34 -0
  36. package/template/docs/decisions/0000-template.md +16 -0
  37. package/template/docs/decisions/README.md +8 -0
  38. package/template/docs/specs/README.md +12 -0
  39. package/template/docs/specs/_template/plan.md +27 -0
  40. package/template/docs/specs/_template/spec.md +23 -0
  41. package/template/docs/specs/_template/tasks.md +8 -0
  42. package/template/gitignore +30 -0
  43. package/template/src/README.md +9 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — 2026-10-18
4
+
5
+ Première version.
6
+
7
+ - Générateur `npm create go-plate` (interactif ou `--yes`), sans dépendance.
8
+ - Cadrage : `AGENTS.md` source unique, PRD, architecture, design, règles, sécurité, tests,
9
+ journal de session, ADR, specs par fonctionnalité.
10
+ - Skills : `/cadrage`, `/stress-test`, `/spec`, `/fin-de-session`, `/audit-securite`.
11
+ - Sous-agents : `reviewer`, `test-writer`.
12
+ - Module Claude Code : permissions, hooks (reprise de contexte, protection des secrets et du git
13
+ destructif, formatage automatique), `.mcp.json` (Context7, Playwright).
14
+ - Modules optionnels : CI GitHub Actions + Dependabot, fonctionnalités IA (`docs/AI.md`, `evals/`).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jeanpierregaba
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,104 @@
1
- # Temporary Holding Version
1
+ # create-go-plate
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Démarre n'importe quel projet d'application avec un cadrage prêt pour le développement avec un
4
+ agent de code (Claude Code, Codex, Cursor, Copilot, Gemini…).
5
+
6
+ ```bash
7
+ npm create go-plate@latest mon-projet
8
+ ```
9
+
10
+ Puis :
11
+
12
+ ```bash
13
+ cd mon-projet
14
+ claude
15
+ ```
16
+
17
+ et dans l'agent : `/cadrage`. L'agent te pose les questions et remplit le PRD, l'architecture,
18
+ le design, les règles, la stratégie de test, le backlog, l'outillage et le squelette du code,
19
+ avant toute ligne de fonctionnalité.
20
+
21
+ ## Ce qui est généré
22
+
23
+ ```
24
+ mon-projet/
25
+ ├── AGENTS.md instructions de l'agent (source unique, standard multi-outils)
26
+ ├── CLAUDE.md importe AGENTS.md (+ spécificités Claude Code)
27
+ ├── TASKS.md avancement
28
+ ├── README.md
29
+ ├── .claude/
30
+ │ ├── settings.json permissions (secrets, git destructif) + hooks
31
+ │ ├── hooks/ reprise de contexte, protection des fichiers, formatage auto
32
+ │ ├── skills/ /cadrage, /stress-test, /spec, /fin-de-session, /audit-securite
33
+ │ └── agents/ sous-agents reviewer et test-writer
34
+ ├── .mcp.json Context7 (doc à jour), Playwright (tests navigateur)
35
+ ├── .github/ CI (squelette) + Dependabot
36
+ ├── docs/
37
+ │ ├── PRD.md quoi et pourquoi
38
+ │ ├── ARCHITECTURE.md comment
39
+ │ ├── DESIGN.md interface et expérience
40
+ │ ├── RULES.md interdits stricts
41
+ │ ├── SECURITY.md checklist sécurité
42
+ │ ├── TESTING.md définition de « fini »
43
+ │ ├── SESSION-LOG.md historique des sessions
44
+ │ ├── decisions/ ADR (une décision = un fichier)
45
+ │ └── specs/ une spec par fonctionnalité (spec → plan → tâches)
46
+ ├── .env.example
47
+ ├── .editorconfig
48
+ ├── .gitignore
49
+ └── src/
50
+ ```
51
+
52
+ En option : `docs/AI.md` et `evals/` pour les applications qui appellent un LLM.
53
+
54
+ ## Options
55
+
56
+ ```bash
57
+ npm create go-plate@latest mon-projet -- --yes --ai
58
+ ```
59
+
60
+ | Option | Effet |
61
+ |---|---|
62
+ | `-y`, `--yes` | Choix par défaut, sans question |
63
+ | `--no-claude` | Sans CLAUDE.md, hooks, permissions ni `.mcp.json` |
64
+ | `--no-ci` | Sans GitHub Actions ni Dependabot |
65
+ | `--ai` | Ajoute le module « fonctionnalités IA » |
66
+ | `--no-git` | Sans `git init` |
67
+ | `-f`, `--force` | Autorise un dossier non vide |
68
+
69
+ Avec `npm create`, les options se placent après `--`. Avec `npx create-go-plate`, ce
70
+ n'est pas nécessaire.
71
+
72
+ ## Le cycle de travail
73
+
74
+ 1. `/cadrage` — une fois, au début.
75
+ 2. `/spec <fonctionnalité>` — pour chaque fonctionnalité : spec, plan, tâches, validés avant le code.
76
+ 3. `/fin-de-session` — à chaque arrêt. Au démarrage suivant, un hook redonne le contexte à l'agent.
77
+ 4. `/audit-securite` — avant chaque mise en production.
78
+
79
+ ## Prérequis
80
+
81
+ - Node.js ≥ 20.16 (pour le générateur et les hooks).
82
+ - Windows : les hooks passent par le shell ; installe Git for Windows (Git Bash).
83
+ Pour le serveur MCP Playwright, remplace dans `.mcp.json` `"command": "npx"` par
84
+ `"command": "cmd"` et préfixe les `args` par `"/c", "npx"`.
85
+
86
+ ## Développer ce générateur
87
+
88
+ ```bash
89
+ npm test
90
+ ```
91
+
92
+ ```bash
93
+ node bin/create.mjs /tmp/essai --yes
94
+ ```
95
+
96
+ ### Publier une version
97
+
98
+ 1. Mettre à jour `CHANGELOG.md`.
99
+ 2. `npm version patch` (ou `minor` / `major`).
100
+ 3. `npm publish` — les tests sont relancés automatiquement avant la publication.
101
+
102
+ Le contenu généré vit dans `template/` (toujours copié) et `modules/<nom>/` (copiés selon les
103
+ choix). Le fichier `template/gitignore` est renommé en `.gitignore` à la génération, car npm
104
+ ne publie pas les fichiers `.gitignore`.
package/bin/create.mjs ADDED
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+ // Générateur : copie le cadrage de vibecoding dans un nouveau dossier projet.
3
+ // Aucune dépendance : uniquement des modules natifs de Node.
4
+ import { spawnSync } from "node:child_process";
5
+ import { cpSync, existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
6
+ import path from "node:path";
7
+ import readline from "node:readline/promises";
8
+ import { fileURLToPath } from "node:url";
9
+ import { parseArgs } from "node:util";
10
+
11
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
12
+ const { version } = JSON.parse(readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf8"));
13
+
14
+ const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
15
+ const paint = (code) => (text) => (useColor ? `\x1b[${code}m${text}\x1b[0m` : text);
16
+ const bold = paint(1);
17
+ const dim = paint(2);
18
+ const green = paint(32);
19
+ const red = paint(31);
20
+ const cyan = paint(36);
21
+
22
+ const HELP = `
23
+ ${bold("create-go-plate")} ${dim(`v${version}`)}
24
+
25
+ Crée un nouveau projet avec le cadrage pour développer avec un agent de code.
26
+
27
+ ${bold("Usage")}
28
+ npm create go-plate@latest [dossier] -- [options]
29
+ npx create-go-plate [dossier] [options]
30
+
31
+ ${bold("Options")}
32
+ -y, --yes Accepte les choix par défaut sans poser de question
33
+ --no-claude Sans les fichiers propres à Claude Code (CLAUDE.md, hooks, permissions, .mcp.json)
34
+ --no-ci Sans la CI GitHub Actions ni Dependabot
35
+ --ai Avec le module « fonctionnalités IA » (docs/AI.md, evals/)
36
+ --no-git Sans initialiser de dépôt git
37
+ -f, --force Autorise un dossier cible non vide (les fichiers existants sont écrasés)
38
+ -h, --help Affiche cette aide
39
+ -v, --version Affiche la version
40
+ `;
41
+
42
+ function fail(message) {
43
+ console.error(`\n${red("✖")} ${message}\n`);
44
+ process.exit(1);
45
+ }
46
+
47
+ let args;
48
+ try {
49
+ args = parseArgs({
50
+ allowPositionals: true,
51
+ allowNegative: true,
52
+ options: {
53
+ yes: { type: "boolean", short: "y", default: false },
54
+ claude: { type: "boolean" },
55
+ ci: { type: "boolean" },
56
+ ai: { type: "boolean" },
57
+ git: { type: "boolean" },
58
+ force: { type: "boolean", short: "f", default: false },
59
+ help: { type: "boolean", short: "h", default: false },
60
+ version: { type: "boolean", short: "v", default: false },
61
+ },
62
+ });
63
+ } catch (error) {
64
+ fail(`${error.message}\nLance avec --help pour voir les options.`);
65
+ }
66
+
67
+ const { values: flags, positionals } = args;
68
+ if (flags.help) {
69
+ console.log(HELP);
70
+ process.exit(0);
71
+ }
72
+ if (flags.version) {
73
+ console.log(version);
74
+ process.exit(0);
75
+ }
76
+
77
+ const interactive = process.stdin.isTTY && process.stdout.isTTY && !flags.yes;
78
+ const rl = interactive ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null;
79
+
80
+ function cancel() {
81
+ console.log(`\n\n${dim("Annulé, aucun fichier créé.")}\n`);
82
+ process.exit(130);
83
+ }
84
+ rl?.on("SIGINT", cancel);
85
+
86
+ // Ctrl+D (fin de saisie) pendant une question : on annule proprement au lieu de planter.
87
+ async function ask(prompt) {
88
+ try {
89
+ return (await rl.question(prompt)).trim();
90
+ } catch {
91
+ cancel();
92
+ }
93
+ }
94
+
95
+ async function askText(question, fallback) {
96
+ if (!rl) return fallback;
97
+ const answer = await ask(`${cyan("?")} ${question} ${dim(`(${fallback})`)} `);
98
+ return answer || fallback;
99
+ }
100
+
101
+ async function askYesNo(question, fallback) {
102
+ if (!rl) return fallback;
103
+ const hint = fallback ? "O/n" : "o/N";
104
+ const answer = (await ask(`${cyan("?")} ${question} ${dim(`(${hint})`)} `)).toLowerCase();
105
+ if (!answer) return fallback;
106
+ return ["o", "oui", "y", "yes"].includes(answer);
107
+ }
108
+
109
+ // Une option passée en ligne de commande l'emporte ; sinon on pose la question.
110
+ const choose = (flag, question, fallback) => (flag !== undefined ? flag : askYesNo(question, fallback));
111
+
112
+ console.log(`\n${bold("create-go-plate")} ${dim(`v${version}`)}\n`);
113
+
114
+ const targetArg = positionals[0] ?? (await askText("Dossier du projet :", "mon-projet"));
115
+ const targetDir = path.resolve(process.cwd(), targetArg);
116
+ const projectName = path.basename(targetDir);
117
+
118
+ if (existsSync(targetDir) && readdirSync(targetDir).length > 0 && !flags.force) {
119
+ rl?.close();
120
+ fail(`Le dossier ${bold(targetArg)} existe et n'est pas vide. Choisis un autre nom ou ajoute --force.`);
121
+ }
122
+
123
+ const options = {
124
+ claude: await choose(flags.claude, "Utiliser Claude Code (CLAUDE.md, hooks, permissions, MCP) ?", true),
125
+ ci: await choose(flags.ci, "Ajouter la CI GitHub Actions et Dependabot ?", true),
126
+ ai: await choose(flags.ai, "L'application aura-t-elle des fonctionnalités IA (appels à un LLM) ?", false),
127
+ git: await choose(flags.git, "Initialiser un dépôt git ?", true),
128
+ };
129
+ rl?.close();
130
+
131
+ // 1. Copie de la base, puis des modules choisis.
132
+ const copy = (from) =>
133
+ cpSync(path.join(PACKAGE_ROOT, from), targetDir, { recursive: true, force: true });
134
+
135
+ copy("template");
136
+ if (options.claude) copy("modules/claude");
137
+ if (options.ci) copy("modules/ci-github");
138
+ if (options.ai) copy("modules/ai");
139
+
140
+ // 2. npm ne publie pas les fichiers .gitignore : le template le livre sous le nom "gitignore".
141
+ const shippedGitignore = path.join(targetDir, "gitignore");
142
+ if (existsSync(shippedGitignore)) renameSync(shippedGitignore, path.join(targetDir, ".gitignore"));
143
+
144
+ // 3. Ajustements selon les choix.
145
+ function edit(relativePath, transform) {
146
+ const file = path.join(targetDir, relativePath);
147
+ if (!existsSync(file)) return;
148
+ writeFileSync(file, transform(readFileSync(file, "utf8")));
149
+ }
150
+
151
+ edit("README.md", (text) => text.replaceAll("{{PROJECT_NAME}}", projectName));
152
+
153
+ if (!options.claude) {
154
+ // Sans les permissions et hooks de Claude Code, ces règles ne sont plus bloquées techniquement.
155
+ edit("docs/RULES.md", (text) => text.replaceAll(" [bloqué]", ""));
156
+ edit("README.md", (text) => text.replace(/^\| `\.mcp\.json` .*\n/m, ""));
157
+ }
158
+
159
+ if (options.ai) {
160
+ edit("AGENTS.md", (text) =>
161
+ text.replace(
162
+ /^(\| Charte visuelle.*\n)/m,
163
+ "$1| Fonctionnalités IA : prompts, évals, coûts | `docs/AI.md`, `evals/` |\n"
164
+ )
165
+ );
166
+ }
167
+
168
+ // 4. Dépôt git avec un premier commit (ignoré proprement si git n'est pas disponible).
169
+ let gitStatus = "non initialisé";
170
+ if (options.git && !existsSync(path.join(targetDir, ".git"))) {
171
+ const git = (...gitArgs) => spawnSync("git", gitArgs, { cwd: targetDir, stdio: "ignore" }).status === 0;
172
+ if (!git("init", "-q")) {
173
+ gitStatus = "échec (git est-il installé ?)";
174
+ } else if (git("add", "-A") && git("commit", "-q", "-m", "chore: initialisation depuis create-go-plate")) {
175
+ gitStatus = "initialisé avec un premier commit";
176
+ } else {
177
+ gitStatus = "initialisé (premier commit impossible : configure user.name / user.email)";
178
+ }
179
+ }
180
+
181
+ // 5. Récapitulatif.
182
+ const fromCwd = path.relative(process.cwd(), targetDir) || ".";
183
+ const relativeTarget = fromCwd.startsWith("..") ? targetDir : fromCwd;
184
+ const check = (on) => (on ? green("✔") : dim("–"));
185
+ console.log(`
186
+ ${green("✔")} Projet créé dans ${bold(relativeTarget)}
187
+
188
+ ${check(true)} Cadrage (AGENTS.md, docs/, skills, sous-agents)
189
+ ${check(options.claude)} Claude Code (CLAUDE.md, hooks, permissions, .mcp.json)
190
+ ${check(options.ci)} CI GitHub Actions + Dependabot
191
+ ${check(options.ai)} Module IA (docs/AI.md, evals/)
192
+ ${check(options.git)} Git : ${gitStatus}
193
+
194
+ ${bold("Prochaines étapes")}
195
+ ${cyan(`cd ${relativeTarget}`)}
196
+ ${cyan("claude")} ${dim("(ou ton agent de code)")}
197
+ ${cyan("/cadrage")} ${dim("l'agent te pose les questions et remplit le cadrage")}
198
+ `);
@@ -0,0 +1,20 @@
1
+ # AI — Fonctionnalités basées sur un modèle de langage
2
+
3
+ <!-- À REMPLIR si l'application appelle un LLM. Supprime ce fichier sinon. -->
4
+
5
+ ## Usages
6
+ <!-- Pour chaque fonctionnalité IA : ce qu'elle fait, entrée, sortie attendue, modèle utilisé et pourquoi. -->
7
+
8
+ ## Prompts
9
+ <!-- Où sont stockés les prompts (fichiers versionnés, pas des chaînes éparpillées dans le code),
10
+ et règle : toute modification de prompt est suivie d'une exécution des évals. -->
11
+
12
+ ## Évaluations
13
+ <!-- Comment on mesure la qualité : jeu de cas dans evals/, critères, seuil minimal. Voir evals/README.md. -->
14
+
15
+ ## Sécurité
16
+ <!-- Séparation instructions / données externes (injection de prompt), outils accessibles au modèle,
17
+ données envoyées au fournisseur (RGPD), sorties affichées sans échappement. -->
18
+
19
+ ## Coûts et limites
20
+ <!-- Coût estimé par appel et par utilisateur, limites par utilisateur, cache, comportement si le fournisseur est indisponible. -->
@@ -0,0 +1,10 @@
1
+ # evals/
2
+
3
+ Jeux de cas pour mesurer la qualité des fonctionnalités IA (voir `docs/AI.md`).
4
+
5
+ Un fichier par fonctionnalité, par exemple `resume-article.jsonl` : une ligne par cas avec
6
+ l'entrée, et soit la sortie attendue, soit les critères qu'elle doit respecter.
7
+ Inclure des cas difficiles et des tentatives d'injection de prompt.
8
+
9
+ À lancer après chaque modification de prompt ou de modèle ; le résultat doit rester au-dessus
10
+ du seuil défini dans `docs/AI.md`.
@@ -0,0 +1,8 @@
1
+ # Mises à jour automatiques des dépendances. Ajoute l'écosystème de ta stack pendant /cadrage
2
+ # (npm, pip, cargo, gomod…) : https://docs.github.com/code-security/dependabot
3
+ version: 2
4
+ updates:
5
+ - package-ecosystem: github-actions
6
+ directory: /
7
+ schedule:
8
+ interval: weekly
@@ -0,0 +1,38 @@
1
+ # Intégration continue — à compléter pendant /cadrage (étape « Outillage »).
2
+ # Tant que ce n'est pas fait, la CI affiche seulement un avertissement.
3
+ name: CI
4
+
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ pull_request:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ verify:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: CI non configurée
20
+ run: echo "::warning::La CI n'est pas encore configurée — lance /cadrage (étape Outillage)."
21
+
22
+ # --- Exemple Node.js (à adapter) ---
23
+ # - uses: actions/setup-node@v4
24
+ # with:
25
+ # node-version: 22
26
+ # cache: npm
27
+ # - run: npm ci
28
+ # - run: npm run lint
29
+ # - run: npm run typecheck
30
+ # - run: npm test
31
+ # - run: npm run build
32
+ # - run: npm audit --audit-level=high
33
+
34
+ # --- Exemple Python (à adapter) ---
35
+ # - uses: astral-sh/setup-uv@v6
36
+ # - run: uv sync --frozen
37
+ # - run: uv run ruff check .
38
+ # - run: uv run pytest
@@ -0,0 +1,37 @@
1
+ // PostToolUse : formate automatiquement chaque fichier modifié par l'agent.
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { projectDir, readStdinJson, relativeToProject } from "./lib.mjs";
6
+
7
+ // À RENSEIGNER pendant /cadrage, une fois la stack choisie. Le chemin du fichier est ajouté à la fin.
8
+ // Exemples :
9
+ // "npx prettier --write --ignore-unknown" (JS/TS, CSS, Markdown…)
10
+ // "npx @biomejs/biome format --write" (JS/TS)
11
+ // "ruff format" (Python)
12
+ // "gofmt -w" (Go)
13
+ // Laisser vide désactive le formatage.
14
+ const FORMAT_COMMAND = "";
15
+
16
+ if (!FORMAT_COMMAND) process.exit(0);
17
+
18
+ const input = readStdinJson();
19
+ const filePath = input.tool_input?.file_path;
20
+ if (!filePath) process.exit(0);
21
+
22
+ const absolute = path.resolve(projectDir, filePath);
23
+ const rel = relativeToProject(absolute);
24
+ // Uniquement les fichiers du projet, hors dépendances.
25
+ if (rel.startsWith("..") || /(^|\/)node_modules\//.test(rel) || !existsSync(absolute)) process.exit(0);
26
+
27
+ const result = spawnSync(`${FORMAT_COMMAND} ${JSON.stringify(absolute)}`, {
28
+ cwd: projectDir,
29
+ shell: true,
30
+ encoding: "utf8",
31
+ });
32
+
33
+ if (result.status !== 0) {
34
+ // Code 1 = erreur non bloquante : affichée à l'utilisateur, l'agent continue.
35
+ process.stderr.write(`Formatage échoué pour ${rel} :\n${result.stderr || result.stdout || ""}`);
36
+ process.exit(1);
37
+ }
@@ -0,0 +1,32 @@
1
+ // Utilitaires partagés par les hooks. Aucune dépendance : seulement Node.
2
+ import { readFileSync } from "node:fs";
3
+ import path from "node:path";
4
+
5
+ export const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
6
+
7
+ export function readStdinJson() {
8
+ try {
9
+ const raw = readFileSync(0, "utf8");
10
+ return raw.trim() ? JSON.parse(raw) : {};
11
+ } catch {
12
+ return {};
13
+ }
14
+ }
15
+
16
+ export function readProjectFile(relativePath) {
17
+ try {
18
+ return readFileSync(path.join(projectDir, relativePath), "utf8");
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ export function stripHtmlComments(text) {
25
+ return text.replace(/<!--[\s\S]*?-->/g, "");
26
+ }
27
+
28
+ // Chemin relatif au projet, avec des "/" quel que soit l'OS.
29
+ export function relativeToProject(filePath) {
30
+ const absolute = path.resolve(projectDir, filePath);
31
+ return path.relative(projectDir, absolute).split(path.sep).join("/");
32
+ }
@@ -0,0 +1,69 @@
1
+ // PreToolUse : bloque l'accès aux fichiers sensibles et les commandes git destructrices.
2
+ // Sortie code 2 = action refusée ; le message sur stderr est renvoyé à l'agent.
3
+ import path from "node:path";
4
+ import { readStdinJson, relativeToProject } from "./lib.mjs";
5
+
6
+ // Fichiers dont le contenu ne doit jamais être lu ni modifié par l'agent.
7
+ const SECRET_FILE = /(^|\/)\.env(\.(?!example$)[^/]+)?$|\.(pem|key|p12|pfx)$/i;
8
+ // Fichiers que l'agent peut lire mais ne doit modifier qu'avec les outils dédiés.
9
+ const GENERATED_FILE =
10
+ /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|poetry\.lock|uv\.lock|Cargo\.lock)$/;
11
+ const GIT_DIR = /(^|\/)\.git\//;
12
+
13
+ // Commandes shell qui mentionnent un fichier .env réel (pas .env.example).
14
+ const BASH_SECRET = /(^|[\s'"=<>|;&(/])\.env(\.(?!example\b)[\w.-]+)?(?![\w.-])/;
15
+ const BASH_FORCE_PUSH = /\bgit\s+push\b[^;&|]*\s(--force(?!-with-lease)|-f)(\s|$)/;
16
+ const BASH_RESET_HARD = /\bgit\s+reset\b[^;&|]*\s--hard\b/;
17
+
18
+ function deny(message) {
19
+ process.stderr.write(`${message}\n`);
20
+ process.exit(2);
21
+ }
22
+
23
+ const input = readStdinJson();
24
+ const tool = input.tool_name;
25
+ const toolInput = input.tool_input || {};
26
+
27
+ if (tool === "Bash") {
28
+ const command = String(toolInput.command || "");
29
+ if (BASH_SECRET.test(command)) {
30
+ deny(
31
+ "Bloqué par .claude/hooks/protect-files.mjs : la commande touche un fichier .env réel. " +
32
+ "Les secrets ne sont pas accessibles à l'agent ; demande à l'utilisateur de faire cette action lui-même " +
33
+ "(ou utilise .env.example pour documenter une variable)."
34
+ );
35
+ }
36
+ if (BASH_FORCE_PUSH.test(command) || BASH_RESET_HARD.test(command)) {
37
+ deny(
38
+ "Bloqué par .claude/hooks/protect-files.mjs : commande git destructrice (push --force / reset --hard). " +
39
+ "Voir docs/RULES.md. Si c'est vraiment nécessaire, l'utilisateur doit la lancer lui-même."
40
+ );
41
+ }
42
+ process.exit(0);
43
+ }
44
+
45
+ const filePath = toolInput.file_path || toolInput.notebook_path;
46
+ if (!filePath) process.exit(0);
47
+
48
+ const rel = relativeToProject(filePath);
49
+ const name = path.posix.basename(rel);
50
+
51
+ if (SECRET_FILE.test(rel)) {
52
+ deny(
53
+ `Bloqué par .claude/hooks/protect-files.mjs : ${name} peut contenir des secrets. ` +
54
+ "Documente les variables dans .env.example et demande à l'utilisateur de remplir le vrai fichier."
55
+ );
56
+ }
57
+
58
+ if (tool !== "Read" && GIT_DIR.test(`${rel}/`)) {
59
+ deny("Bloqué par .claude/hooks/protect-files.mjs : ne modifie pas le dossier .git directement, utilise les commandes git.");
60
+ }
61
+
62
+ if (tool !== "Read" && GENERATED_FILE.test(rel)) {
63
+ deny(
64
+ `Bloqué par .claude/hooks/protect-files.mjs : ${name} est généré. ` +
65
+ "Modifie-le via le gestionnaire de paquets (install / add / remove), pas à la main."
66
+ );
67
+ }
68
+
69
+ process.exit(0);
@@ -0,0 +1,50 @@
1
+ // SessionStart : redonne à l'agent le contexte de reprise (statut du cadrage, tâches en cours,
2
+ // dernière entrée du journal). Ce qui est écrit sur stdout est ajouté au contexte de la session.
3
+ import { readProjectFile, stripHtmlComments } from "./lib.mjs";
4
+
5
+ const MAX_CHARS = 3000;
6
+
7
+ function section(markdown, title) {
8
+ const lines = stripHtmlComments(markdown).split("\n");
9
+ const start = lines.findIndex((l) => l.trim().toLowerCase() === `## ${title}`.toLowerCase());
10
+ if (start === -1) return "";
11
+ const end = lines.findIndex((l, i) => i > start && /^##\s/.test(l));
12
+ return lines
13
+ .slice(start + 1, end === -1 ? undefined : end)
14
+ .join("\n")
15
+ .trim();
16
+ }
17
+
18
+ function lastLogEntry(markdown) {
19
+ const text = stripHtmlComments(markdown);
20
+ const match = text.match(/^###\s[\s\S]*?(?=^###\s|(?![\s\S]))/m);
21
+ return match ? match[0].trim() : "";
22
+ }
23
+
24
+ const out = [];
25
+
26
+ const agents = readProjectFile("AGENTS.md");
27
+ if (agents) {
28
+ const status = section(agents, "Statut du cadrage").split("\n")[0].replace(/\*/g, "").trim();
29
+ if (status) out.push(`Statut du cadrage : ${status}`);
30
+ if (/NON FAIT/.test(status)) {
31
+ out.push("Le cadrage n'est pas fait : avant de coder une fonctionnalité, propose /cadrage.");
32
+ }
33
+ }
34
+
35
+ const tasks = readProjectFile("TASKS.md");
36
+ if (tasks) {
37
+ const current = section(tasks, "En cours");
38
+ out.push(`Tâches en cours (TASKS.md) :\n${current || "(aucune)"}`);
39
+ }
40
+
41
+ const log = readProjectFile("docs/SESSION-LOG.md");
42
+ if (log) {
43
+ const entry = lastLogEntry(log);
44
+ if (entry) out.push(`Dernière session (docs/SESSION-LOG.md) :\n${entry}`);
45
+ }
46
+
47
+ if (out.length) {
48
+ const text = `Contexte de reprise du projet\n\n${out.join("\n\n")}`;
49
+ process.stdout.write(text.length > MAX_CHARS ? `${text.slice(0, MAX_CHARS)}\n…(tronqué)` : text);
50
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
+ "permissions": {
4
+ "deny": [
5
+ "Read(./.env)",
6
+ "Read(./.env.local)",
7
+ "Read(./.env.*.local)",
8
+ "Read(./.env.production)",
9
+ "Edit(./.env)",
10
+ "Edit(./.env.local)",
11
+ "Edit(./.env.*.local)",
12
+ "Edit(./.env.production)",
13
+ "Bash(git push --force *)",
14
+ "Bash(git push -f *)",
15
+ "Bash(git reset --hard *)"
16
+ ],
17
+ "ask": [
18
+ "Bash(git push *)",
19
+ "Bash(rm -rf *)",
20
+ "Bash(git clean *)",
21
+ "Bash(npm publish *)"
22
+ ]
23
+ },
24
+ "hooks": {
25
+ "SessionStart": [
26
+ {
27
+ "hooks": [
28
+ {
29
+ "type": "command",
30
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.mjs\"",
31
+ "timeout": 10
32
+ }
33
+ ]
34
+ }
35
+ ],
36
+ "PreToolUse": [
37
+ {
38
+ "matcher": "Read|Edit|Write|MultiEdit|NotebookEdit|Bash",
39
+ "hooks": [
40
+ {
41
+ "type": "command",
42
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect-files.mjs\"",
43
+ "timeout": 10
44
+ }
45
+ ]
46
+ }
47
+ ],
48
+ "PostToolUse": [
49
+ {
50
+ "matcher": "Edit|Write|MultiEdit",
51
+ "hooks": [
52
+ {
53
+ "type": "command",
54
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/format.mjs\"",
55
+ "timeout": 60
56
+ }
57
+ ]
58
+ }
59
+ ]
60
+ }
61
+ }