@patricksardinha/agentkit-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/.gitattributes +2 -0
- package/.github/workflows/release.yml +31 -0
- package/AGENT_WORKFLOW.md +55 -0
- package/CLAUDE.md +45 -0
- package/README.md +327 -0
- package/dist/cli.cjs +1079 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1056 -0
- package/dist/cli.js.map +1 -0
- package/package.json +45 -0
- package/src/cli.ts +18 -0
- package/src/commands/add.ts +166 -0
- package/src/commands/init.ts +130 -0
- package/src/commands/status.ts +57 -0
- package/src/detectors/gitDetector.ts +11 -0
- package/src/detectors/stackDetector.ts +88 -0
- package/src/generators/claudeMdGenerator.ts +42 -0
- package/src/generators/playbookGenerator.ts +76 -0
- package/src/generators/skillsGenerator.ts +56 -0
- package/src/generators/workflowGenerator.ts +62 -0
- package/src/templates/express.ts +64 -0
- package/src/templates/fastapi.ts +63 -0
- package/src/templates/nextjs.ts +63 -0
- package/src/templates/node.ts +54 -0
- package/src/templates/react.ts +61 -0
- package/src/templates/tauri.ts +65 -0
- package/src/templates/unknown.ts +45 -0
- package/src/types/agent.ts +9 -0
- package/src/types/inquirer.d.ts +36 -0
- package/src/utils/agentParser.ts +67 -0
- package/src/utils/blueprintParser.ts +28 -0
- package/src/utils/logger.ts +16 -0
- package/tests/commands/add.test.ts +130 -0
- package/tests/detectors/fixtures/express-app/package.json +9 -0
- package/tests/detectors/fixtures/fastapi-app/requirements.txt +3 -0
- package/tests/detectors/fixtures/nextjs-app/package.json +13 -0
- package/tests/detectors/fixtures/no-git/README.md +2 -0
- package/tests/detectors/fixtures/react-app/package.json +10 -0
- package/tests/detectors/fixtures/tauri-app/package.json +10 -0
- package/tests/detectors/fixtures/tauri-app/src-tauri/tauri.conf.json +12 -0
- package/tests/detectors/gitDetector.test.ts +29 -0
- package/tests/detectors/stackDetector.test.ts +50 -0
- package/tests/generators/blueprintSupport.test.ts +130 -0
- package/tests/generators/claudeMdGenerator.test.ts +86 -0
- package/tests/generators/playbookGenerator.test.ts +152 -0
- package/tests/generators/skillsGenerator.test.ts +94 -0
- package/tests/generators/workflowGenerator.test.ts +84 -0
- package/tsconfig.json +19 -0
- package/tsup.config.ts +11 -0
- package/vitest.config.ts +9 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import { writeFile as writeFile2, readFile as readFile2 } from "fs/promises";
|
|
8
|
+
import { join as join4 } from "path";
|
|
9
|
+
import inquirer from "inquirer";
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
|
|
12
|
+
// src/detectors/stackDetector.ts
|
|
13
|
+
import { readFile, access } from "fs/promises";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
async function pathExists(p) {
|
|
16
|
+
try {
|
|
17
|
+
await access(p);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function readJson(p) {
|
|
24
|
+
try {
|
|
25
|
+
const raw = await readFile(p, "utf-8");
|
|
26
|
+
return JSON.parse(raw);
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function readText(p) {
|
|
32
|
+
try {
|
|
33
|
+
return await readFile(p, "utf-8");
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function detectStack(projectPath) {
|
|
39
|
+
const info = {
|
|
40
|
+
framework: "unknown",
|
|
41
|
+
language: "unknown",
|
|
42
|
+
hasTypeScript: false,
|
|
43
|
+
extras: []
|
|
44
|
+
};
|
|
45
|
+
const packageJson = await readJson(join(projectPath, "package.json"));
|
|
46
|
+
const requirementsTxt = await readText(join(projectPath, "requirements.txt"));
|
|
47
|
+
const hasTauriDir = await pathExists(join(projectPath, "src-tauri"));
|
|
48
|
+
const hasTsConfig = await pathExists(join(projectPath, "tsconfig.json"));
|
|
49
|
+
if (packageJson !== null) {
|
|
50
|
+
const deps = packageJson.dependencies ?? {};
|
|
51
|
+
const devDeps = packageJson.devDependencies ?? {};
|
|
52
|
+
const allDeps = { ...deps, ...devDeps };
|
|
53
|
+
info.hasTypeScript = "typescript" in allDeps || hasTsConfig;
|
|
54
|
+
info.language = info.hasTypeScript ? "typescript" : "javascript";
|
|
55
|
+
if ("next" in allDeps) {
|
|
56
|
+
info.framework = "nextjs";
|
|
57
|
+
} else if (hasTauriDir || "@tauri-apps/api" in allDeps || "@tauri-apps/cli" in allDeps) {
|
|
58
|
+
info.framework = "tauri";
|
|
59
|
+
} else if ("react" in allDeps) {
|
|
60
|
+
info.framework = "react";
|
|
61
|
+
} else if ("express" in allDeps) {
|
|
62
|
+
info.framework = "express";
|
|
63
|
+
} else {
|
|
64
|
+
info.framework = "node";
|
|
65
|
+
}
|
|
66
|
+
if ("vitest" in allDeps || "jest" in allDeps) info.extras.push("testing");
|
|
67
|
+
if ("prisma" in allDeps || "@prisma/client" in allDeps) info.extras.push("prisma");
|
|
68
|
+
if ("tailwindcss" in allDeps) info.extras.push("tailwind");
|
|
69
|
+
}
|
|
70
|
+
if (requirementsTxt !== null && info.framework === "unknown") {
|
|
71
|
+
info.language = "python";
|
|
72
|
+
if (/\bfastapi\b/i.test(requirementsTxt)) {
|
|
73
|
+
info.framework = "fastapi";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (hasTauriDir && info.framework === "unknown") {
|
|
77
|
+
info.framework = "tauri";
|
|
78
|
+
}
|
|
79
|
+
return info;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/detectors/gitDetector.ts
|
|
83
|
+
import { access as access2 } from "fs/promises";
|
|
84
|
+
import { join as join2 } from "path";
|
|
85
|
+
async function isGitRepo(projectPath) {
|
|
86
|
+
try {
|
|
87
|
+
await access2(join2(projectPath, ".git"));
|
|
88
|
+
return true;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/utils/blueprintParser.ts
|
|
95
|
+
function parseBlueprint(content) {
|
|
96
|
+
const features = [];
|
|
97
|
+
const sectionRegex = /^## (.+)$/gm;
|
|
98
|
+
const sections = [];
|
|
99
|
+
let m;
|
|
100
|
+
while ((m = sectionRegex.exec(content)) !== null) {
|
|
101
|
+
sections.push({ name: m[1].trim(), start: m.index });
|
|
102
|
+
}
|
|
103
|
+
for (let i = 0; i < sections.length; i++) {
|
|
104
|
+
const section = sections[i];
|
|
105
|
+
const end = i + 1 < sections.length ? sections[i + 1].start : content.length;
|
|
106
|
+
const body = content.slice(section.start, end);
|
|
107
|
+
const items = [...body.matchAll(/^[-*]\s+(.+)$/gm)].map((r) => r[1].trim());
|
|
108
|
+
features.push({ name: section.name, items });
|
|
109
|
+
}
|
|
110
|
+
return features;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/templates/react.ts
|
|
114
|
+
function claudeMd(stack) {
|
|
115
|
+
const lang = stack.hasTypeScript ? "TypeScript" : "JavaScript";
|
|
116
|
+
const testLine = stack.extras.includes("testing") ? "- `npm test` \u2014 run tests\n" : "";
|
|
117
|
+
return `# CLAUDE.md \u2014 React Project
|
|
118
|
+
|
|
119
|
+
## Stack
|
|
120
|
+
- Framework : React (${lang})
|
|
121
|
+
- Language : ${lang}
|
|
122
|
+
- Build : Vite
|
|
123
|
+
|
|
124
|
+
## Commands
|
|
125
|
+
- \`npm run dev\` \u2014 development server
|
|
126
|
+
- \`npm run build\` \u2014 production build
|
|
127
|
+
${testLine}
|
|
128
|
+
## Structure
|
|
129
|
+
src/
|
|
130
|
+
components/ \u2190 UI components (PascalCase)
|
|
131
|
+
hooks/ \u2190 custom hooks (prefix: use*)
|
|
132
|
+
pages/ \u2190 page-level components
|
|
133
|
+
utils/ \u2190 shared helpers
|
|
134
|
+
|
|
135
|
+
## Conventions
|
|
136
|
+
1. Components in PascalCase
|
|
137
|
+
2. Hooks prefixed with \`use\`
|
|
138
|
+
3. Props interfaces named \`*Props\`
|
|
139
|
+
4. Tout output console passe par un logger centralis\xE9
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
142
|
+
function workflow(stack) {
|
|
143
|
+
const lang = stack.hasTypeScript ? "TypeScript" : "JavaScript";
|
|
144
|
+
return `# Agent Workflow \u2014 React Project
|
|
145
|
+
|
|
146
|
+
## Stack d\xE9tect\xE9e
|
|
147
|
+
Framework: React | Language: ${lang}
|
|
148
|
+
|
|
149
|
+
## Agents
|
|
150
|
+
|
|
151
|
+
### Agent 1 \xB7 Components
|
|
152
|
+
P\xE9rim\xE8tre : composants UI r\xE9utilisables
|
|
153
|
+
Produit : src/components/
|
|
154
|
+
Crit\xE8re : composants document\xE9s et test\xE9s
|
|
155
|
+
|
|
156
|
+
### Agent 2 \xB7 State & Hooks
|
|
157
|
+
P\xE9rim\xE8tre : state management, hooks personnalis\xE9s
|
|
158
|
+
Produit : src/hooks/
|
|
159
|
+
Crit\xE8re : hooks test\xE9s unitairement
|
|
160
|
+
|
|
161
|
+
### Agent 3 \xB7 Pages & Routing
|
|
162
|
+
P\xE9rim\xE8tre : assemblage des pages, react-router
|
|
163
|
+
Produit : src/pages/
|
|
164
|
+
Crit\xE8re : navigation fonctionnelle
|
|
165
|
+
|
|
166
|
+
### Agent 4 \xB7 Tests & CI
|
|
167
|
+
P\xE9rim\xE8tre : couverture de tests, configuration CI
|
|
168
|
+
Produit : tests/, .github/workflows/
|
|
169
|
+
Crit\xE8re : npm test passe
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/templates/nextjs.ts
|
|
174
|
+
function claudeMd2(stack) {
|
|
175
|
+
const hasTailwind = stack.extras.includes("tailwind");
|
|
176
|
+
const hasPrisma = stack.extras.includes("prisma");
|
|
177
|
+
return `# CLAUDE.md \u2014 Next.js Project
|
|
178
|
+
|
|
179
|
+
## Stack
|
|
180
|
+
- Framework : Next.js (TypeScript)
|
|
181
|
+
- Rendering : App Router (RSC + Client Components)
|
|
182
|
+
- Styling : ${hasTailwind ? "Tailwind CSS" : "CSS Modules"}
|
|
183
|
+
${hasPrisma ? "- Database : Prisma ORM\n" : ""}
|
|
184
|
+
## Commands
|
|
185
|
+
- \`npm run dev\` \u2014 development server (http://localhost:3000)
|
|
186
|
+
- \`npm run build\` \u2014 production build
|
|
187
|
+
- \`npm start\` \u2014 production server
|
|
188
|
+
- \`npm test\` \u2014 run tests
|
|
189
|
+
|
|
190
|
+
## Structure
|
|
191
|
+
src/
|
|
192
|
+
app/ \u2190 App Router pages and layouts
|
|
193
|
+
components/ \u2190 shared UI components
|
|
194
|
+
lib/ \u2190 server utilities, db clients
|
|
195
|
+
utils/ \u2190 shared helpers
|
|
196
|
+
|
|
197
|
+
## Conventions
|
|
198
|
+
1. Server Components by default, \`'use client'\` only when needed
|
|
199
|
+
2. API routes in \`src/app/api/\`
|
|
200
|
+
3. Environment variables via \`src/env.ts\` (validated)
|
|
201
|
+
4. Tout output console passe par un logger centralis\xE9
|
|
202
|
+
`;
|
|
203
|
+
}
|
|
204
|
+
function workflow2(stack) {
|
|
205
|
+
const hasPrisma = stack.extras.includes("prisma");
|
|
206
|
+
return `# Agent Workflow \u2014 Next.js Project
|
|
207
|
+
|
|
208
|
+
## Stack d\xE9tect\xE9e
|
|
209
|
+
Framework: Next.js | Language: TypeScript
|
|
210
|
+
|
|
211
|
+
## Agents
|
|
212
|
+
|
|
213
|
+
### Agent 1 \xB7 Data Layer
|
|
214
|
+
P\xE9rim\xE8tre : sch\xE9ma${hasPrisma ? " Prisma" : ""}, types, server actions
|
|
215
|
+
Produit : src/lib/, ${hasPrisma ? "prisma/schema.prisma" : "src/types/"}
|
|
216
|
+
Crit\xE8re : types compilent, migrations propres
|
|
217
|
+
|
|
218
|
+
### Agent 2 \xB7 UI Components
|
|
219
|
+
P\xE9rim\xE8tre : composants r\xE9utilisables (Server + Client)
|
|
220
|
+
Produit : src/components/
|
|
221
|
+
Crit\xE8re : composants rendus sans erreur
|
|
222
|
+
|
|
223
|
+
### Agent 3 \xB7 Pages & Layout
|
|
224
|
+
P\xE9rim\xE8tre : App Router, layouts, pages
|
|
225
|
+
Produit : src/app/
|
|
226
|
+
Crit\xE8re : navigation fonctionnelle, build passe
|
|
227
|
+
|
|
228
|
+
### Agent 4 \xB7 API & Tests
|
|
229
|
+
P\xE9rim\xE8tre : API routes, tests e2e/unitaires
|
|
230
|
+
Produit : src/app/api/, tests/
|
|
231
|
+
Crit\xE8re : npm test passe
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/templates/tauri.ts
|
|
236
|
+
function claudeMd3(stack) {
|
|
237
|
+
const lang = stack.hasTypeScript ? "TypeScript" : "JavaScript";
|
|
238
|
+
return `# CLAUDE.md \u2014 Tauri Project
|
|
239
|
+
|
|
240
|
+
## Stack
|
|
241
|
+
- Framework : Tauri (Rust + ${lang} frontend)
|
|
242
|
+
- Language : Rust (backend) + ${lang} (frontend)
|
|
243
|
+
- Build : Cargo + Vite
|
|
244
|
+
|
|
245
|
+
## Commands
|
|
246
|
+
- \`npm run tauri dev\` \u2014 development (hot reload)
|
|
247
|
+
- \`npm run tauri build\` \u2014 production bundle
|
|
248
|
+
- \`npm run build\` \u2014 frontend only
|
|
249
|
+
- \`npm test\` \u2014 run tests
|
|
250
|
+
|
|
251
|
+
## Structure
|
|
252
|
+
src/ \u2190 frontend (${lang})
|
|
253
|
+
components/
|
|
254
|
+
utils/
|
|
255
|
+
src-tauri/ \u2190 Rust backend
|
|
256
|
+
src/
|
|
257
|
+
main.rs \u2190 Tauri entry point
|
|
258
|
+
commands.rs \u2190 Tauri commands (IPC)
|
|
259
|
+
tauri.conf.json
|
|
260
|
+
|
|
261
|
+
## Conventions
|
|
262
|
+
1. IPC commands defined in \`src-tauri/src/commands.rs\`
|
|
263
|
+
2. Frontend invokes via \`@tauri-apps/api/tauri\`
|
|
264
|
+
3. No direct filesystem access from frontend
|
|
265
|
+
4. Tout output console passe par un logger centralis\xE9
|
|
266
|
+
`;
|
|
267
|
+
}
|
|
268
|
+
function workflow3(stack) {
|
|
269
|
+
const lang = stack.hasTypeScript ? "TypeScript" : "JavaScript";
|
|
270
|
+
return `# Agent Workflow \u2014 Tauri Project
|
|
271
|
+
|
|
272
|
+
## Stack d\xE9tect\xE9e
|
|
273
|
+
Framework: Tauri | Language: Rust + ${lang}
|
|
274
|
+
|
|
275
|
+
## Agents
|
|
276
|
+
|
|
277
|
+
### Agent 1 \xB7 Rust Commands
|
|
278
|
+
P\xE9rim\xE8tre : commandes Tauri (IPC), permissions
|
|
279
|
+
Produit : src-tauri/src/commands.rs, tauri.conf.json
|
|
280
|
+
Crit\xE8re : \`cargo build\` passe
|
|
281
|
+
|
|
282
|
+
### Agent 2 \xB7 Frontend UI
|
|
283
|
+
P\xE9rim\xE8tre : interface ${lang}, int\xE9gration IPC
|
|
284
|
+
Produit : src/components/, src/utils/
|
|
285
|
+
Crit\xE8re : appels IPC fonctionnels
|
|
286
|
+
|
|
287
|
+
### Agent 3 \xB7 Build & Packaging
|
|
288
|
+
P\xE9rim\xE8tre : configuration build, ic\xF4nes, installeurs
|
|
289
|
+
Produit : src-tauri/tauri.conf.json, icons/
|
|
290
|
+
Crit\xE8re : \`npm run tauri build\` produit un bundle
|
|
291
|
+
|
|
292
|
+
### Agent 4 \xB7 Tests
|
|
293
|
+
P\xE9rim\xE8tre : tests Rust + tests frontend
|
|
294
|
+
Produit : src-tauri/tests/, tests/
|
|
295
|
+
Crit\xE8re : \`cargo test\` + \`npm test\` passent
|
|
296
|
+
`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/templates/fastapi.ts
|
|
300
|
+
function claudeMd4(_stack) {
|
|
301
|
+
return `# CLAUDE.md \u2014 FastAPI Project
|
|
302
|
+
|
|
303
|
+
## Stack
|
|
304
|
+
- Framework : FastAPI (Python)
|
|
305
|
+
- Language : Python 3.11+
|
|
306
|
+
- Server : Uvicorn
|
|
307
|
+
- Validation: Pydantic v2
|
|
308
|
+
|
|
309
|
+
## Commands
|
|
310
|
+
- \`uvicorn main:app --reload\` \u2014 development server (http://localhost:8000)
|
|
311
|
+
- \`pytest\` \u2014 run tests
|
|
312
|
+
- \`pip install -r requirements.txt\` \u2014 install dependencies
|
|
313
|
+
|
|
314
|
+
## Structure
|
|
315
|
+
app/
|
|
316
|
+
main.py \u2190 FastAPI app entry point
|
|
317
|
+
routers/ \u2190 API route groups
|
|
318
|
+
models/ \u2190 Pydantic models
|
|
319
|
+
services/ \u2190 business logic
|
|
320
|
+
dependencies/ \u2190 FastAPI dependencies (DI)
|
|
321
|
+
tests/ \u2190 pytest tests
|
|
322
|
+
|
|
323
|
+
## Conventions
|
|
324
|
+
1. Routers grouped by domain in \`app/routers/\`
|
|
325
|
+
2. Pydantic models for all request/response bodies
|
|
326
|
+
3. Business logic in \`app/services/\`, not in routes
|
|
327
|
+
4. Async endpoints by default (\`async def\`)
|
|
328
|
+
5. Environment variables via \`python-dotenv\` + \`pydantic-settings\`
|
|
329
|
+
`;
|
|
330
|
+
}
|
|
331
|
+
function workflow4(_stack) {
|
|
332
|
+
return `# Agent Workflow \u2014 FastAPI Project
|
|
333
|
+
|
|
334
|
+
## Stack d\xE9tect\xE9e
|
|
335
|
+
Framework: FastAPI | Language: Python
|
|
336
|
+
|
|
337
|
+
## Agents
|
|
338
|
+
|
|
339
|
+
### Agent 1 \xB7 Models & Schemas
|
|
340
|
+
P\xE9rim\xE8tre : mod\xE8les Pydantic, sch\xE9mas DB (SQLAlchemy/SQLModel)
|
|
341
|
+
Produit : app/models/
|
|
342
|
+
Crit\xE8re : mod\xE8les valid\xE9s, migrations propres
|
|
343
|
+
|
|
344
|
+
### Agent 2 \xB7 Services
|
|
345
|
+
P\xE9rim\xE8tre : logique m\xE9tier, acc\xE8s base de donn\xE9es
|
|
346
|
+
Produit : app/services/
|
|
347
|
+
Crit\xE8re : services test\xE9s unitairement (pytest)
|
|
348
|
+
|
|
349
|
+
### Agent 3 \xB7 Routers & API
|
|
350
|
+
P\xE9rim\xE8tre : routes FastAPI, d\xE9pendances, auth
|
|
351
|
+
Produit : app/routers/, app/dependencies/
|
|
352
|
+
Crit\xE8re : endpoints document\xE9s (OpenAPI), tests d'int\xE9gration
|
|
353
|
+
|
|
354
|
+
### Agent 4 \xB7 Tests & CI
|
|
355
|
+
P\xE9rim\xE8tre : couverture pytest, configuration CI
|
|
356
|
+
Produit : tests/, .github/workflows/
|
|
357
|
+
Crit\xE8re : \`pytest\` passe \xE0 100%
|
|
358
|
+
`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/templates/express.ts
|
|
362
|
+
function claudeMd5(stack) {
|
|
363
|
+
const lang = stack.hasTypeScript ? "TypeScript" : "JavaScript";
|
|
364
|
+
const hasPrisma = stack.extras.includes("prisma");
|
|
365
|
+
return `# CLAUDE.md \u2014 Express Project
|
|
366
|
+
|
|
367
|
+
## Stack
|
|
368
|
+
- Framework : Express (${lang})
|
|
369
|
+
- Language : ${lang}
|
|
370
|
+
- Runtime : Node.js 20+
|
|
371
|
+
${hasPrisma ? "- Database : Prisma ORM\n" : ""}
|
|
372
|
+
## Commands
|
|
373
|
+
- \`npm run dev\` \u2014 development server (nodemon)
|
|
374
|
+
- \`npm run build\` \u2014 compile TypeScript
|
|
375
|
+
- \`npm start\` \u2014 production server
|
|
376
|
+
- \`npm test\` \u2014 run tests
|
|
377
|
+
|
|
378
|
+
## Structure
|
|
379
|
+
src/
|
|
380
|
+
routes/ \u2190 Express routers (one per domain)
|
|
381
|
+
controllers/ \u2190 request handlers
|
|
382
|
+
services/ \u2190 business logic
|
|
383
|
+
middleware/ \u2190 Express middleware
|
|
384
|
+
utils/ \u2190 shared helpers
|
|
385
|
+
|
|
386
|
+
## Conventions
|
|
387
|
+
1. Routes grouped by domain in \`src/routes/\`
|
|
388
|
+
2. Business logic in \`src/services/\`, not in controllers
|
|
389
|
+
3. Middleware for cross-cutting concerns (auth, validation)
|
|
390
|
+
4. Tout output console passe par un logger centralis\xE9
|
|
391
|
+
`;
|
|
392
|
+
}
|
|
393
|
+
function workflow5(stack) {
|
|
394
|
+
const lang = stack.hasTypeScript ? "TypeScript" : "JavaScript";
|
|
395
|
+
return `# Agent Workflow \u2014 Express Project
|
|
396
|
+
|
|
397
|
+
## Stack d\xE9tect\xE9e
|
|
398
|
+
Framework: Express | Language: ${lang}
|
|
399
|
+
|
|
400
|
+
## Agents
|
|
401
|
+
|
|
402
|
+
### Agent 1 \xB7 Data & Models
|
|
403
|
+
P\xE9rim\xE8tre : mod\xE8les de donn\xE9es, acc\xE8s DB
|
|
404
|
+
Produit : src/models/, src/services/db.ts
|
|
405
|
+
Crit\xE8re : connexion DB fonctionnelle
|
|
406
|
+
|
|
407
|
+
### Agent 2 \xB7 Services
|
|
408
|
+
P\xE9rim\xE8tre : logique m\xE9tier
|
|
409
|
+
Produit : src/services/
|
|
410
|
+
Crit\xE8re : services test\xE9s unitairement
|
|
411
|
+
|
|
412
|
+
### Agent 3 \xB7 Routes & Controllers
|
|
413
|
+
P\xE9rim\xE8tre : routes Express, validation, auth
|
|
414
|
+
Produit : src/routes/, src/controllers/, src/middleware/
|
|
415
|
+
Crit\xE8re : endpoints r\xE9pondent correctement
|
|
416
|
+
|
|
417
|
+
### Agent 4 \xB7 Tests & CI
|
|
418
|
+
P\xE9rim\xE8tre : tests d'int\xE9gration, configuration CI
|
|
419
|
+
Produit : tests/, .github/workflows/
|
|
420
|
+
Crit\xE8re : npm test passe
|
|
421
|
+
`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/templates/node.ts
|
|
425
|
+
function claudeMd6(stack) {
|
|
426
|
+
const lang = stack.hasTypeScript ? "TypeScript" : "JavaScript";
|
|
427
|
+
return `# CLAUDE.md \u2014 Node.js Project
|
|
428
|
+
|
|
429
|
+
## Stack
|
|
430
|
+
- Runtime : Node.js 20+
|
|
431
|
+
- Language : ${lang}
|
|
432
|
+
|
|
433
|
+
## Commands
|
|
434
|
+
- \`npm run dev\` \u2014 development (with watch)
|
|
435
|
+
- \`npm run build\` \u2014 compile${stack.hasTypeScript ? " TypeScript" : ""}
|
|
436
|
+
- \`npm start\` \u2014 run production build
|
|
437
|
+
- \`npm test\` \u2014 run tests
|
|
438
|
+
|
|
439
|
+
## Structure
|
|
440
|
+
src/
|
|
441
|
+
index.ts \u2190 entry point
|
|
442
|
+
lib/ \u2190 core library code
|
|
443
|
+
utils/ \u2190 shared helpers
|
|
444
|
+
|
|
445
|
+
## Conventions
|
|
446
|
+
1. Modules follow single-responsibility principle
|
|
447
|
+
2. Async/await over callbacks
|
|
448
|
+
3. Tout output console passe par un logger centralis\xE9
|
|
449
|
+
`;
|
|
450
|
+
}
|
|
451
|
+
function workflow6(stack) {
|
|
452
|
+
const lang = stack.hasTypeScript ? "TypeScript" : "JavaScript";
|
|
453
|
+
return `# Agent Workflow \u2014 Node.js Project
|
|
454
|
+
|
|
455
|
+
## Stack d\xE9tect\xE9e
|
|
456
|
+
Runtime: Node.js | Language: ${lang}
|
|
457
|
+
|
|
458
|
+
## Agents
|
|
459
|
+
|
|
460
|
+
### Agent 1 \xB7 Core Library
|
|
461
|
+
P\xE9rim\xE8tre : logique principale
|
|
462
|
+
Produit : src/lib/
|
|
463
|
+
Crit\xE8re : module fonctionne et test\xE9
|
|
464
|
+
|
|
465
|
+
### Agent 2 \xB7 CLI / API
|
|
466
|
+
P\xE9rim\xE8tre : interface utilisateur (CLI ou API)
|
|
467
|
+
Produit : src/index.ts, src/cli.ts
|
|
468
|
+
Crit\xE8re : commandes fonctionnelles
|
|
469
|
+
|
|
470
|
+
### Agent 3 \xB7 Tests & CI
|
|
471
|
+
P\xE9rim\xE8tre : couverture de tests, configuration CI
|
|
472
|
+
Produit : tests/, .github/workflows/
|
|
473
|
+
Crit\xE8re : npm test passe
|
|
474
|
+
`;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/templates/unknown.ts
|
|
478
|
+
function claudeMd7(_stack) {
|
|
479
|
+
return `# CLAUDE.md
|
|
480
|
+
|
|
481
|
+
## Stack
|
|
482
|
+
Stack non d\xE9tect\xE9e automatiquement \u2014 \xE0 remplir manuellement.
|
|
483
|
+
|
|
484
|
+
## Commands
|
|
485
|
+
- \xC0 d\xE9finir selon le projet
|
|
486
|
+
|
|
487
|
+
## Structure
|
|
488
|
+
src/ \u2190 code source
|
|
489
|
+
tests/ \u2190 tests
|
|
490
|
+
|
|
491
|
+
## Conventions
|
|
492
|
+
1. Tout output console passe par un logger centralis\xE9
|
|
493
|
+
2. \xC0 compl\xE9ter selon les conventions du projet
|
|
494
|
+
`;
|
|
495
|
+
}
|
|
496
|
+
function workflow7(_stack) {
|
|
497
|
+
return `# Agent Workflow
|
|
498
|
+
|
|
499
|
+
## Stack d\xE9tect\xE9e
|
|
500
|
+
Stack inconnue \u2014 workflow g\xE9n\xE9rique.
|
|
501
|
+
|
|
502
|
+
## Agents
|
|
503
|
+
|
|
504
|
+
### Agent 1 \xB7 Setup
|
|
505
|
+
P\xE9rim\xE8tre : configuration initiale du projet
|
|
506
|
+
Produit : structure de base
|
|
507
|
+
Crit\xE8re : projet compilable
|
|
508
|
+
|
|
509
|
+
### Agent 2 \xB7 Core
|
|
510
|
+
P\xE9rim\xE8tre : logique principale
|
|
511
|
+
Produit : src/
|
|
512
|
+
Crit\xE8re : fonctionnalit\xE9s principales op\xE9rationnelles
|
|
513
|
+
|
|
514
|
+
### Agent 3 \xB7 Tests
|
|
515
|
+
P\xE9rim\xE8tre : couverture de tests
|
|
516
|
+
Produit : tests/
|
|
517
|
+
Crit\xE8re : tests passent
|
|
518
|
+
`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/generators/claudeMdGenerator.ts
|
|
522
|
+
function generateClaudeMd(stack, blueprintContent) {
|
|
523
|
+
let base;
|
|
524
|
+
switch (stack.framework) {
|
|
525
|
+
case "react":
|
|
526
|
+
base = claudeMd(stack);
|
|
527
|
+
break;
|
|
528
|
+
case "nextjs":
|
|
529
|
+
base = claudeMd2(stack);
|
|
530
|
+
break;
|
|
531
|
+
case "tauri":
|
|
532
|
+
base = claudeMd3(stack);
|
|
533
|
+
break;
|
|
534
|
+
case "fastapi":
|
|
535
|
+
base = claudeMd4(stack);
|
|
536
|
+
break;
|
|
537
|
+
case "express":
|
|
538
|
+
base = claudeMd5(stack);
|
|
539
|
+
break;
|
|
540
|
+
case "node":
|
|
541
|
+
base = claudeMd6(stack);
|
|
542
|
+
break;
|
|
543
|
+
default:
|
|
544
|
+
base = claudeMd7(stack);
|
|
545
|
+
}
|
|
546
|
+
if (!blueprintContent) return base;
|
|
547
|
+
const features = parseBlueprint(blueprintContent);
|
|
548
|
+
if (features.length === 0) return base;
|
|
549
|
+
const featureLines = features.map((f, i) => {
|
|
550
|
+
const sub = f.items.length > 0 ? "\n" + f.items.map((it) => ` - ${it}`).join("\n") : "";
|
|
551
|
+
return `${i + 1}. **${f.name}**${sub}`;
|
|
552
|
+
}).join("\n");
|
|
553
|
+
const featureSection = `
|
|
554
|
+
## Features (Blueprint)
|
|
555
|
+
|
|
556
|
+
${featureLines}
|
|
557
|
+
`;
|
|
558
|
+
const conventionsIdx = base.indexOf("\n## Conventions");
|
|
559
|
+
if (conventionsIdx !== -1) {
|
|
560
|
+
return base.slice(0, conventionsIdx) + featureSection + base.slice(conventionsIdx);
|
|
561
|
+
}
|
|
562
|
+
return base + featureSection;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/utils/agentParser.ts
|
|
566
|
+
function toSlug(name) {
|
|
567
|
+
return name.toLowerCase().replace(/[·•&]/g, " ").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
568
|
+
}
|
|
569
|
+
function getFieldValue(lines, pattern) {
|
|
570
|
+
for (const line of lines) {
|
|
571
|
+
const m = line.match(pattern);
|
|
572
|
+
if (m) return (m[1] ?? "").trim();
|
|
573
|
+
}
|
|
574
|
+
return "";
|
|
575
|
+
}
|
|
576
|
+
function extractAgentsFromWorkflow(content) {
|
|
577
|
+
const agents = [];
|
|
578
|
+
const blocks = content.split(/(?=^### Agent \d)/m).filter((b) => /^### Agent \d/.test(b.trimStart()));
|
|
579
|
+
for (const block of blocks) {
|
|
580
|
+
const lines = block.split("\n");
|
|
581
|
+
const headerMatch = lines[0].match(/^### Agent (\d+)\s*[·•]\s*(.+)$/);
|
|
582
|
+
if (!headerMatch) continue;
|
|
583
|
+
const number = parseInt(headerMatch[1], 10);
|
|
584
|
+
const name = headerMatch[2].trim();
|
|
585
|
+
const fullName = `Agent ${number} \xB7 ${name}`;
|
|
586
|
+
const slug = toSlug(name);
|
|
587
|
+
const scope = getFieldValue(lines, /Périmètre\s*:\s*(.+)/);
|
|
588
|
+
const criterion = getFieldValue(lines, /Critère[s]?\s*:\s*(.+)/);
|
|
589
|
+
const outputs = [];
|
|
590
|
+
const produitIdx = lines.findIndex((l) => /Produit\s*:/.test(l));
|
|
591
|
+
if (produitIdx !== -1) {
|
|
592
|
+
const inlineVal = (lines[produitIdx].match(/Produit\s*:\s*(.+)/)?.[1] ?? "").trim();
|
|
593
|
+
if (inlineVal) {
|
|
594
|
+
outputs.push(inlineVal);
|
|
595
|
+
} else {
|
|
596
|
+
for (let i = produitIdx + 1; i < lines.length; i++) {
|
|
597
|
+
const line = lines[i];
|
|
598
|
+
if (/^\s+[-]/.test(line)) {
|
|
599
|
+
outputs.push(line.trim().replace(/^-\s*/, ""));
|
|
600
|
+
} else if (line.trim() !== "" && !/^\s/.test(line)) {
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
agents.push({ number, name, fullName, slug, scope, outputs, criterion });
|
|
607
|
+
}
|
|
608
|
+
return agents;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/generators/workflowGenerator.ts
|
|
612
|
+
function generateWorkflow(stack, blueprintContent) {
|
|
613
|
+
if (blueprintContent) return blueprintWorkflow(stack, blueprintContent);
|
|
614
|
+
switch (stack.framework) {
|
|
615
|
+
case "react":
|
|
616
|
+
return workflow(stack);
|
|
617
|
+
case "nextjs":
|
|
618
|
+
return workflow2(stack);
|
|
619
|
+
case "tauri":
|
|
620
|
+
return workflow3(stack);
|
|
621
|
+
case "fastapi":
|
|
622
|
+
return workflow4(stack);
|
|
623
|
+
case "express":
|
|
624
|
+
return workflow5(stack);
|
|
625
|
+
case "node":
|
|
626
|
+
return workflow6(stack);
|
|
627
|
+
default:
|
|
628
|
+
return workflow7(stack);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function blueprintWorkflow(stack, blueprintContent) {
|
|
632
|
+
const features = parseBlueprint(blueprintContent);
|
|
633
|
+
const agentBlocks = features.map((feature, i) => {
|
|
634
|
+
const n = i + 1;
|
|
635
|
+
const slug = toSlug(feature.name);
|
|
636
|
+
const outputLines = feature.items.length > 0 ? feature.items.map((item) => ` - ${item}`).join("\n") : ` - src/${slug}/`;
|
|
637
|
+
return `### Agent ${n} \xB7 ${feature.name}
|
|
638
|
+
P\xE9rim\xE8tre : Impl\xE9menter la fonctionnalit\xE9 ${feature.name.toLowerCase()}
|
|
639
|
+
Produit :
|
|
640
|
+
${outputLines}
|
|
641
|
+
Crit\xE8re : npm test (tests ${feature.name.toLowerCase()} passent)`;
|
|
642
|
+
});
|
|
643
|
+
const ciN = features.length + 1;
|
|
644
|
+
agentBlocks.push(
|
|
645
|
+
`### Agent ${ciN} \xB7 Tests & CI
|
|
646
|
+
P\xE9rim\xE8tre : Couverture de tests compl\xE8te et configuration du pipeline CI
|
|
647
|
+
Produit :
|
|
648
|
+
- tests/
|
|
649
|
+
- .github/workflows/
|
|
650
|
+
Crit\xE8re : npm test passe, pipeline CI vert`
|
|
651
|
+
);
|
|
652
|
+
return `# Agent Workflow \u2014 ${stack.framework} (Blueprint)
|
|
653
|
+
|
|
654
|
+
## Stack d\xE9tect\xE9e
|
|
655
|
+
Framework: ${stack.framework} | Language: ${stack.language}
|
|
656
|
+
|
|
657
|
+
## Agents
|
|
658
|
+
|
|
659
|
+
${agentBlocks.join("\n\n")}
|
|
660
|
+
`;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// src/generators/playbookGenerator.ts
|
|
664
|
+
function generatePlaybook({ agents, projectName }) {
|
|
665
|
+
const agentBlocks = agents.map((a) => agentBlock(a)).join("\n---\n\n");
|
|
666
|
+
return `# PLAYBOOK.md \u2014 ${projectName}
|
|
667
|
+
|
|
668
|
+
> Donne cette instruction \xE0 Claude Code : 'Lis PLAYBOOK.md et ex\xE9cute la proc\xE9dure.'
|
|
669
|
+
|
|
670
|
+
## R\xE8gles d'ex\xE9cution globales
|
|
671
|
+
|
|
672
|
+
Avant chaque agent :
|
|
673
|
+
1. Lire \`CLAUDE.md\`
|
|
674
|
+
2. Lire \`agents/agent-{N}-{slug}/skills.md\` (le fichier de l'agent courant)
|
|
675
|
+
|
|
676
|
+
Apr\xE8s chaque agent :
|
|
677
|
+
- Ex\xE9cuter le crit\xE8re de succ\xE8s
|
|
678
|
+
- Si succ\xE8s \u2192 annoncer "\u2705 Agent N termin\xE9" et passer au suivant
|
|
679
|
+
- Si \xE9chec \u2192 analyser la cause racine, corriger, r\xE9ex\xE9cuter (max 3 tentatives)
|
|
680
|
+
- Apr\xE8s 3 \xE9checs cons\xE9cutifs \u2192 pause et demander validation humaine
|
|
681
|
+
- **Ne jamais passer \xE0 l'agent suivant sans crit\xE8re valid\xE9**
|
|
682
|
+
|
|
683
|
+
## Agents
|
|
684
|
+
|
|
685
|
+
${agentBlocks}
|
|
686
|
+
|
|
687
|
+
## It\xE9rations futures
|
|
688
|
+
|
|
689
|
+
Lorsqu'un nouvel agent est ajout\xE9 via \`agentkit add --feature <description>\` :
|
|
690
|
+
1. Un nouveau bloc agent est ajout\xE9 \xE0 la fin de \`AGENT_WORKFLOW.md\`
|
|
691
|
+
2. Le dossier \`agents/agent-{N}-{slug}/\` est cr\xE9\xE9 avec \`skills.md\` et \`context.md\`
|
|
692
|
+
3. Ce \`PLAYBOOK.md\` est r\xE9g\xE9n\xE9r\xE9 automatiquement pour inclure le nouvel agent
|
|
693
|
+
4. L'ex\xE9cution reprend \xE0 ce nouvel agent uniquement \u2014 les agents pr\xE9c\xE9dents ne sont pas r\xE9ex\xE9cut\xE9s
|
|
694
|
+
|
|
695
|
+
## Validation humaine requise
|
|
696
|
+
|
|
697
|
+
Les cas suivants n\xE9cessitent une pause et une confirmation humaine avant de continuer :
|
|
698
|
+
- **3 \xE9checs cons\xE9cutifs** sur le crit\xE8re de succ\xE8s d'un agent
|
|
699
|
+
- **D\xE9pendance externe manquante** : cl\xE9 API, variable d'environnement non d\xE9finie, service tiers inaccessible
|
|
700
|
+
- **Conflit** entre le blueprint fourni (\`--blueprint\`) et la stack d\xE9tect\xE9e automatiquement
|
|
701
|
+
`;
|
|
702
|
+
}
|
|
703
|
+
function agentBlock(agent) {
|
|
704
|
+
const skillsPath = `agents/agent-${agent.number}-${agent.slug}/skills.md`;
|
|
705
|
+
const outputLines = agent.outputs.length > 0 ? agent.outputs.map((o) => `- ${o}`).join("\n") : "- (voir skills.md pour le d\xE9tail)";
|
|
706
|
+
return `### ${agent.fullName}
|
|
707
|
+
|
|
708
|
+
**P\xE9rim\xE8tre** : ${agent.scope}
|
|
709
|
+
|
|
710
|
+
**Skills** : \`${skillsPath}\`
|
|
711
|
+
|
|
712
|
+
**Fichiers produits** :
|
|
713
|
+
${outputLines}
|
|
714
|
+
|
|
715
|
+
**Crit\xE8re de succ\xE8s** :
|
|
716
|
+
\`\`\`bash
|
|
717
|
+
${agent.criterion || "npm run build && npm test"}
|
|
718
|
+
\`\`\`
|
|
719
|
+
|
|
720
|
+
**En cas d'\xE9chec** :
|
|
721
|
+
1. Analyser le message d'erreur complet
|
|
722
|
+
2. Corriger la cause racine (pas les sympt\xF4mes)
|
|
723
|
+
3. R\xE9ex\xE9cuter le crit\xE8re (max 3 tentatives)
|
|
724
|
+
4. Apr\xE8s 3 \xE9checs \u2192 demander validation humaine
|
|
725
|
+
`;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/generators/skillsGenerator.ts
|
|
729
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
730
|
+
import { join as join3 } from "path";
|
|
731
|
+
async function generateSkills(agents, outputDir) {
|
|
732
|
+
for (const agent of agents) {
|
|
733
|
+
const agentDir = join3(outputDir, "agents", `agent-${agent.number}-${agent.slug}`);
|
|
734
|
+
await mkdir(agentDir, { recursive: true });
|
|
735
|
+
await writeFile(join3(agentDir, "skills.md"), skillsMd(agent), "utf-8");
|
|
736
|
+
await writeFile(join3(agentDir, "context.md"), contextMd(agent), "utf-8");
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function skillsMd(agent) {
|
|
740
|
+
return `# Skills \u2014 ${agent.fullName}
|
|
741
|
+
|
|
742
|
+
> Ce fichier est lu par l'agent avant de commencer.
|
|
743
|
+
|
|
744
|
+
## Contexte technique
|
|
745
|
+
|
|
746
|
+
<!-- \xC0 remplir : biblioth\xE8ques, versions, d\xE9cisions d'architecture sp\xE9cifiques \xE0 cet agent -->
|
|
747
|
+
|
|
748
|
+
## Documentation de r\xE9f\xE9rence
|
|
749
|
+
|
|
750
|
+
<!-- \xC0 remplir : liens vers docs, exemples, ADRs pertinents -->
|
|
751
|
+
|
|
752
|
+
## Conventions sp\xE9cifiques
|
|
753
|
+
|
|
754
|
+
<!-- \xC0 remplir : r\xE8gles propres \xE0 cet agent (naming, patterns, structure de fichiers) -->
|
|
755
|
+
`;
|
|
756
|
+
}
|
|
757
|
+
function contextMd(agent) {
|
|
758
|
+
const outputLines = agent.outputs.length > 0 ? agent.outputs.map((o) => `- ${o}`).join("\n") : "- (voir AGENT_WORKFLOW.md)";
|
|
759
|
+
return `# Context \u2014 ${agent.fullName}
|
|
760
|
+
|
|
761
|
+
> Ce fichier fournit le contexte additionnel \xE0 l'agent avant ex\xE9cution.
|
|
762
|
+
> \xC0 compl\xE9ter avant de lancer cet agent.
|
|
763
|
+
|
|
764
|
+
## P\xE9rim\xE8tre
|
|
765
|
+
|
|
766
|
+
${agent.scope}
|
|
767
|
+
|
|
768
|
+
## Fichiers produits attendus
|
|
769
|
+
|
|
770
|
+
${outputLines}
|
|
771
|
+
|
|
772
|
+
## Crit\xE8re de succ\xE8s
|
|
773
|
+
|
|
774
|
+
\`${agent.criterion || "npm run build && npm test"}\`
|
|
775
|
+
`;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// src/utils/logger.ts
|
|
779
|
+
import chalk from "chalk";
|
|
780
|
+
var logger = {
|
|
781
|
+
info: (message) => {
|
|
782
|
+
process.stdout.write(chalk.blue("\u2139") + " " + message + "\n");
|
|
783
|
+
},
|
|
784
|
+
success: (message) => {
|
|
785
|
+
process.stdout.write(chalk.green("\u2714") + " " + message + "\n");
|
|
786
|
+
},
|
|
787
|
+
warn: (message) => {
|
|
788
|
+
process.stdout.write(chalk.yellow("\u26A0") + " " + message + "\n");
|
|
789
|
+
},
|
|
790
|
+
error: (message) => {
|
|
791
|
+
process.stderr.write(chalk.red("\u2716") + " " + message + "\n");
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
// src/commands/init.ts
|
|
796
|
+
import { basename } from "path";
|
|
797
|
+
var FRAMEWORK_LABELS = {
|
|
798
|
+
react: "React",
|
|
799
|
+
nextjs: "Next.js",
|
|
800
|
+
tauri: "Tauri",
|
|
801
|
+
fastapi: "FastAPI (Python)",
|
|
802
|
+
express: "Express",
|
|
803
|
+
node: "Node.js",
|
|
804
|
+
unknown: "Unknown (generic)"
|
|
805
|
+
};
|
|
806
|
+
async function fileExists(path) {
|
|
807
|
+
try {
|
|
808
|
+
await readFile2(path);
|
|
809
|
+
return true;
|
|
810
|
+
} catch {
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
function registerInit(program2) {
|
|
815
|
+
program2.command("init").description("G\xE9n\xE8re CLAUDE.md et AGENT_WORKFLOW.md dans le dossier courant").option("-f, --force", "\xC9crase les fichiers existants sans confirmation").option("--blueprint <path>", "Fichier blueprint .md \xE0 utiliser pour personnaliser les fichiers g\xE9n\xE9r\xE9s").action(async (options) => {
|
|
816
|
+
const cwd = process.cwd();
|
|
817
|
+
const spinner = ora("D\xE9tection de la stack\u2026").start();
|
|
818
|
+
const [stack, isGit] = await Promise.all([detectStack(cwd), isGitRepo(cwd)]);
|
|
819
|
+
spinner.stop();
|
|
820
|
+
if (!isGit) {
|
|
821
|
+
logger.warn("Ce dossier n'est pas un repo git \u2014 lancez git init si n\xE9cessaire");
|
|
822
|
+
}
|
|
823
|
+
const label = FRAMEWORK_LABELS[stack.framework];
|
|
824
|
+
logger.info(`Stack d\xE9tect\xE9e : ${label} (${stack.language})`);
|
|
825
|
+
const { confirmed } = await inquirer.prompt([
|
|
826
|
+
{
|
|
827
|
+
type: "confirm",
|
|
828
|
+
name: "confirmed",
|
|
829
|
+
message: `G\xE9n\xE9rer les fichiers pour ${label} ?`,
|
|
830
|
+
default: true
|
|
831
|
+
}
|
|
832
|
+
]);
|
|
833
|
+
if (!confirmed) {
|
|
834
|
+
logger.warn("Annul\xE9.");
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
const claudeMdPath = join4(cwd, "CLAUDE.md");
|
|
838
|
+
const workflowPath = join4(cwd, "AGENT_WORKFLOW.md");
|
|
839
|
+
const playbookPath = join4(cwd, "PLAYBOOK.md");
|
|
840
|
+
let projectName = basename(cwd);
|
|
841
|
+
try {
|
|
842
|
+
const pkg = JSON.parse(await readFile2(join4(cwd, "package.json"), "utf-8"));
|
|
843
|
+
if (pkg.name) projectName = pkg.name;
|
|
844
|
+
} catch {
|
|
845
|
+
}
|
|
846
|
+
if (!options.force) {
|
|
847
|
+
const existing = [];
|
|
848
|
+
if (await fileExists(claudeMdPath)) existing.push("CLAUDE.md");
|
|
849
|
+
if (await fileExists(workflowPath)) existing.push("AGENT_WORKFLOW.md");
|
|
850
|
+
if (await fileExists(playbookPath)) existing.push("PLAYBOOK.md");
|
|
851
|
+
if (existing.length > 0) {
|
|
852
|
+
const { overwrite } = await inquirer.prompt([
|
|
853
|
+
{
|
|
854
|
+
type: "confirm",
|
|
855
|
+
name: "overwrite",
|
|
856
|
+
message: `${existing.join(" et ")} existe d\xE9j\xE0. \xC9craser ?`,
|
|
857
|
+
default: false
|
|
858
|
+
}
|
|
859
|
+
]);
|
|
860
|
+
if (!overwrite) {
|
|
861
|
+
logger.warn("Annul\xE9.");
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
let blueprintContent;
|
|
867
|
+
if (options.blueprint) {
|
|
868
|
+
try {
|
|
869
|
+
blueprintContent = await readFile2(options.blueprint, "utf-8");
|
|
870
|
+
} catch {
|
|
871
|
+
logger.error(`Blueprint introuvable : ${options.blueprint}`);
|
|
872
|
+
process.exit(1);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
const genSpinner = ora("G\xE9n\xE9ration des fichiers\u2026").start();
|
|
876
|
+
const claudeMdContent = generateClaudeMd(stack, blueprintContent);
|
|
877
|
+
const workflowContent = generateWorkflow(stack, blueprintContent);
|
|
878
|
+
const agents = extractAgentsFromWorkflow(workflowContent);
|
|
879
|
+
const playbookContent = generatePlaybook({ agents, projectName });
|
|
880
|
+
await writeFile2(claudeMdPath, claudeMdContent, "utf-8");
|
|
881
|
+
await writeFile2(workflowPath, workflowContent, "utf-8");
|
|
882
|
+
await writeFile2(playbookPath, playbookContent, "utf-8");
|
|
883
|
+
await generateSkills(agents, cwd);
|
|
884
|
+
genSpinner.succeed("Fichiers g\xE9n\xE9r\xE9s");
|
|
885
|
+
logger.success("CLAUDE.md \u2192 cr\xE9\xE9");
|
|
886
|
+
logger.success("AGENT_WORKFLOW.md \u2192 cr\xE9\xE9");
|
|
887
|
+
logger.success("PLAYBOOK.md \u2192 cr\xE9\xE9");
|
|
888
|
+
logger.success(`agents/ \u2192 ${agents.length} dossier(s) cr\xE9\xE9(s)`);
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/commands/add.ts
|
|
893
|
+
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
894
|
+
import { join as join5, basename as basename2 } from "path";
|
|
895
|
+
import inquirer2 from "inquirer";
|
|
896
|
+
function featureToAgentName(description) {
|
|
897
|
+
const clean = description.replace(/^(add|implement|create|build|integrate|setup|configure|refactor|improve)\s+/i, "").trim();
|
|
898
|
+
return clean.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
899
|
+
}
|
|
900
|
+
async function addFeatureToProject(description, projectDir) {
|
|
901
|
+
const workflowPath = join5(projectDir, "AGENT_WORKFLOW.md");
|
|
902
|
+
const playbookPath = join5(projectDir, "PLAYBOOK.md");
|
|
903
|
+
let workflowContent = "";
|
|
904
|
+
try {
|
|
905
|
+
workflowContent = await readFile3(workflowPath, "utf-8");
|
|
906
|
+
} catch {
|
|
907
|
+
throw new Error(`AGENT_WORKFLOW.md introuvable dans ${projectDir} \u2014 lancez agentkit init`);
|
|
908
|
+
}
|
|
909
|
+
const existingAgents = extractAgentsFromWorkflow(workflowContent);
|
|
910
|
+
const nextNumber = existingAgents.length + 1;
|
|
911
|
+
const name = featureToAgentName(description);
|
|
912
|
+
const slug = toSlug(name);
|
|
913
|
+
const fullName = `Agent ${nextNumber} \xB7 ${name}`;
|
|
914
|
+
const newAgent = {
|
|
915
|
+
number: nextNumber,
|
|
916
|
+
name,
|
|
917
|
+
fullName,
|
|
918
|
+
slug,
|
|
919
|
+
scope: description,
|
|
920
|
+
outputs: [`agents/agent-${nextNumber}-${slug}/`],
|
|
921
|
+
criterion: "npm run build && npm test"
|
|
922
|
+
};
|
|
923
|
+
const agentBlock2 = `
|
|
924
|
+
### ${fullName}
|
|
925
|
+
P\xE9rim\xE8tre : ${description}
|
|
926
|
+
Produit :
|
|
927
|
+
- agents/agent-${nextNumber}-${slug}/
|
|
928
|
+
Crit\xE8re : npm run build && npm test
|
|
929
|
+
`;
|
|
930
|
+
await writeFile3(workflowPath, workflowContent + agentBlock2, "utf-8");
|
|
931
|
+
await generateSkills([newAgent], projectDir);
|
|
932
|
+
let projectName = basename2(projectDir);
|
|
933
|
+
try {
|
|
934
|
+
const pkg = JSON.parse(
|
|
935
|
+
await readFile3(join5(projectDir, "package.json"), "utf-8")
|
|
936
|
+
);
|
|
937
|
+
if (pkg.name) projectName = pkg.name;
|
|
938
|
+
} catch {
|
|
939
|
+
}
|
|
940
|
+
const allAgents = [...existingAgents, newAgent];
|
|
941
|
+
const playbookContent = generatePlaybook({ agents: allAgents, projectName });
|
|
942
|
+
await writeFile3(playbookPath, playbookContent, "utf-8");
|
|
943
|
+
return {
|
|
944
|
+
agent: newAgent,
|
|
945
|
+
agentDirPath: join5(projectDir, "agents", `agent-${nextNumber}-${slug}`)
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
function registerAdd(program2) {
|
|
949
|
+
const addCmd = program2.command("add").description("Ajoute des ressources au projet agentkit").option("--feature <description>", "Ajoute un agent depuis une description de feature et r\xE9g\xE9n\xE8re PLAYBOOK.md").action(async (options) => {
|
|
950
|
+
if (options.feature) {
|
|
951
|
+
try {
|
|
952
|
+
const result = await addFeatureToProject(options.feature, process.cwd());
|
|
953
|
+
logger.success(`Agent ajout\xE9 : ${result.agent.fullName}`);
|
|
954
|
+
logger.success(`Dossier cr\xE9\xE9 : agents/agent-${result.agent.number}-${result.agent.slug}/`);
|
|
955
|
+
logger.success("PLAYBOOK.md : r\xE9g\xE9n\xE9r\xE9");
|
|
956
|
+
} catch (err) {
|
|
957
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
958
|
+
process.exit(1);
|
|
959
|
+
}
|
|
960
|
+
} else {
|
|
961
|
+
addCmd.help();
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
addCmd.command("agent").description("Ajoute un nouvel agent dans AGENT_WORKFLOW.md (interactif)").action(async () => {
|
|
965
|
+
const cwd = process.cwd();
|
|
966
|
+
const workflowPath = join5(cwd, "AGENT_WORKFLOW.md");
|
|
967
|
+
let existing = "";
|
|
968
|
+
try {
|
|
969
|
+
existing = await readFile3(workflowPath, "utf-8");
|
|
970
|
+
} catch {
|
|
971
|
+
logger.error("AGENT_WORKFLOW.md introuvable \u2014 lancez d'abord : agentkit init");
|
|
972
|
+
process.exit(1);
|
|
973
|
+
}
|
|
974
|
+
const agentCount = (existing.match(/^### Agent \d+/gm) ?? []).length;
|
|
975
|
+
const nextNumber = agentCount + 1;
|
|
976
|
+
const answers = await inquirer2.prompt([
|
|
977
|
+
{
|
|
978
|
+
type: "input",
|
|
979
|
+
name: "name",
|
|
980
|
+
message: `Nom de l'agent (ex: "Agent ${nextNumber} \xB7 Feature X") :`,
|
|
981
|
+
default: `Agent ${nextNumber}`
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
type: "input",
|
|
985
|
+
name: "scope",
|
|
986
|
+
message: "P\xE9rim\xE8tre (une phrase) :"
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
type: "input",
|
|
990
|
+
name: "outputs",
|
|
991
|
+
message: "Fichiers produits (s\xE9par\xE9s par des virgules) :"
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
type: "input",
|
|
995
|
+
name: "criterion",
|
|
996
|
+
message: "Crit\xE8re de succ\xE8s :"
|
|
997
|
+
}
|
|
998
|
+
]);
|
|
999
|
+
const outputLines = answers.outputs.split(",").map((o) => ` - ${o.trim()}`).join("\n");
|
|
1000
|
+
const agentSection = `
|
|
1001
|
+
### ${answers.name}
|
|
1002
|
+
P\xE9rim\xE8tre : ${answers.scope}
|
|
1003
|
+
Produit :
|
|
1004
|
+
${outputLines}
|
|
1005
|
+
Crit\xE8re : ${answers.criterion}
|
|
1006
|
+
`;
|
|
1007
|
+
await writeFile3(workflowPath, existing + agentSection, "utf-8");
|
|
1008
|
+
logger.success(`Agent "${answers.name}" ajout\xE9 dans AGENT_WORKFLOW.md`);
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// src/commands/status.ts
|
|
1013
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1014
|
+
import { join as join6 } from "path";
|
|
1015
|
+
import chalk2 from "chalk";
|
|
1016
|
+
function registerStatus(program2) {
|
|
1017
|
+
program2.command("status").description("Affiche l'\xE9tat du workflow agentkit dans le dossier courant").action(async () => {
|
|
1018
|
+
const cwd = process.cwd();
|
|
1019
|
+
const [stack, isGit, claudeMd8, workflow8] = await Promise.all([
|
|
1020
|
+
detectStack(cwd),
|
|
1021
|
+
isGitRepo(cwd),
|
|
1022
|
+
readFile4(join6(cwd, "CLAUDE.md"), "utf-8").catch(() => null),
|
|
1023
|
+
readFile4(join6(cwd, "AGENT_WORKFLOW.md"), "utf-8").catch(() => null)
|
|
1024
|
+
]);
|
|
1025
|
+
process.stdout.write("\n" + chalk2.bold("AgentKit Status") + "\n");
|
|
1026
|
+
process.stdout.write("\u2500".repeat(40) + "\n");
|
|
1027
|
+
process.stdout.write(
|
|
1028
|
+
chalk2.bold("Git repo : ") + (isGit ? chalk2.green("\u2714 oui") : chalk2.red("\u2716 non")) + "\n"
|
|
1029
|
+
);
|
|
1030
|
+
process.stdout.write(
|
|
1031
|
+
chalk2.bold("Stack : ") + chalk2.cyan(stack.framework) + " (" + stack.language + ")\n"
|
|
1032
|
+
);
|
|
1033
|
+
process.stdout.write(
|
|
1034
|
+
chalk2.bold("CLAUDE.md : ") + (claudeMd8 !== null ? chalk2.green("\u2714 pr\xE9sent") : chalk2.yellow("\u2716 absent \u2014 lancez agentkit init")) + "\n"
|
|
1035
|
+
);
|
|
1036
|
+
process.stdout.write(
|
|
1037
|
+
chalk2.bold("AGENT_WORKFLOW.md : ") + (workflow8 !== null ? chalk2.green("\u2714 pr\xE9sent") : chalk2.yellow("\u2716 absent \u2014 lancez agentkit init")) + "\n"
|
|
1038
|
+
);
|
|
1039
|
+
if (workflow8 !== null) {
|
|
1040
|
+
const agentMatches = workflow8.match(/^### Agent \d+/gm) ?? [];
|
|
1041
|
+
process.stdout.write(
|
|
1042
|
+
chalk2.bold("Agents d\xE9finis : ") + chalk2.cyan(String(agentMatches.length)) + "\n"
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
process.stdout.write("\u2500".repeat(40) + "\n\n");
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/cli.ts
|
|
1050
|
+
var program = new Command();
|
|
1051
|
+
program.name("agentkit").description("Scaffolder des workflows multi-agents Claude Code").version("0.1.0");
|
|
1052
|
+
registerInit(program);
|
|
1053
|
+
registerAdd(program);
|
|
1054
|
+
registerStatus(program);
|
|
1055
|
+
program.parse();
|
|
1056
|
+
//# sourceMappingURL=cli.js.map
|