agent-proteus 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 +188 -0
- package/dist/agent-registry-IWDWNZDN.js +12 -0
- package/dist/chunk-XNEZQVEV.js +236 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +2359 -0
- package/package.json +59 -0
- package/templates/README.md +1 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,2359 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
scanExistingAgents,
|
|
4
|
+
updateAgentRegistry
|
|
5
|
+
} from "./chunk-XNEZQVEV.js";
|
|
6
|
+
|
|
7
|
+
// src/cli.ts
|
|
8
|
+
import fs6 from "fs/promises";
|
|
9
|
+
import path6 from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import ora from "ora";
|
|
14
|
+
import prompts from "prompts";
|
|
15
|
+
|
|
16
|
+
// src/detectors/stack.ts
|
|
17
|
+
import fs from "fs/promises";
|
|
18
|
+
import path from "path";
|
|
19
|
+
async function createContext(cwd) {
|
|
20
|
+
const entries = await fs.readdir(cwd, { withFileTypes: true });
|
|
21
|
+
const files = entries.filter((e) => e.isFile()).map((e) => e.name);
|
|
22
|
+
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
23
|
+
return {
|
|
24
|
+
cwd,
|
|
25
|
+
files,
|
|
26
|
+
hasFile: (name) => files.includes(name),
|
|
27
|
+
hasDir: (name) => dirs.includes(name),
|
|
28
|
+
readJson: async (name) => {
|
|
29
|
+
try {
|
|
30
|
+
const content = await fs.readFile(path.join(cwd, name), "utf-8");
|
|
31
|
+
return JSON.parse(content);
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
readFile: async (name) => {
|
|
37
|
+
try {
|
|
38
|
+
return await fs.readFile(path.join(cwd, name), "utf-8");
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function detectLanguage(ctx) {
|
|
46
|
+
if (ctx.hasFile("tsconfig.json")) {
|
|
47
|
+
const pkg2 = await ctx.readJson("package.json");
|
|
48
|
+
const version = pkg2?.devDependencies?.typescript?.replace("^", "").replace("~", "");
|
|
49
|
+
return { language: "typescript", version };
|
|
50
|
+
}
|
|
51
|
+
if (ctx.hasFile("go.mod")) {
|
|
52
|
+
const goMod = await ctx.readFile("go.mod");
|
|
53
|
+
const match = goMod?.match(/^go\s+(\d+\.\d+)/m);
|
|
54
|
+
return { language: "go", version: match?.[1] };
|
|
55
|
+
}
|
|
56
|
+
if (ctx.hasFile("pyproject.toml") || ctx.hasFile("requirements.txt") || ctx.hasFile("setup.py")) {
|
|
57
|
+
return { language: "python" };
|
|
58
|
+
}
|
|
59
|
+
if (ctx.hasFile("Cargo.toml")) {
|
|
60
|
+
return { language: "rust" };
|
|
61
|
+
}
|
|
62
|
+
if (ctx.hasFile("Gemfile")) {
|
|
63
|
+
return { language: "ruby" };
|
|
64
|
+
}
|
|
65
|
+
if (ctx.hasFile("pom.xml") || ctx.hasFile("build.gradle") || ctx.hasFile("build.gradle.kts")) {
|
|
66
|
+
return { language: "java" };
|
|
67
|
+
}
|
|
68
|
+
if (ctx.hasFile("composer.json")) {
|
|
69
|
+
return { language: "php" };
|
|
70
|
+
}
|
|
71
|
+
if (ctx.hasFile("package.json")) {
|
|
72
|
+
return { language: "javascript" };
|
|
73
|
+
}
|
|
74
|
+
return { language: "unknown" };
|
|
75
|
+
}
|
|
76
|
+
async function detectFramework(ctx, language) {
|
|
77
|
+
if (language === "typescript" || language === "javascript") {
|
|
78
|
+
const pkg2 = await ctx.readJson("package.json");
|
|
79
|
+
const deps = { ...pkg2?.dependencies, ...pkg2?.devDependencies };
|
|
80
|
+
if (deps?.next) {
|
|
81
|
+
return { framework: "nextjs", version: deps.next.replace("^", "").replace("~", "") };
|
|
82
|
+
}
|
|
83
|
+
if (deps?.react && !deps?.next) {
|
|
84
|
+
if (deps?.["@remix-run/react"]) return { framework: "react", version: deps.react };
|
|
85
|
+
return { framework: "react", version: deps.react.replace("^", "").replace("~", "") };
|
|
86
|
+
}
|
|
87
|
+
if (deps?.vue) {
|
|
88
|
+
return { framework: "vue", version: deps.vue.replace("^", "").replace("~", "") };
|
|
89
|
+
}
|
|
90
|
+
if (deps?.["@angular/core"]) {
|
|
91
|
+
return { framework: "angular", version: deps["@angular/core"].replace("^", "").replace("~", "") };
|
|
92
|
+
}
|
|
93
|
+
if (deps?.svelte) {
|
|
94
|
+
return { framework: "svelte", version: deps.svelte.replace("^", "").replace("~", "") };
|
|
95
|
+
}
|
|
96
|
+
if (deps?.["@nestjs/core"]) {
|
|
97
|
+
return { framework: "nestjs", version: deps["@nestjs/core"].replace("^", "").replace("~", "") };
|
|
98
|
+
}
|
|
99
|
+
if (deps?.express) {
|
|
100
|
+
return { framework: "express", version: deps.express.replace("^", "").replace("~", "") };
|
|
101
|
+
}
|
|
102
|
+
if (deps?.fastify) {
|
|
103
|
+
return { framework: "fastify", version: deps.fastify.replace("^", "").replace("~", "") };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (language === "go") {
|
|
107
|
+
const goMod = await ctx.readFile("go.mod");
|
|
108
|
+
if (goMod?.includes("github.com/gin-gonic/gin")) {
|
|
109
|
+
return { framework: "gin" };
|
|
110
|
+
}
|
|
111
|
+
if (goMod?.includes("github.com/labstack/echo")) {
|
|
112
|
+
return { framework: "echo" };
|
|
113
|
+
}
|
|
114
|
+
if (goMod?.includes("github.com/gofiber/fiber")) {
|
|
115
|
+
return { framework: "fiber" };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (language === "python") {
|
|
119
|
+
const requirements = await ctx.readFile("requirements.txt");
|
|
120
|
+
const pyproject = await ctx.readFile("pyproject.toml");
|
|
121
|
+
const content = (requirements || "") + (pyproject || "");
|
|
122
|
+
if (content.includes("django")) return { framework: "django" };
|
|
123
|
+
if (content.includes("fastapi")) return { framework: "fastapi" };
|
|
124
|
+
if (content.includes("flask")) return { framework: "flask" };
|
|
125
|
+
}
|
|
126
|
+
if (language === "ruby") {
|
|
127
|
+
const gemfile = await ctx.readFile("Gemfile");
|
|
128
|
+
if (gemfile?.includes("rails")) return { framework: "rails" };
|
|
129
|
+
}
|
|
130
|
+
if (language === "rust") {
|
|
131
|
+
const cargo = await ctx.readFile("Cargo.toml");
|
|
132
|
+
if (cargo?.includes("actix-web")) return { framework: "actix" };
|
|
133
|
+
if (cargo?.includes("axum")) return { framework: "axum" };
|
|
134
|
+
}
|
|
135
|
+
return { framework: "unknown" };
|
|
136
|
+
}
|
|
137
|
+
async function detectTestFramework(ctx, language) {
|
|
138
|
+
if (language === "typescript" || language === "javascript") {
|
|
139
|
+
const pkg2 = await ctx.readJson("package.json");
|
|
140
|
+
const deps = { ...pkg2?.dependencies, ...pkg2?.devDependencies };
|
|
141
|
+
if (deps?.vitest || ctx.hasFile("vitest.config.ts") || ctx.hasFile("vitest.config.js")) {
|
|
142
|
+
return "vitest";
|
|
143
|
+
}
|
|
144
|
+
if (deps?.jest || ctx.hasFile("jest.config.js") || ctx.hasFile("jest.config.ts")) {
|
|
145
|
+
return "jest";
|
|
146
|
+
}
|
|
147
|
+
if (deps?.mocha) {
|
|
148
|
+
return "mocha";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (language === "go") {
|
|
152
|
+
return "go-test";
|
|
153
|
+
}
|
|
154
|
+
if (language === "python") {
|
|
155
|
+
const content = (await ctx.readFile("requirements.txt") || "") + (await ctx.readFile("pyproject.toml") || "");
|
|
156
|
+
if (content.includes("pytest")) return "pytest";
|
|
157
|
+
}
|
|
158
|
+
if (language === "ruby") {
|
|
159
|
+
const gemfile = await ctx.readFile("Gemfile");
|
|
160
|
+
if (gemfile?.includes("rspec")) return "rspec";
|
|
161
|
+
}
|
|
162
|
+
return "unknown";
|
|
163
|
+
}
|
|
164
|
+
async function detectPackageManager(ctx, language) {
|
|
165
|
+
if (language === "typescript" || language === "javascript") {
|
|
166
|
+
if (ctx.hasFile("bun.lockb")) return "bun";
|
|
167
|
+
if (ctx.hasFile("pnpm-lock.yaml")) return "pnpm";
|
|
168
|
+
if (ctx.hasFile("yarn.lock")) return "yarn";
|
|
169
|
+
if (ctx.hasFile("package-lock.json")) return "npm";
|
|
170
|
+
return "npm";
|
|
171
|
+
}
|
|
172
|
+
if (language === "go") return "go-modules";
|
|
173
|
+
if (language === "python") {
|
|
174
|
+
if (ctx.hasFile("poetry.lock")) return "poetry";
|
|
175
|
+
return "pip";
|
|
176
|
+
}
|
|
177
|
+
if (language === "rust") return "cargo";
|
|
178
|
+
if (language === "ruby") return "bundler";
|
|
179
|
+
if (language === "java") {
|
|
180
|
+
if (ctx.hasFile("pom.xml")) return "maven";
|
|
181
|
+
return "gradle";
|
|
182
|
+
}
|
|
183
|
+
if (language === "php") return "composer";
|
|
184
|
+
return "unknown";
|
|
185
|
+
}
|
|
186
|
+
async function detectAdditionalTools(ctx, language) {
|
|
187
|
+
const tools = [];
|
|
188
|
+
let styling;
|
|
189
|
+
let database;
|
|
190
|
+
if (language === "typescript" || language === "javascript") {
|
|
191
|
+
const pkg2 = await ctx.readJson("package.json");
|
|
192
|
+
const deps = { ...pkg2?.dependencies, ...pkg2?.devDependencies };
|
|
193
|
+
if (deps?.tailwindcss || ctx.hasFile("tailwind.config.js") || ctx.hasFile("tailwind.config.ts")) {
|
|
194
|
+
styling = "Tailwind CSS";
|
|
195
|
+
} else if (deps?.["styled-components"]) {
|
|
196
|
+
styling = "styled-components";
|
|
197
|
+
} else if (deps?.["@emotion/react"]) {
|
|
198
|
+
styling = "Emotion";
|
|
199
|
+
} else if (ctx.files.some((f) => f.endsWith(".module.css") || f.endsWith(".module.scss"))) {
|
|
200
|
+
styling = "CSS Modules";
|
|
201
|
+
}
|
|
202
|
+
if (deps?.prisma || deps?.["@prisma/client"]) {
|
|
203
|
+
database = "Prisma";
|
|
204
|
+
tools.push("Prisma");
|
|
205
|
+
}
|
|
206
|
+
if (deps?.drizzle || deps?.["drizzle-orm"]) {
|
|
207
|
+
database = "Drizzle";
|
|
208
|
+
tools.push("Drizzle");
|
|
209
|
+
}
|
|
210
|
+
if (deps?.zustand) tools.push("Zustand");
|
|
211
|
+
if (deps?.["@reduxjs/toolkit"] || deps?.redux) tools.push("Redux");
|
|
212
|
+
if (deps?.jotai) tools.push("Jotai");
|
|
213
|
+
if (deps?.recoil) tools.push("Recoil");
|
|
214
|
+
if (deps?.["@tanstack/react-query"]) tools.push("React Query");
|
|
215
|
+
if (deps?.swr) tools.push("SWR");
|
|
216
|
+
if (deps?.axios) tools.push("Axios");
|
|
217
|
+
if (deps?.zod) tools.push("Zod");
|
|
218
|
+
if (deps?.yup) tools.push("Yup");
|
|
219
|
+
if (deps?.eslint || ctx.files.some((f) => f.startsWith(".eslintrc"))) tools.push("ESLint");
|
|
220
|
+
if (deps?.prettier || ctx.hasFile(".prettierrc") || ctx.hasFile("prettier.config.js")) tools.push("Prettier");
|
|
221
|
+
if (deps?.biome || ctx.hasFile("biome.json")) tools.push("Biome");
|
|
222
|
+
if (ctx.hasFile("docker-compose.yml") || ctx.hasFile("docker-compose.yaml")) tools.push("Docker");
|
|
223
|
+
if (ctx.hasFile(".github")) tools.push("GitHub Actions");
|
|
224
|
+
}
|
|
225
|
+
return { styling, database, additionalTools: tools };
|
|
226
|
+
}
|
|
227
|
+
async function detectStack(cwd) {
|
|
228
|
+
const ctx = await createContext(cwd);
|
|
229
|
+
const { language, version: languageVersion } = await detectLanguage(ctx);
|
|
230
|
+
const { framework, version: frameworkVersion } = await detectFramework(ctx, language);
|
|
231
|
+
const testFramework = await detectTestFramework(ctx, language);
|
|
232
|
+
const packageManager = await detectPackageManager(ctx, language);
|
|
233
|
+
const { styling, database, additionalTools } = await detectAdditionalTools(ctx, language);
|
|
234
|
+
return {
|
|
235
|
+
language,
|
|
236
|
+
languageVersion,
|
|
237
|
+
framework,
|
|
238
|
+
frameworkVersion,
|
|
239
|
+
testFramework,
|
|
240
|
+
packageManager,
|
|
241
|
+
styling,
|
|
242
|
+
database,
|
|
243
|
+
additionalTools
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/detectors/patterns.ts
|
|
248
|
+
import fs2 from "fs/promises";
|
|
249
|
+
import path2 from "path";
|
|
250
|
+
import fg from "fast-glob";
|
|
251
|
+
function detectNamingConvention(samples) {
|
|
252
|
+
if (samples.length === 0) return "mixed";
|
|
253
|
+
const patterns = {
|
|
254
|
+
camelCase: /^[a-z][a-zA-Z0-9]*$/,
|
|
255
|
+
PascalCase: /^[A-Z][a-zA-Z0-9]*$/,
|
|
256
|
+
snake_case: /^[a-z][a-z0-9_]*$/,
|
|
257
|
+
"kebab-case": /^[a-z][a-z0-9-]*$/,
|
|
258
|
+
SCREAMING_SNAKE_CASE: /^[A-Z][A-Z0-9_]*$/
|
|
259
|
+
};
|
|
260
|
+
const counts = {
|
|
261
|
+
camelCase: 0,
|
|
262
|
+
PascalCase: 0,
|
|
263
|
+
snake_case: 0,
|
|
264
|
+
"kebab-case": 0,
|
|
265
|
+
SCREAMING_SNAKE_CASE: 0,
|
|
266
|
+
mixed: 0
|
|
267
|
+
};
|
|
268
|
+
for (const sample of samples) {
|
|
269
|
+
const name = path2.basename(sample).replace(/\.[^.]+$/, "");
|
|
270
|
+
let matched = false;
|
|
271
|
+
for (const [convention, regex] of Object.entries(patterns)) {
|
|
272
|
+
if (regex.test(name)) {
|
|
273
|
+
counts[convention]++;
|
|
274
|
+
matched = true;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (!matched) {
|
|
279
|
+
counts.mixed++;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
let maxCount = 0;
|
|
283
|
+
let dominant = "mixed";
|
|
284
|
+
for (const [convention, count] of Object.entries(counts)) {
|
|
285
|
+
if (count > maxCount) {
|
|
286
|
+
maxCount = count;
|
|
287
|
+
dominant = convention;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (maxCount < samples.length * 0.6) {
|
|
291
|
+
return "mixed";
|
|
292
|
+
}
|
|
293
|
+
return dominant;
|
|
294
|
+
}
|
|
295
|
+
async function detectNamingPatterns(cwd, stack) {
|
|
296
|
+
const patterns = {
|
|
297
|
+
files: {},
|
|
298
|
+
code: {
|
|
299
|
+
functions: "camelCase",
|
|
300
|
+
variables: "camelCase",
|
|
301
|
+
constants: "SCREAMING_SNAKE_CASE"
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
if (stack.language === "typescript" || stack.language === "javascript") {
|
|
305
|
+
const componentFiles = await fg(["**/components/**/*.{tsx,jsx}", "**/Components/**/*.{tsx,jsx}"], {
|
|
306
|
+
cwd,
|
|
307
|
+
ignore: ["**/node_modules/**"],
|
|
308
|
+
onlyFiles: true
|
|
309
|
+
});
|
|
310
|
+
if (componentFiles.length > 0) {
|
|
311
|
+
patterns.files.components = detectNamingConvention(componentFiles);
|
|
312
|
+
patterns.code.components = "PascalCase";
|
|
313
|
+
}
|
|
314
|
+
const utilFiles = await fg(["**/utils/**/*.{ts,js}", "**/lib/**/*.{ts,js}", "**/helpers/**/*.{ts,js}"], {
|
|
315
|
+
cwd,
|
|
316
|
+
ignore: ["**/node_modules/**"],
|
|
317
|
+
onlyFiles: true
|
|
318
|
+
});
|
|
319
|
+
if (utilFiles.length > 0) {
|
|
320
|
+
patterns.files.utilities = detectNamingConvention(utilFiles);
|
|
321
|
+
}
|
|
322
|
+
const testFiles = await fg(["**/*.test.{ts,tsx,js,jsx}", "**/*.spec.{ts,tsx,js,jsx}"], {
|
|
323
|
+
cwd,
|
|
324
|
+
ignore: ["**/node_modules/**"],
|
|
325
|
+
onlyFiles: true
|
|
326
|
+
});
|
|
327
|
+
if (testFiles.length > 0) {
|
|
328
|
+
const hasSpec = testFiles.some((f) => f.includes(".spec."));
|
|
329
|
+
const hasTest = testFiles.some((f) => f.includes(".test."));
|
|
330
|
+
patterns.files.tests = hasSpec && !hasTest ? "*.spec.ts" : "*.test.ts";
|
|
331
|
+
}
|
|
332
|
+
if (stack.language === "typescript") {
|
|
333
|
+
patterns.code.types = "PascalCase";
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (stack.language === "go") {
|
|
337
|
+
patterns.code.functions = "camelCase";
|
|
338
|
+
patterns.code.variables = "camelCase";
|
|
339
|
+
patterns.code.constants = "PascalCase";
|
|
340
|
+
patterns.files.utilities = "snake_case";
|
|
341
|
+
}
|
|
342
|
+
if (stack.language === "python") {
|
|
343
|
+
patterns.code.functions = "snake_case";
|
|
344
|
+
patterns.code.variables = "snake_case";
|
|
345
|
+
patterns.code.constants = "SCREAMING_SNAKE_CASE";
|
|
346
|
+
patterns.files.utilities = "snake_case";
|
|
347
|
+
}
|
|
348
|
+
return patterns;
|
|
349
|
+
}
|
|
350
|
+
async function detectDirectoryStructure(cwd, stack) {
|
|
351
|
+
const structure = {
|
|
352
|
+
type: "unknown",
|
|
353
|
+
sourceDir: ".",
|
|
354
|
+
keyDirectories: []
|
|
355
|
+
};
|
|
356
|
+
const sourceDirs = ["src", "app", "lib", "pkg", "internal", "cmd"];
|
|
357
|
+
for (const dir of sourceDirs) {
|
|
358
|
+
try {
|
|
359
|
+
const stat = await fs2.stat(path2.join(cwd, dir));
|
|
360
|
+
if (stat.isDirectory()) {
|
|
361
|
+
structure.sourceDir = dir;
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const testDirs = ["test", "tests", "__tests__", "spec"];
|
|
368
|
+
for (const dir of testDirs) {
|
|
369
|
+
try {
|
|
370
|
+
const stat = await fs2.stat(path2.join(cwd, dir));
|
|
371
|
+
if (stat.isDirectory()) {
|
|
372
|
+
structure.testDir = dir;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
} catch {
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const dirs = await fg(["**/"], {
|
|
379
|
+
cwd: path2.join(cwd, structure.sourceDir),
|
|
380
|
+
onlyDirectories: true,
|
|
381
|
+
deep: 2,
|
|
382
|
+
ignore: ["**/node_modules/**", "**/.git/**"]
|
|
383
|
+
});
|
|
384
|
+
const hasComponents = dirs.some((d) => d.includes("components"));
|
|
385
|
+
const hasFeatures = dirs.some((d) => d.includes("features"));
|
|
386
|
+
const hasModules = dirs.some((d) => d.includes("modules"));
|
|
387
|
+
const hasServices = dirs.some((d) => d.includes("services"));
|
|
388
|
+
const hasControllers = dirs.some((d) => d.includes("controllers"));
|
|
389
|
+
const hasRepositories = dirs.some((d) => d.includes("repositories"));
|
|
390
|
+
const hasHandlers = dirs.some((d) => d.includes("handlers"));
|
|
391
|
+
if (hasFeatures || hasModules) {
|
|
392
|
+
structure.type = "feature-based";
|
|
393
|
+
} else if ((hasControllers || hasHandlers) && (hasServices || hasRepositories)) {
|
|
394
|
+
structure.type = "layer-based";
|
|
395
|
+
} else if (hasComponents && !hasServices) {
|
|
396
|
+
structure.type = "flat";
|
|
397
|
+
} else if (hasComponents && hasServices) {
|
|
398
|
+
structure.type = "hybrid";
|
|
399
|
+
}
|
|
400
|
+
const keyDirPatterns = [
|
|
401
|
+
{ pattern: "components", purpose: "UI Components" },
|
|
402
|
+
{ pattern: "features", purpose: "Feature modules" },
|
|
403
|
+
{ pattern: "services", purpose: "Business logic" },
|
|
404
|
+
{ pattern: "hooks", purpose: "Custom React hooks" },
|
|
405
|
+
{ pattern: "utils", purpose: "Utility functions" },
|
|
406
|
+
{ pattern: "lib", purpose: "Library code" },
|
|
407
|
+
{ pattern: "api", purpose: "API routes/handlers" },
|
|
408
|
+
{ pattern: "pages", purpose: "Page components (Pages Router)" },
|
|
409
|
+
{ pattern: "app", purpose: "App Router pages" },
|
|
410
|
+
{ pattern: "controllers", purpose: "Request handlers" },
|
|
411
|
+
{ pattern: "models", purpose: "Data models" },
|
|
412
|
+
{ pattern: "repositories", purpose: "Data access layer" },
|
|
413
|
+
{ pattern: "handlers", purpose: "HTTP handlers" },
|
|
414
|
+
{ pattern: "middleware", purpose: "Middleware functions" },
|
|
415
|
+
{ pattern: "types", purpose: "Type definitions" },
|
|
416
|
+
{ pattern: "config", purpose: "Configuration" },
|
|
417
|
+
{ pattern: "constants", purpose: "Constants and enums" }
|
|
418
|
+
];
|
|
419
|
+
for (const { pattern, purpose } of keyDirPatterns) {
|
|
420
|
+
const matches = dirs.filter((d) => d.includes(pattern));
|
|
421
|
+
if (matches.length > 0) {
|
|
422
|
+
structure.keyDirectories.push({
|
|
423
|
+
path: matches[0],
|
|
424
|
+
purpose
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return structure;
|
|
429
|
+
}
|
|
430
|
+
async function detectAdditionalPatterns(cwd, stack) {
|
|
431
|
+
const additional = {};
|
|
432
|
+
if (stack.language === "typescript" || stack.language === "javascript") {
|
|
433
|
+
try {
|
|
434
|
+
const tsconfig = await fs2.readFile(path2.join(cwd, "tsconfig.json"), "utf-8");
|
|
435
|
+
const config = JSON.parse(tsconfig);
|
|
436
|
+
if (config.compilerOptions?.paths) {
|
|
437
|
+
additional.imports = {
|
|
438
|
+
style: "absolute",
|
|
439
|
+
aliases: config.compilerOptions.paths
|
|
440
|
+
};
|
|
441
|
+
} else if (config.compilerOptions?.baseUrl) {
|
|
442
|
+
additional.imports = { style: "absolute" };
|
|
443
|
+
} else {
|
|
444
|
+
additional.imports = { style: "relative" };
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
additional.imports = { style: "relative" };
|
|
448
|
+
}
|
|
449
|
+
const sampleFiles = await fg(["src/**/*.{ts,tsx}", "app/**/*.{ts,tsx}"], {
|
|
450
|
+
cwd,
|
|
451
|
+
ignore: ["**/node_modules/**"],
|
|
452
|
+
onlyFiles: true
|
|
453
|
+
});
|
|
454
|
+
let defaultExports = 0;
|
|
455
|
+
let namedExports = 0;
|
|
456
|
+
for (const file of sampleFiles.slice(0, 10)) {
|
|
457
|
+
try {
|
|
458
|
+
const content = await fs2.readFile(path2.join(cwd, file), "utf-8");
|
|
459
|
+
if (content.includes("export default")) defaultExports++;
|
|
460
|
+
if (content.match(/export\s+(const|function|class|interface|type)\s+/)) namedExports++;
|
|
461
|
+
} catch {
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (defaultExports > namedExports * 2) {
|
|
465
|
+
additional.exports = { style: "default" };
|
|
466
|
+
} else if (namedExports > defaultExports * 2) {
|
|
467
|
+
additional.exports = { style: "named" };
|
|
468
|
+
} else {
|
|
469
|
+
additional.exports = { style: "mixed" };
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return additional;
|
|
473
|
+
}
|
|
474
|
+
async function detectPatterns(cwd, stack) {
|
|
475
|
+
const naming = await detectNamingPatterns(cwd, stack);
|
|
476
|
+
const structure = await detectDirectoryStructure(cwd, stack);
|
|
477
|
+
const additional = await detectAdditionalPatterns(cwd, stack);
|
|
478
|
+
return {
|
|
479
|
+
naming,
|
|
480
|
+
structure,
|
|
481
|
+
...additional
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/detectors/commands.ts
|
|
486
|
+
import fs3 from "fs/promises";
|
|
487
|
+
import path3 from "path";
|
|
488
|
+
async function detectCommands(cwd, stack) {
|
|
489
|
+
const commands = {};
|
|
490
|
+
if (stack.language === "typescript" || stack.language === "javascript") {
|
|
491
|
+
try {
|
|
492
|
+
const pkgContent = await fs3.readFile(path3.join(cwd, "package.json"), "utf-8");
|
|
493
|
+
const pkg2 = JSON.parse(pkgContent);
|
|
494
|
+
if (pkg2.scripts) {
|
|
495
|
+
const devCmd = pkg2.scripts.dev || pkg2.scripts.start || pkg2.scripts.serve;
|
|
496
|
+
if (devCmd) {
|
|
497
|
+
commands.dev = findScriptRunner(cwd, stack) + " run dev";
|
|
498
|
+
}
|
|
499
|
+
if (pkg2.scripts.build) {
|
|
500
|
+
commands.build = findScriptRunner(cwd, stack) + " run build";
|
|
501
|
+
}
|
|
502
|
+
if (pkg2.scripts.test) {
|
|
503
|
+
commands.test = findScriptRunner(cwd, stack) + " run test";
|
|
504
|
+
}
|
|
505
|
+
if (pkg2.scripts.lint) {
|
|
506
|
+
commands.lint = findScriptRunner(cwd, stack) + " run lint";
|
|
507
|
+
}
|
|
508
|
+
if (pkg2.scripts.format) {
|
|
509
|
+
commands.format = findScriptRunner(cwd, stack) + " run format";
|
|
510
|
+
}
|
|
511
|
+
if (pkg2.scripts.typecheck || pkg2.scripts["type-check"]) {
|
|
512
|
+
commands.typecheck = findScriptRunner(cwd, stack) + " run typecheck";
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
} catch {
|
|
516
|
+
commands.dev = "npm run dev";
|
|
517
|
+
commands.build = "npm run build";
|
|
518
|
+
commands.test = "npm test";
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (stack.language === "go") {
|
|
522
|
+
commands.build = "go build ./...";
|
|
523
|
+
commands.test = "go test ./...";
|
|
524
|
+
commands.lint = "golangci-lint run";
|
|
525
|
+
try {
|
|
526
|
+
const makefile = await fs3.readFile(path3.join(cwd, "Makefile"), "utf-8");
|
|
527
|
+
if (makefile.includes("run:")) commands.dev = "make run";
|
|
528
|
+
if (makefile.includes("build:")) commands.build = "make build";
|
|
529
|
+
if (makefile.includes("test:")) commands.test = "make test";
|
|
530
|
+
if (makefile.includes("lint:")) commands.lint = "make lint";
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (stack.language === "python") {
|
|
535
|
+
commands.test = "pytest";
|
|
536
|
+
commands.lint = "ruff check .";
|
|
537
|
+
commands.format = "ruff format .";
|
|
538
|
+
if (stack.framework === "django") {
|
|
539
|
+
commands.dev = "python manage.py runserver";
|
|
540
|
+
} else if (stack.framework === "fastapi") {
|
|
541
|
+
commands.dev = "uvicorn main:app --reload";
|
|
542
|
+
} else if (stack.framework === "flask") {
|
|
543
|
+
commands.dev = "flask run";
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (stack.language === "ruby") {
|
|
547
|
+
if (stack.framework === "rails") {
|
|
548
|
+
commands.dev = "rails server";
|
|
549
|
+
commands.test = "rails test";
|
|
550
|
+
commands.lint = "rubocop";
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (stack.language === "rust") {
|
|
554
|
+
commands.build = "cargo build";
|
|
555
|
+
commands.test = "cargo test";
|
|
556
|
+
commands.lint = "cargo clippy";
|
|
557
|
+
commands.dev = "cargo run";
|
|
558
|
+
}
|
|
559
|
+
return commands;
|
|
560
|
+
}
|
|
561
|
+
function findScriptRunner(cwd, stack) {
|
|
562
|
+
switch (stack.packageManager) {
|
|
563
|
+
case "pnpm":
|
|
564
|
+
return "pnpm";
|
|
565
|
+
case "yarn":
|
|
566
|
+
return "yarn";
|
|
567
|
+
case "bun":
|
|
568
|
+
return "bun";
|
|
569
|
+
default:
|
|
570
|
+
return "npm";
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/detectors/documents.ts
|
|
575
|
+
import fs4 from "fs/promises";
|
|
576
|
+
import path4 from "path";
|
|
577
|
+
import fg2 from "fast-glob";
|
|
578
|
+
function extractSection(content, headerPatterns) {
|
|
579
|
+
const lines = [];
|
|
580
|
+
let inSection = false;
|
|
581
|
+
for (const line of content.split("\n")) {
|
|
582
|
+
const isTargetHeader = headerPatterns.some((pattern) => {
|
|
583
|
+
const regex = new RegExp(`^##\\s*${pattern}\\s*$`, "i");
|
|
584
|
+
return regex.test(line.trim());
|
|
585
|
+
});
|
|
586
|
+
if (isTargetHeader) {
|
|
587
|
+
inSection = true;
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
if (inSection && /^##\s/.test(line)) {
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
if (inSection) {
|
|
594
|
+
const trimmed = line.trim();
|
|
595
|
+
if (trimmed.startsWith("- ") || trimmed.startsWith("* ")) {
|
|
596
|
+
lines.push(trimmed.slice(2).trim());
|
|
597
|
+
} else if (/^\d+\.\s/.test(trimmed)) {
|
|
598
|
+
lines.push(trimmed.replace(/^\d+\.\s*/, "").trim());
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return lines;
|
|
603
|
+
}
|
|
604
|
+
function extractCustomSections(content) {
|
|
605
|
+
const sections = {};
|
|
606
|
+
const lines = content.split("\n");
|
|
607
|
+
let currentHeader = "";
|
|
608
|
+
let currentContent = [];
|
|
609
|
+
const knownHeaders = [
|
|
610
|
+
"rules",
|
|
611
|
+
"\u30EB\u30FC\u30EB",
|
|
612
|
+
"conventions",
|
|
613
|
+
"\u898F\u7D04",
|
|
614
|
+
"must do",
|
|
615
|
+
"prefer",
|
|
616
|
+
"warnings",
|
|
617
|
+
"\u6CE8\u610F",
|
|
618
|
+
"tech stack",
|
|
619
|
+
"commands",
|
|
620
|
+
"project structure"
|
|
621
|
+
];
|
|
622
|
+
for (const line of lines) {
|
|
623
|
+
const headerMatch = line.match(/^##\s+(.+)$/);
|
|
624
|
+
if (headerMatch) {
|
|
625
|
+
if (currentHeader && !knownHeaders.some(
|
|
626
|
+
(h) => currentHeader.toLowerCase().includes(h)
|
|
627
|
+
)) {
|
|
628
|
+
sections[currentHeader] = currentContent.join("\n").trim();
|
|
629
|
+
}
|
|
630
|
+
currentHeader = headerMatch[1].trim();
|
|
631
|
+
currentContent = [];
|
|
632
|
+
} else if (currentHeader) {
|
|
633
|
+
currentContent.push(line);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (currentHeader && !knownHeaders.some(
|
|
637
|
+
(h) => currentHeader.toLowerCase().includes(h)
|
|
638
|
+
)) {
|
|
639
|
+
sections[currentHeader] = currentContent.join("\n").trim();
|
|
640
|
+
}
|
|
641
|
+
return sections;
|
|
642
|
+
}
|
|
643
|
+
async function parseClaudeMd(filePath) {
|
|
644
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
645
|
+
return {
|
|
646
|
+
path: filePath,
|
|
647
|
+
content,
|
|
648
|
+
rules: extractSection(content, ["Rules", "\u30EB\u30FC\u30EB", "Project Rules"]),
|
|
649
|
+
conventions: extractSection(content, ["Conventions", "\u898F\u7D04", "Code Conventions"]),
|
|
650
|
+
warnings: extractSection(content, ["Warnings", "\u6CE8\u610F", "\u8B66\u544A", "Cautions"]),
|
|
651
|
+
mustDo: extractSection(content, ["Must Do", "\u5FC5\u9808", "Required"]),
|
|
652
|
+
prefer: extractSection(content, ["Prefer", "\u63A8\u5968", "Recommended"]),
|
|
653
|
+
customSections: extractCustomSections(content)
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
async function parseReadme(filePath) {
|
|
657
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
658
|
+
const lines = content.split("\n");
|
|
659
|
+
let description = "";
|
|
660
|
+
let foundTitle = false;
|
|
661
|
+
for (const line of lines) {
|
|
662
|
+
if (line.startsWith("# ")) {
|
|
663
|
+
foundTitle = true;
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (foundTitle && line.trim() && !line.startsWith("!") && !line.startsWith("[")) {
|
|
667
|
+
if (!line.includes("![") && !line.includes("[![")) {
|
|
668
|
+
description = line.trim();
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const badges = [];
|
|
674
|
+
const badgeRegex = /\[!\[.+?\]\(.+?\)\]\(.+?\)/g;
|
|
675
|
+
const matches = content.match(badgeRegex);
|
|
676
|
+
if (matches) {
|
|
677
|
+
badges.push(...matches);
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
path: filePath,
|
|
681
|
+
content,
|
|
682
|
+
description,
|
|
683
|
+
badges
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
async function detectExistingAgents(cwd) {
|
|
687
|
+
const agents = [];
|
|
688
|
+
let agentDirectory;
|
|
689
|
+
let skillDirectory;
|
|
690
|
+
const agentDirs = [
|
|
691
|
+
{ path: ".claude/agents", type: "agent" },
|
|
692
|
+
{ path: ".agents", type: "agent" }
|
|
693
|
+
];
|
|
694
|
+
const skillDirs = [
|
|
695
|
+
{ path: ".claude/skills", type: "skill" },
|
|
696
|
+
{ path: ".skills", type: "skill" }
|
|
697
|
+
];
|
|
698
|
+
for (const dir of agentDirs) {
|
|
699
|
+
const fullPath = path4.join(cwd, dir.path);
|
|
700
|
+
try {
|
|
701
|
+
const stat = await fs4.stat(fullPath);
|
|
702
|
+
if (stat.isDirectory()) {
|
|
703
|
+
if (!agentDirectory) agentDirectory = dir.path;
|
|
704
|
+
const files = await fg2(["*.md"], {
|
|
705
|
+
cwd: fullPath,
|
|
706
|
+
onlyFiles: true
|
|
707
|
+
});
|
|
708
|
+
for (const file of files) {
|
|
709
|
+
if (file.startsWith("_")) continue;
|
|
710
|
+
const filePath = path4.join(fullPath, file);
|
|
711
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
712
|
+
const name = path4.basename(file, ".md");
|
|
713
|
+
agents.push({
|
|
714
|
+
path: filePath,
|
|
715
|
+
name,
|
|
716
|
+
content,
|
|
717
|
+
type: "agent"
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
} catch {
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
for (const dir of skillDirs) {
|
|
726
|
+
const fullPath = path4.join(cwd, dir.path);
|
|
727
|
+
try {
|
|
728
|
+
const stat = await fs4.stat(fullPath);
|
|
729
|
+
if (stat.isDirectory()) {
|
|
730
|
+
if (!skillDirectory) skillDirectory = dir.path;
|
|
731
|
+
const files = await fg2(["*.md"], {
|
|
732
|
+
cwd: fullPath,
|
|
733
|
+
onlyFiles: true
|
|
734
|
+
});
|
|
735
|
+
for (const file of files) {
|
|
736
|
+
if (file.startsWith("_") || file === "SKILL.md") continue;
|
|
737
|
+
const filePath = path4.join(fullPath, file);
|
|
738
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
739
|
+
const name = path4.basename(file, ".md");
|
|
740
|
+
const isSkill = content.startsWith("---");
|
|
741
|
+
agents.push({
|
|
742
|
+
path: filePath,
|
|
743
|
+
name,
|
|
744
|
+
content,
|
|
745
|
+
type: isSkill ? "skill" : "agent"
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
} catch {
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return { agents, agentDirectory, skillDirectory };
|
|
754
|
+
}
|
|
755
|
+
async function detectProjectDocuments(cwd) {
|
|
756
|
+
const result = {
|
|
757
|
+
existingAgents: []
|
|
758
|
+
};
|
|
759
|
+
const claudeMdPaths = ["CLAUDE.md", "claude.md", ".claude/CLAUDE.md"];
|
|
760
|
+
for (const claudePath of claudeMdPaths) {
|
|
761
|
+
const fullPath = path4.join(cwd, claudePath);
|
|
762
|
+
try {
|
|
763
|
+
await fs4.access(fullPath);
|
|
764
|
+
result.claudeMd = await parseClaudeMd(fullPath);
|
|
765
|
+
break;
|
|
766
|
+
} catch {
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
const readmePaths = ["README.md", "readme.md", "Readme.md"];
|
|
770
|
+
for (const readmePath of readmePaths) {
|
|
771
|
+
const fullPath = path4.join(cwd, readmePath);
|
|
772
|
+
try {
|
|
773
|
+
await fs4.access(fullPath);
|
|
774
|
+
result.readme = await parseReadme(fullPath);
|
|
775
|
+
break;
|
|
776
|
+
} catch {
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const { agents, agentDirectory, skillDirectory } = await detectExistingAgents(cwd);
|
|
780
|
+
result.existingAgents = agents;
|
|
781
|
+
result.agentDirectory = agentDirectory;
|
|
782
|
+
result.skillDirectory = skillDirectory;
|
|
783
|
+
if (result.claudeMd) {
|
|
784
|
+
result.claudeMdRawContent = result.claudeMd.content;
|
|
785
|
+
}
|
|
786
|
+
return result;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// src/claude-bridge.ts
|
|
790
|
+
import { execSync, spawnSync } from "child_process";
|
|
791
|
+
var LANGUAGE_NAMES = {
|
|
792
|
+
en: "English",
|
|
793
|
+
ja: "Japanese",
|
|
794
|
+
zh: "Chinese",
|
|
795
|
+
ko: "Korean",
|
|
796
|
+
es: "Spanish",
|
|
797
|
+
fr: "French",
|
|
798
|
+
de: "German"
|
|
799
|
+
};
|
|
800
|
+
function isClaudeAvailable() {
|
|
801
|
+
try {
|
|
802
|
+
const result = spawnSync("claude", ["--version"], {
|
|
803
|
+
encoding: "utf-8",
|
|
804
|
+
timeout: 5e3
|
|
805
|
+
});
|
|
806
|
+
return result.status === 0;
|
|
807
|
+
} catch {
|
|
808
|
+
return false;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function callClaude(prompt, options = {}) {
|
|
812
|
+
const { timeout = 12e4, verbose = false } = options;
|
|
813
|
+
if (verbose) {
|
|
814
|
+
console.log("\u{1F4E4} Sending to Claude Code...");
|
|
815
|
+
}
|
|
816
|
+
try {
|
|
817
|
+
const result = execSync(`claude -p "${escapeForShell(prompt)}"`, {
|
|
818
|
+
encoding: "utf-8",
|
|
819
|
+
timeout,
|
|
820
|
+
maxBuffer: 10 * 1024 * 1024
|
|
821
|
+
// 10MB
|
|
822
|
+
});
|
|
823
|
+
return result.trim();
|
|
824
|
+
} catch (error) {
|
|
825
|
+
if (error instanceof Error) {
|
|
826
|
+
throw new Error(`Claude Code call failed: ${error.message}`);
|
|
827
|
+
}
|
|
828
|
+
throw error;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
function suggestAgents(analysis, documents, options = {}) {
|
|
832
|
+
const prompt = buildAgentSuggestionPrompt(analysis, documents, options.lang || "en");
|
|
833
|
+
const response = callClaude(prompt, options);
|
|
834
|
+
return parseAgentSuggestions(response);
|
|
835
|
+
}
|
|
836
|
+
function generateClaudeMdContent(analysis, documents, options = {}) {
|
|
837
|
+
const prompt = buildClaudeMdPrompt(analysis, documents, options.lang || "en");
|
|
838
|
+
return callClaude(prompt, options);
|
|
839
|
+
}
|
|
840
|
+
function generateAgentContent(agentName, agentDescription, analysis, documents, options = {}) {
|
|
841
|
+
const prompt = buildAgentPrompt(agentName, agentDescription, analysis, documents, options.lang || "en");
|
|
842
|
+
return callClaude(prompt, options);
|
|
843
|
+
}
|
|
844
|
+
function buildAgentSuggestionPrompt(analysis, documents, lang) {
|
|
845
|
+
const existingRules = documents.claudeMd?.rules || [];
|
|
846
|
+
const existingAgentsList = documents.existingAgents.filter((a) => a.type === "agent");
|
|
847
|
+
const existingAgentNames = existingAgentsList.map((a) => a.name);
|
|
848
|
+
const existingAgentsStr = existingAgentNames.length > 0 ? existingAgentNames.join(", ") : "none";
|
|
849
|
+
const langName = LANGUAGE_NAMES[lang];
|
|
850
|
+
const keyDirs = analysis.patterns.structure.keyDirectories || [];
|
|
851
|
+
const existingCount = existingAgentNames.length;
|
|
852
|
+
let suggestCount;
|
|
853
|
+
let suggestNote;
|
|
854
|
+
if (existingCount === 0) {
|
|
855
|
+
suggestCount = 5;
|
|
856
|
+
suggestNote = "This project has no agents yet. Suggest 5 agents to provide good coverage.";
|
|
857
|
+
} else if (existingCount <= 2) {
|
|
858
|
+
suggestCount = 3;
|
|
859
|
+
suggestNote = `This project has ${existingCount} agent(s). Suggest up to 3 MORE agents that complement the existing ones.`;
|
|
860
|
+
} else if (existingCount <= 4) {
|
|
861
|
+
suggestCount = 2;
|
|
862
|
+
suggestNote = `This project has ${existingCount} agents. Only suggest 1-2 MORE agents if there are clear gaps.`;
|
|
863
|
+
} else {
|
|
864
|
+
suggestCount = 1;
|
|
865
|
+
suggestNote = `This project already has ${existingCount} agents. Only suggest 0-1 agents if there's a critical gap. Return empty array [] if coverage is sufficient.`;
|
|
866
|
+
}
|
|
867
|
+
return `You are a project analysis expert. Based on the following project information, suggest Claude Code agents specifically tailored for this project.
|
|
868
|
+
|
|
869
|
+
**IMPORTANT: All output text (description, focus, reason) must be in ${langName}.**
|
|
870
|
+
|
|
871
|
+
## Suggestion Guidelines
|
|
872
|
+
|
|
873
|
+
${suggestNote}
|
|
874
|
+
|
|
875
|
+
**Maximum suggestions: ${suggestCount}**
|
|
876
|
+
**If existing agents already cover the project well, return an EMPTY array: []**
|
|
877
|
+
|
|
878
|
+
## Project Information
|
|
879
|
+
|
|
880
|
+
- **Name**: ${analysis.projectName}
|
|
881
|
+
- **Description**: ${analysis.description || "Not specified"}
|
|
882
|
+
- **Language**: ${analysis.stack.language} ${analysis.stack.languageVersion || ""}
|
|
883
|
+
- **Framework**: ${analysis.stack.framework}${analysis.stack.frameworkVersion ? " " + analysis.stack.frameworkVersion : ""}
|
|
884
|
+
- **Test Framework**: ${analysis.stack.testFramework}
|
|
885
|
+
- **Package Manager**: ${analysis.stack.packageManager}
|
|
886
|
+
- **Additional Tools**: ${analysis.stack.additionalTools.join(", ") || "none"}
|
|
887
|
+
- **Project Structure**: ${analysis.patterns.structure.type}
|
|
888
|
+
- **Source Directory**: ${analysis.patterns.structure.sourceDir}
|
|
889
|
+
- **Test Directory**: ${analysis.patterns.structure.testDir || "same as source"}
|
|
890
|
+
|
|
891
|
+
## Key Directories
|
|
892
|
+
${keyDirs.length > 0 ? keyDirs.map((d) => `- \`${d.path}\`: ${d.purpose}`).join("\n") : "Not analyzed"}
|
|
893
|
+
|
|
894
|
+
## Commands
|
|
895
|
+
- Build: \`${analysis.commands.build || "N/A"}\`
|
|
896
|
+
- Test: \`${analysis.commands.test || "N/A"}\`
|
|
897
|
+
- Lint: \`${analysis.commands.lint || "N/A"}\`
|
|
898
|
+
|
|
899
|
+
## Code Patterns
|
|
900
|
+
- Import Style: ${analysis.patterns.imports?.style || "mixed"}
|
|
901
|
+
- Export Style: ${analysis.patterns.exports?.style || "mixed"}
|
|
902
|
+
- Naming: functions=${analysis.patterns.naming.code.functions}, types=${analysis.patterns.naming.code.types || "PascalCase"}
|
|
903
|
+
|
|
904
|
+
## Existing Rules/Conventions
|
|
905
|
+
${existingRules.length > 0 ? existingRules.map((r) => `- ${r}`).join("\n") : "None specified"}
|
|
906
|
+
|
|
907
|
+
## Existing Agents (NEVER duplicate these names)
|
|
908
|
+
${existingAgentsStr}
|
|
909
|
+
|
|
910
|
+
## Requirements
|
|
911
|
+
|
|
912
|
+
1. **NEVER suggest agents with same names as existing agents**
|
|
913
|
+
2. **HIGHLY PERSONALIZED**: Agents must be specific to THIS project, not generic
|
|
914
|
+
3. **Complementary**: New agents should fill gaps, not overlap with existing ones
|
|
915
|
+
4. **Practical focus**: Each agent should address a REAL need based on the project's stack
|
|
916
|
+
5. **Write all descriptions in ${langName}**
|
|
917
|
+
6. **Return empty array [] if existing agents are sufficient**
|
|
918
|
+
|
|
919
|
+
## What makes a GOOD agent suggestion:
|
|
920
|
+
- \u2705 "nextjs-app-router-optimizer" for a Next.js 14 project
|
|
921
|
+
- \u2705 "vitest-coverage-improver" for a project using Vitest
|
|
922
|
+
- \u274C "code-reviewer" (too generic)
|
|
923
|
+
- \u274C Anything that overlaps with existing agents
|
|
924
|
+
|
|
925
|
+
## Output Format (JSON)
|
|
926
|
+
|
|
927
|
+
\`\`\`json
|
|
928
|
+
[
|
|
929
|
+
{
|
|
930
|
+
"name": "specific-descriptive-name",
|
|
931
|
+
"description": "One-line description in ${langName}",
|
|
932
|
+
"focus": "The specific domain in ${langName}",
|
|
933
|
+
"reason": "Why valuable for THIS project specifically in ${langName}"
|
|
934
|
+
}
|
|
935
|
+
]
|
|
936
|
+
\`\`\`
|
|
937
|
+
|
|
938
|
+
Output ONLY the JSON. Return [] if no new agents needed.`;
|
|
939
|
+
}
|
|
940
|
+
function buildClaudeMdPrompt(analysis, documents, lang) {
|
|
941
|
+
const existingContent = documents.claudeMd?.rawContent || "";
|
|
942
|
+
const langName = LANGUAGE_NAMES[lang];
|
|
943
|
+
return `You are a project documentation expert. Based on the following project information, generate a CLAUDE.md file (project description file for Claude Code).
|
|
944
|
+
|
|
945
|
+
**IMPORTANT: Write the entire document in ${langName}.**
|
|
946
|
+
|
|
947
|
+
## Project Information
|
|
948
|
+
|
|
949
|
+
- **Name**: ${analysis.projectName}
|
|
950
|
+
- **Description**: ${analysis.description || "Unknown"}
|
|
951
|
+
- **Language**: ${analysis.stack.language} ${analysis.stack.languageVersion || ""}
|
|
952
|
+
- **Framework**: ${analysis.stack.framework} ${analysis.stack.frameworkVersion || ""}
|
|
953
|
+
- **Test Framework**: ${analysis.stack.testFramework}
|
|
954
|
+
- **Package Manager**: ${analysis.stack.packageManager}
|
|
955
|
+
- **Additional Tools**: ${analysis.stack.additionalTools.join(", ") || "none"}
|
|
956
|
+
|
|
957
|
+
## Project Structure
|
|
958
|
+
- Type: ${analysis.patterns.structure.type}
|
|
959
|
+
- Source Directory: ${analysis.patterns.structure.sourceDir}
|
|
960
|
+
- Test Directory: ${analysis.patterns.structure.testDir || "none"}
|
|
961
|
+
|
|
962
|
+
## Commands
|
|
963
|
+
- Development: ${analysis.commands.dev || "none"}
|
|
964
|
+
- Build: ${analysis.commands.build || "none"}
|
|
965
|
+
- Test: ${analysis.commands.test || "none"}
|
|
966
|
+
- Lint: ${analysis.commands.lint || "none"}
|
|
967
|
+
|
|
968
|
+
## Naming Conventions
|
|
969
|
+
- Component Files: ${analysis.patterns.naming.files.components || "unknown"}
|
|
970
|
+
- Test Files: ${analysis.patterns.naming.files.tests || "unknown"}
|
|
971
|
+
- Functions: ${analysis.patterns.naming.code.functions}
|
|
972
|
+
- Variables: ${analysis.patterns.naming.code.variables}
|
|
973
|
+
|
|
974
|
+
${existingContent ? `## Existing CLAUDE.md Content (for reference)
|
|
975
|
+
${existingContent}` : ""}
|
|
976
|
+
|
|
977
|
+
## Requirements
|
|
978
|
+
|
|
979
|
+
1. Create content that helps Claude Code understand and work effectively with this project
|
|
980
|
+
2. Include important rules, conventions, and best practices
|
|
981
|
+
3. Output in clear, readable Markdown format
|
|
982
|
+
4. If existing content exists, improve upon it while preserving valuable information
|
|
983
|
+
5. **Write the entire document in ${langName}**
|
|
984
|
+
|
|
985
|
+
Output ONLY the Markdown content.`;
|
|
986
|
+
}
|
|
987
|
+
function buildAgentPrompt(agentName, agentDescription, analysis, documents, lang) {
|
|
988
|
+
const existingRules = documents.claudeMd?.rules || [];
|
|
989
|
+
const conventions = documents.claudeMd?.conventions || [];
|
|
990
|
+
const langName = LANGUAGE_NAMES[lang];
|
|
991
|
+
const keyDirs = analysis.patterns.structure.keyDirectories || [];
|
|
992
|
+
const existingAgentNames = documents.existingAgents.map((a) => a.name);
|
|
993
|
+
const claudeMdContent = documents.claudeMd?.rawContent || "";
|
|
994
|
+
return `You are a Claude Code agent design expert. Generate a HIGHLY PERSONALIZED agent definition for this specific project.
|
|
995
|
+
|
|
996
|
+
**IMPORTANT: Write the entire agent definition in ${langName}.**
|
|
997
|
+
|
|
998
|
+
## Agent to Generate
|
|
999
|
+
- **Name**: ${agentName}
|
|
1000
|
+
- **Description**: ${agentDescription}
|
|
1001
|
+
|
|
1002
|
+
## Project Context
|
|
1003
|
+
|
|
1004
|
+
### Basic Info
|
|
1005
|
+
- **Project**: ${analysis.projectName}
|
|
1006
|
+
- **Description**: ${analysis.description || "Not specified"}
|
|
1007
|
+
- **Language**: ${analysis.stack.language} ${analysis.stack.languageVersion || ""}
|
|
1008
|
+
- **Framework**: ${analysis.stack.framework}${analysis.stack.frameworkVersion ? " " + analysis.stack.frameworkVersion : ""}
|
|
1009
|
+
- **Test Framework**: ${analysis.stack.testFramework}
|
|
1010
|
+
- **Package Manager**: ${analysis.stack.packageManager}
|
|
1011
|
+
|
|
1012
|
+
### Project Structure
|
|
1013
|
+
- **Type**: ${analysis.patterns.structure.type}
|
|
1014
|
+
- **Source**: \`${analysis.patterns.structure.sourceDir}\`
|
|
1015
|
+
- **Tests**: \`${analysis.patterns.structure.testDir || analysis.patterns.structure.sourceDir}\`
|
|
1016
|
+
|
|
1017
|
+
### Key Directories (IMPORTANT - reference these in the agent!)
|
|
1018
|
+
${keyDirs.length > 0 ? keyDirs.map((d) => `- \`${d.path}\`: ${d.purpose}`).join("\n") : "- Standard structure"}
|
|
1019
|
+
|
|
1020
|
+
### Commands
|
|
1021
|
+
- Build: \`${analysis.commands.build || "npm run build"}\`
|
|
1022
|
+
- Test: \`${analysis.commands.test || "npm test"}\`
|
|
1023
|
+
- Lint: \`${analysis.commands.lint || "npm run lint"}\`
|
|
1024
|
+
- Typecheck: \`${analysis.commands.typecheck || "npm run typecheck"}\`
|
|
1025
|
+
|
|
1026
|
+
### Code Conventions
|
|
1027
|
+
- Functions: ${analysis.patterns.naming.code.functions}
|
|
1028
|
+
- Variables: ${analysis.patterns.naming.code.variables}
|
|
1029
|
+
- Constants: ${analysis.patterns.naming.code.constants}
|
|
1030
|
+
- Types: ${analysis.patterns.naming.code.types || "PascalCase"}
|
|
1031
|
+
- Test files: ${analysis.patterns.naming.files.tests || "*.test.ts"}
|
|
1032
|
+
- Import style: ${analysis.patterns.imports?.style || "relative"}
|
|
1033
|
+
- Export style: ${analysis.patterns.exports?.style || "named"}
|
|
1034
|
+
|
|
1035
|
+
### Project Rules (from CLAUDE.md)
|
|
1036
|
+
${existingRules.length > 0 ? existingRules.map((r) => `- ${r}`).join("\n") : "No explicit rules defined"}
|
|
1037
|
+
|
|
1038
|
+
### Project Practices
|
|
1039
|
+
${conventions.length > 0 ? conventions.map((c) => `- ${c}`).join("\n") : "No explicit conventions defined"}
|
|
1040
|
+
|
|
1041
|
+
${claudeMdContent ? `### Full CLAUDE.md Content (for deep context)
|
|
1042
|
+
\`\`\`
|
|
1043
|
+
${claudeMdContent.slice(0, 2e3)}${claudeMdContent.length > 2e3 ? "\n...(truncated)" : ""}
|
|
1044
|
+
\`\`\`` : ""}
|
|
1045
|
+
|
|
1046
|
+
### Related Agents (for potential collaboration)
|
|
1047
|
+
${existingAgentNames.length > 0 ? existingAgentNames.map((n) => `- ${n}`).join("\n") : "This is the first agent"}
|
|
1048
|
+
|
|
1049
|
+
## Generation Requirements
|
|
1050
|
+
|
|
1051
|
+
1. **Be EXTREMELY specific** to this project:
|
|
1052
|
+
- Reference actual directory paths (e.g., "when working in \`${analysis.patterns.structure.sourceDir}\`...")
|
|
1053
|
+
- Mention the specific framework/versions
|
|
1054
|
+
- Use the project's actual commands
|
|
1055
|
+
|
|
1056
|
+
2. **Include project-specific code examples**:
|
|
1057
|
+
- Show examples using the project's naming conventions
|
|
1058
|
+
- Use the project's test framework syntax
|
|
1059
|
+
- Follow the import/export style
|
|
1060
|
+
|
|
1061
|
+
3. **Reference other agents** if relevant:
|
|
1062
|
+
- Mention when to hand off to other agents
|
|
1063
|
+
- Describe complementary workflows
|
|
1064
|
+
|
|
1065
|
+
4. **Constraints must be actionable**:
|
|
1066
|
+
- Include verification commands (e.g., "Run \`${analysis.commands.test}\` after changes")
|
|
1067
|
+
- Reference project-specific limits or rules
|
|
1068
|
+
|
|
1069
|
+
5. **Usage Examples must be concrete**:
|
|
1070
|
+
- Use realistic file paths from this project
|
|
1071
|
+
- Show actual commands the user would run
|
|
1072
|
+
|
|
1073
|
+
## Output Format
|
|
1074
|
+
|
|
1075
|
+
\`\`\`markdown
|
|
1076
|
+
# {Agent Name}
|
|
1077
|
+
|
|
1078
|
+
{One-line description connecting to ${analysis.projectName}}
|
|
1079
|
+
|
|
1080
|
+
## Role
|
|
1081
|
+
|
|
1082
|
+
{Role specific to this ${analysis.stack.language}/${analysis.stack.framework} project}
|
|
1083
|
+
|
|
1084
|
+
## Expertise
|
|
1085
|
+
|
|
1086
|
+
{Bulleted list of expertise areas, referencing project specifics}
|
|
1087
|
+
|
|
1088
|
+
## Instructions
|
|
1089
|
+
|
|
1090
|
+
{Step-by-step instructions with project-specific details}
|
|
1091
|
+
{Include subsections for different scenarios}
|
|
1092
|
+
{Reference actual paths like \`${analysis.patterns.structure.sourceDir}/...\`}
|
|
1093
|
+
|
|
1094
|
+
## Constraints
|
|
1095
|
+
|
|
1096
|
+
{Numbered constraints with verification steps}
|
|
1097
|
+
{Include commands like \`${analysis.commands.test}\`, \`${analysis.commands.lint}\`}
|
|
1098
|
+
|
|
1099
|
+
## Usage Examples
|
|
1100
|
+
|
|
1101
|
+
{3+ concrete examples with:}
|
|
1102
|
+
- Realistic prompts a user might give
|
|
1103
|
+
- Expected behavior referencing project structure
|
|
1104
|
+
\`\`\`
|
|
1105
|
+
|
|
1106
|
+
Output ONLY the Markdown content, no code fences around it.`;
|
|
1107
|
+
}
|
|
1108
|
+
function escapeForShell(str) {
|
|
1109
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`").replace(/\n/g, "\\n");
|
|
1110
|
+
}
|
|
1111
|
+
function parseAgentSuggestions(response) {
|
|
1112
|
+
try {
|
|
1113
|
+
const jsonMatch = response.match(/```json\s*([\s\S]*?)\s*```/) || response.match(/\[[\s\S]*\]/);
|
|
1114
|
+
if (!jsonMatch) {
|
|
1115
|
+
console.warn("Could not parse agent suggestions from response");
|
|
1116
|
+
return [];
|
|
1117
|
+
}
|
|
1118
|
+
const jsonStr = jsonMatch[1] || jsonMatch[0];
|
|
1119
|
+
const parsed = JSON.parse(jsonStr);
|
|
1120
|
+
if (!Array.isArray(parsed)) {
|
|
1121
|
+
return [];
|
|
1122
|
+
}
|
|
1123
|
+
return parsed.map((item) => ({
|
|
1124
|
+
name: String(item.name || ""),
|
|
1125
|
+
description: String(item.description || ""),
|
|
1126
|
+
focus: String(item.focus || ""),
|
|
1127
|
+
reason: String(item.reason || "")
|
|
1128
|
+
})).filter((s) => s.name && s.description);
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
console.warn("Failed to parse agent suggestions:", error);
|
|
1131
|
+
return [];
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// src/generator.ts
|
|
1136
|
+
function generateClaudeMd(result, options) {
|
|
1137
|
+
const documents = options.documents || { existingAgents: [] };
|
|
1138
|
+
if (isClaudeAvailable()) {
|
|
1139
|
+
try {
|
|
1140
|
+
if (options.verbose) {
|
|
1141
|
+
console.log("\u{1F916} Generating CLAUDE.md using Claude Code...");
|
|
1142
|
+
}
|
|
1143
|
+
return generateClaudeMdContent(result, documents, { verbose: options.verbose, lang: options.lang });
|
|
1144
|
+
} catch (error) {
|
|
1145
|
+
console.warn("\u26A0\uFE0F Claude Code call failed, using fallback");
|
|
1146
|
+
if (options.verbose && error instanceof Error) {
|
|
1147
|
+
console.warn(` Details: ${error.message}`);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
} else if (options.verbose) {
|
|
1151
|
+
console.log("\u2139\uFE0F Claude Code not available, using template");
|
|
1152
|
+
}
|
|
1153
|
+
if (options.template === "minimal") {
|
|
1154
|
+
return generateMinimalFallback(result);
|
|
1155
|
+
}
|
|
1156
|
+
return generateFullFallback(result, options);
|
|
1157
|
+
}
|
|
1158
|
+
function generateMinimalFallback(result) {
|
|
1159
|
+
const { projectName, stack, commands, patterns } = result;
|
|
1160
|
+
const lines = [];
|
|
1161
|
+
lines.push(`# ${projectName}`);
|
|
1162
|
+
lines.push("");
|
|
1163
|
+
lines.push("## Tech Stack");
|
|
1164
|
+
lines.push(`- **Language**: ${formatLanguage(stack.language)}${stack.languageVersion ? ` ${stack.languageVersion}` : ""}`);
|
|
1165
|
+
if (stack.framework !== "unknown") {
|
|
1166
|
+
lines.push(`- **Framework**: ${formatFramework(stack.framework)}${stack.frameworkVersion ? ` ${stack.frameworkVersion}` : ""}`);
|
|
1167
|
+
}
|
|
1168
|
+
if (stack.testFramework !== "unknown") {
|
|
1169
|
+
lines.push(`- **Testing**: ${formatTestFramework(stack.testFramework)}`);
|
|
1170
|
+
}
|
|
1171
|
+
lines.push("");
|
|
1172
|
+
if (Object.keys(commands).length > 0) {
|
|
1173
|
+
lines.push("## Commands");
|
|
1174
|
+
lines.push("```bash");
|
|
1175
|
+
if (commands.dev) lines.push(`# Development
|
|
1176
|
+
${commands.dev}`);
|
|
1177
|
+
if (commands.build) lines.push(`
|
|
1178
|
+
# Build
|
|
1179
|
+
${commands.build}`);
|
|
1180
|
+
if (commands.test) lines.push(`
|
|
1181
|
+
# Test
|
|
1182
|
+
${commands.test}`);
|
|
1183
|
+
if (commands.lint) lines.push(`
|
|
1184
|
+
# Lint
|
|
1185
|
+
${commands.lint}`);
|
|
1186
|
+
lines.push("```");
|
|
1187
|
+
lines.push("");
|
|
1188
|
+
}
|
|
1189
|
+
lines.push("## Conventions");
|
|
1190
|
+
if (patterns.naming.files.components) {
|
|
1191
|
+
lines.push(`- Component files: ${patterns.naming.files.components}`);
|
|
1192
|
+
}
|
|
1193
|
+
if (patterns.naming.files.tests) {
|
|
1194
|
+
lines.push(`- Test files: ${patterns.naming.files.tests}`);
|
|
1195
|
+
}
|
|
1196
|
+
if (patterns.structure.type !== "unknown") {
|
|
1197
|
+
lines.push(`- Project structure: ${formatStructureType(patterns.structure.type)}`);
|
|
1198
|
+
}
|
|
1199
|
+
lines.push("");
|
|
1200
|
+
lines.push("---");
|
|
1201
|
+
lines.push(`*Generated by Proteus (fallback mode) on ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}*`);
|
|
1202
|
+
return lines.join("\n");
|
|
1203
|
+
}
|
|
1204
|
+
function generateFullFallback(result, options) {
|
|
1205
|
+
const { projectName, description, stack, commands, patterns, confidence } = result;
|
|
1206
|
+
const lines = [];
|
|
1207
|
+
lines.push(`# ${projectName}`);
|
|
1208
|
+
lines.push("");
|
|
1209
|
+
if (description) {
|
|
1210
|
+
lines.push(`> ${description}`);
|
|
1211
|
+
lines.push("");
|
|
1212
|
+
}
|
|
1213
|
+
lines.push("## Tech Stack");
|
|
1214
|
+
lines.push("");
|
|
1215
|
+
lines.push("| Category | Technology | Version |");
|
|
1216
|
+
lines.push("|----------|------------|---------|");
|
|
1217
|
+
lines.push(`| Language | ${formatLanguage(stack.language)} | ${stack.languageVersion || "-"} |`);
|
|
1218
|
+
if (stack.framework !== "unknown") {
|
|
1219
|
+
lines.push(`| Framework | ${formatFramework(stack.framework)} | ${stack.frameworkVersion || "-"} |`);
|
|
1220
|
+
}
|
|
1221
|
+
if (stack.testFramework !== "unknown") {
|
|
1222
|
+
lines.push(`| Testing | ${formatTestFramework(stack.testFramework)} | - |`);
|
|
1223
|
+
}
|
|
1224
|
+
lines.push(`| Package Manager | ${stack.packageManager} | - |`);
|
|
1225
|
+
lines.push("");
|
|
1226
|
+
if (stack.additionalTools.length > 0) {
|
|
1227
|
+
lines.push("### Additional Tools");
|
|
1228
|
+
lines.push(stack.additionalTools.map((t) => `- ${t}`).join("\n"));
|
|
1229
|
+
lines.push("");
|
|
1230
|
+
}
|
|
1231
|
+
lines.push("## Project Structure");
|
|
1232
|
+
lines.push("");
|
|
1233
|
+
lines.push(`- **Type**: ${formatStructureType(patterns.structure.type)}`);
|
|
1234
|
+
lines.push(`- **Source Directory**: \`${patterns.structure.sourceDir}/\``);
|
|
1235
|
+
if (patterns.structure.testDir) {
|
|
1236
|
+
lines.push(`- **Test Directory**: \`${patterns.structure.testDir}/\``);
|
|
1237
|
+
}
|
|
1238
|
+
lines.push("");
|
|
1239
|
+
if (Object.keys(commands).length > 0) {
|
|
1240
|
+
lines.push("## Commands");
|
|
1241
|
+
lines.push("");
|
|
1242
|
+
lines.push("| Command | Description |");
|
|
1243
|
+
lines.push("|---------|-------------|");
|
|
1244
|
+
if (commands.dev) lines.push(`| \`${commands.dev}\` | Start development server |`);
|
|
1245
|
+
if (commands.build) lines.push(`| \`${commands.build}\` | Build for production |`);
|
|
1246
|
+
if (commands.test) lines.push(`| \`${commands.test}\` | Run tests |`);
|
|
1247
|
+
if (commands.lint) lines.push(`| \`${commands.lint}\` | Run linter |`);
|
|
1248
|
+
lines.push("");
|
|
1249
|
+
}
|
|
1250
|
+
lines.push("## Code Conventions");
|
|
1251
|
+
lines.push("");
|
|
1252
|
+
lines.push("### Naming");
|
|
1253
|
+
lines.push("");
|
|
1254
|
+
lines.push("| Type | Convention |");
|
|
1255
|
+
lines.push("|------|------------|");
|
|
1256
|
+
lines.push(`| Functions | ${patterns.naming.code.functions} |`);
|
|
1257
|
+
lines.push(`| Variables | ${patterns.naming.code.variables} |`);
|
|
1258
|
+
lines.push(`| Constants | ${patterns.naming.code.constants} |`);
|
|
1259
|
+
if (patterns.naming.code.types) {
|
|
1260
|
+
lines.push(`| Types/Interfaces | ${patterns.naming.code.types} |`);
|
|
1261
|
+
}
|
|
1262
|
+
lines.push("");
|
|
1263
|
+
lines.push("---");
|
|
1264
|
+
lines.push("");
|
|
1265
|
+
lines.push("## Proteus Metadata");
|
|
1266
|
+
lines.push("");
|
|
1267
|
+
lines.push("```yaml");
|
|
1268
|
+
lines.push(`version: ${options.version}`);
|
|
1269
|
+
lines.push(`generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
1270
|
+
lines.push(`confidence: ${Math.round(confidence.overall * 100)}%`);
|
|
1271
|
+
lines.push("mode: fallback (Claude Code not available)");
|
|
1272
|
+
lines.push("```");
|
|
1273
|
+
lines.push("");
|
|
1274
|
+
lines.push("*This file was generated by [Proteus](https://github.com/ischca/proteus) in fallback mode.*");
|
|
1275
|
+
lines.push("*Re-run with Claude Code available for better results.*");
|
|
1276
|
+
return lines.join("\n");
|
|
1277
|
+
}
|
|
1278
|
+
function formatLanguage(lang) {
|
|
1279
|
+
const map = {
|
|
1280
|
+
typescript: "TypeScript",
|
|
1281
|
+
javascript: "JavaScript",
|
|
1282
|
+
go: "Go",
|
|
1283
|
+
python: "Python",
|
|
1284
|
+
rust: "Rust",
|
|
1285
|
+
ruby: "Ruby",
|
|
1286
|
+
java: "Java",
|
|
1287
|
+
php: "PHP"
|
|
1288
|
+
};
|
|
1289
|
+
return map[lang] || lang;
|
|
1290
|
+
}
|
|
1291
|
+
function formatFramework(fw) {
|
|
1292
|
+
const map = {
|
|
1293
|
+
nextjs: "Next.js",
|
|
1294
|
+
react: "React",
|
|
1295
|
+
vue: "Vue.js",
|
|
1296
|
+
angular: "Angular",
|
|
1297
|
+
svelte: "Svelte",
|
|
1298
|
+
express: "Express",
|
|
1299
|
+
fastify: "Fastify",
|
|
1300
|
+
nestjs: "NestJS",
|
|
1301
|
+
gin: "Gin",
|
|
1302
|
+
echo: "Echo",
|
|
1303
|
+
fiber: "Fiber",
|
|
1304
|
+
django: "Django",
|
|
1305
|
+
flask: "Flask",
|
|
1306
|
+
fastapi: "FastAPI",
|
|
1307
|
+
rails: "Ruby on Rails",
|
|
1308
|
+
spring: "Spring",
|
|
1309
|
+
laravel: "Laravel",
|
|
1310
|
+
actix: "Actix Web",
|
|
1311
|
+
axum: "Axum"
|
|
1312
|
+
};
|
|
1313
|
+
return map[fw] || fw;
|
|
1314
|
+
}
|
|
1315
|
+
function formatTestFramework(tf) {
|
|
1316
|
+
const map = {
|
|
1317
|
+
jest: "Jest",
|
|
1318
|
+
vitest: "Vitest",
|
|
1319
|
+
mocha: "Mocha",
|
|
1320
|
+
pytest: "pytest",
|
|
1321
|
+
"go-test": "Go testing",
|
|
1322
|
+
rspec: "RSpec",
|
|
1323
|
+
junit: "JUnit",
|
|
1324
|
+
phpunit: "PHPUnit"
|
|
1325
|
+
};
|
|
1326
|
+
return map[tf] || tf;
|
|
1327
|
+
}
|
|
1328
|
+
function formatStructureType(type) {
|
|
1329
|
+
const map = {
|
|
1330
|
+
flat: "Flat (simple)",
|
|
1331
|
+
"feature-based": "Feature-based (modular)",
|
|
1332
|
+
"layer-based": "Layer-based (MVC/Clean)",
|
|
1333
|
+
hybrid: "Hybrid"
|
|
1334
|
+
};
|
|
1335
|
+
return map[type] || type;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// src/agent-generator.ts
|
|
1339
|
+
function suggestAgents2(analysis, documents, options = {}) {
|
|
1340
|
+
if (!isClaudeAvailable()) {
|
|
1341
|
+
if (options.verbose) {
|
|
1342
|
+
console.log("\u26A0\uFE0F Claude Code not available, using default suggestions");
|
|
1343
|
+
}
|
|
1344
|
+
return getDefaultSuggestions(analysis);
|
|
1345
|
+
}
|
|
1346
|
+
try {
|
|
1347
|
+
if (options.verbose) {
|
|
1348
|
+
console.log("\u{1F916} Querying Claude Code for agent suggestions...");
|
|
1349
|
+
}
|
|
1350
|
+
return suggestAgents(analysis, documents, { verbose: options.verbose, lang: options.lang });
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
if (options.verbose) {
|
|
1353
|
+
console.warn("\u26A0\uFE0F Claude Code call failed, using default suggestions");
|
|
1354
|
+
if (error instanceof Error) {
|
|
1355
|
+
console.warn(` Details: ${error.message}`);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
return getDefaultSuggestions(analysis);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
function generateAgent(suggestion, analysis, documents, options = {}) {
|
|
1362
|
+
const outputDir = options.outputDir || ".claude/agents";
|
|
1363
|
+
if (!isClaudeAvailable()) {
|
|
1364
|
+
if (options.verbose) {
|
|
1365
|
+
console.log(`\u26A0\uFE0F Claude Code not available, using fallback for ${suggestion.name}`);
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
name: suggestion.name,
|
|
1369
|
+
description: suggestion.description,
|
|
1370
|
+
content: generateFallbackAgent(suggestion, analysis, documents),
|
|
1371
|
+
path: `${outputDir}/${suggestion.name}.md`
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
try {
|
|
1375
|
+
if (options.verbose) {
|
|
1376
|
+
console.log(`\u{1F916} Generating ${suggestion.name} agent...`);
|
|
1377
|
+
}
|
|
1378
|
+
const content = generateAgentContent(
|
|
1379
|
+
suggestion.name,
|
|
1380
|
+
suggestion.description,
|
|
1381
|
+
analysis,
|
|
1382
|
+
documents,
|
|
1383
|
+
{ verbose: options.verbose, lang: options.lang }
|
|
1384
|
+
);
|
|
1385
|
+
return {
|
|
1386
|
+
name: suggestion.name,
|
|
1387
|
+
description: suggestion.description,
|
|
1388
|
+
content,
|
|
1389
|
+
path: `${outputDir}/${suggestion.name}.md`
|
|
1390
|
+
};
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
if (options.verbose) {
|
|
1393
|
+
console.warn(`\u26A0\uFE0F Failed to generate ${suggestion.name}, using fallback`);
|
|
1394
|
+
if (error instanceof Error) {
|
|
1395
|
+
console.warn(` Details: ${error.message}`);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return {
|
|
1399
|
+
name: suggestion.name,
|
|
1400
|
+
description: suggestion.description,
|
|
1401
|
+
content: generateFallbackAgent(suggestion, analysis, documents),
|
|
1402
|
+
path: `${outputDir}/${suggestion.name}.md`
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
function getDefaultSuggestions(analysis) {
|
|
1407
|
+
const suggestions = [];
|
|
1408
|
+
const { stack } = analysis;
|
|
1409
|
+
suggestions.push({
|
|
1410
|
+
name: "code-reviewer",
|
|
1411
|
+
description: `${formatLanguage2(stack.language)} code review specialist`,
|
|
1412
|
+
focus: "Code quality, best practices, security",
|
|
1413
|
+
reason: "Code review is valuable for all projects"
|
|
1414
|
+
});
|
|
1415
|
+
if (stack.testFramework !== "unknown") {
|
|
1416
|
+
suggestions.push({
|
|
1417
|
+
name: "test-writer",
|
|
1418
|
+
description: `${formatTestFramework2(stack.testFramework)} test writing specialist`,
|
|
1419
|
+
focus: "Unit tests, integration tests, test coverage",
|
|
1420
|
+
reason: `${formatTestFramework2(stack.testFramework)} detected in project`
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
if (stack.framework !== "unknown") {
|
|
1424
|
+
if (["react", "vue", "svelte", "angular"].includes(stack.framework)) {
|
|
1425
|
+
suggestions.push({
|
|
1426
|
+
name: "component-builder",
|
|
1427
|
+
description: `${formatFramework2(stack.framework)} component design specialist`,
|
|
1428
|
+
focus: "Component design, state management, performance",
|
|
1429
|
+
reason: `${formatFramework2(stack.framework)} frontend project`
|
|
1430
|
+
});
|
|
1431
|
+
} else if (["express", "fastify", "nestjs", "fastapi", "gin"].includes(stack.framework)) {
|
|
1432
|
+
suggestions.push({
|
|
1433
|
+
name: "api-designer",
|
|
1434
|
+
description: `${formatFramework2(stack.framework)} API endpoint design specialist`,
|
|
1435
|
+
focus: "API design, validation, error handling",
|
|
1436
|
+
reason: `${formatFramework2(stack.framework)} backend project`
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
suggestions.push({
|
|
1441
|
+
name: "docs-writer",
|
|
1442
|
+
description: "Documentation writing specialist",
|
|
1443
|
+
focus: "README, API docs, code comments",
|
|
1444
|
+
reason: "Documentation is important for all projects"
|
|
1445
|
+
});
|
|
1446
|
+
suggestions.push({
|
|
1447
|
+
name: "refactorer",
|
|
1448
|
+
description: `${formatLanguage2(stack.language)} refactoring specialist`,
|
|
1449
|
+
focus: "Code improvement, design patterns, technical debt",
|
|
1450
|
+
reason: "Continuous code improvement"
|
|
1451
|
+
});
|
|
1452
|
+
return suggestions.slice(0, 5);
|
|
1453
|
+
}
|
|
1454
|
+
function generateFallbackAgent(suggestion, analysis, documents) {
|
|
1455
|
+
const { stack, patterns, projectName } = analysis;
|
|
1456
|
+
const rules = documents.claudeMd?.rules || [];
|
|
1457
|
+
const conventions = documents.claudeMd?.conventions || [];
|
|
1458
|
+
const lines = [];
|
|
1459
|
+
lines.push(`# ${suggestion.name}`);
|
|
1460
|
+
lines.push("");
|
|
1461
|
+
lines.push(`> ${suggestion.description}`);
|
|
1462
|
+
lines.push("");
|
|
1463
|
+
lines.push("## Role");
|
|
1464
|
+
lines.push("");
|
|
1465
|
+
lines.push(`Handles ${suggestion.focus} for the ${projectName} project.`);
|
|
1466
|
+
lines.push("");
|
|
1467
|
+
lines.push("## Project Information");
|
|
1468
|
+
lines.push("");
|
|
1469
|
+
lines.push(`- **Language**: ${formatLanguage2(stack.language)}${stack.languageVersion ? ` ${stack.languageVersion}` : ""}`);
|
|
1470
|
+
if (stack.framework !== "unknown") {
|
|
1471
|
+
lines.push(`- **Framework**: ${formatFramework2(stack.framework)}`);
|
|
1472
|
+
}
|
|
1473
|
+
if (stack.testFramework !== "unknown") {
|
|
1474
|
+
lines.push(`- **Testing**: ${formatTestFramework2(stack.testFramework)}`);
|
|
1475
|
+
}
|
|
1476
|
+
lines.push(`- **Structure**: ${patterns.structure.type}`);
|
|
1477
|
+
lines.push("");
|
|
1478
|
+
if (rules.length > 0 || conventions.length > 0) {
|
|
1479
|
+
lines.push("## Project Rules");
|
|
1480
|
+
lines.push("");
|
|
1481
|
+
lines.push("Follow these rules:");
|
|
1482
|
+
lines.push("");
|
|
1483
|
+
[...rules, ...conventions].forEach((rule) => {
|
|
1484
|
+
lines.push(`- ${rule}`);
|
|
1485
|
+
});
|
|
1486
|
+
lines.push("");
|
|
1487
|
+
}
|
|
1488
|
+
lines.push("## Instructions");
|
|
1489
|
+
lines.push("");
|
|
1490
|
+
lines.push(`This agent specializes in ${suggestion.focus}.`);
|
|
1491
|
+
lines.push(`Follow ${formatLanguage2(stack.language)} best practices.`);
|
|
1492
|
+
lines.push("");
|
|
1493
|
+
lines.push("---");
|
|
1494
|
+
lines.push("");
|
|
1495
|
+
lines.push("*Generated by Proteus (fallback mode)*");
|
|
1496
|
+
return lines.join("\n");
|
|
1497
|
+
}
|
|
1498
|
+
function formatLanguage2(lang) {
|
|
1499
|
+
const map = {
|
|
1500
|
+
typescript: "TypeScript",
|
|
1501
|
+
javascript: "JavaScript",
|
|
1502
|
+
go: "Go",
|
|
1503
|
+
python: "Python",
|
|
1504
|
+
rust: "Rust",
|
|
1505
|
+
ruby: "Ruby",
|
|
1506
|
+
java: "Java",
|
|
1507
|
+
php: "PHP"
|
|
1508
|
+
};
|
|
1509
|
+
return map[lang] || lang;
|
|
1510
|
+
}
|
|
1511
|
+
function formatFramework2(fw) {
|
|
1512
|
+
const map = {
|
|
1513
|
+
nextjs: "Next.js",
|
|
1514
|
+
react: "React",
|
|
1515
|
+
vue: "Vue.js",
|
|
1516
|
+
angular: "Angular",
|
|
1517
|
+
svelte: "Svelte",
|
|
1518
|
+
express: "Express",
|
|
1519
|
+
fastify: "Fastify",
|
|
1520
|
+
nestjs: "NestJS",
|
|
1521
|
+
gin: "Gin",
|
|
1522
|
+
echo: "Echo",
|
|
1523
|
+
fiber: "Fiber",
|
|
1524
|
+
django: "Django",
|
|
1525
|
+
flask: "Flask",
|
|
1526
|
+
fastapi: "FastAPI",
|
|
1527
|
+
rails: "Ruby on Rails",
|
|
1528
|
+
spring: "Spring",
|
|
1529
|
+
laravel: "Laravel",
|
|
1530
|
+
actix: "Actix Web",
|
|
1531
|
+
axum: "Axum"
|
|
1532
|
+
};
|
|
1533
|
+
return map[fw] || fw;
|
|
1534
|
+
}
|
|
1535
|
+
function formatTestFramework2(tf) {
|
|
1536
|
+
const map = {
|
|
1537
|
+
jest: "Jest",
|
|
1538
|
+
vitest: "Vitest",
|
|
1539
|
+
mocha: "Mocha",
|
|
1540
|
+
pytest: "pytest",
|
|
1541
|
+
"go-test": "Go testing",
|
|
1542
|
+
rspec: "RSpec",
|
|
1543
|
+
junit: "JUnit",
|
|
1544
|
+
phpunit: "PHPUnit"
|
|
1545
|
+
};
|
|
1546
|
+
return map[tf] || tf;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// src/skill-generator.ts
|
|
1550
|
+
import * as fs5 from "fs";
|
|
1551
|
+
import * as path5 from "path";
|
|
1552
|
+
var I18N = {
|
|
1553
|
+
en: {
|
|
1554
|
+
skillName: "proteus",
|
|
1555
|
+
skillDescription: "Select and delegate tasks to project-specific agents based on your needs",
|
|
1556
|
+
roleTitle: "Role",
|
|
1557
|
+
roleDescription: "You are a task router that analyzes user requests and delegates them to the most appropriate project-specific agent. Each agent is specialized for a particular aspect of this project.",
|
|
1558
|
+
availableAgentsTitle: "Available Agents",
|
|
1559
|
+
instructionsTitle: "Instructions",
|
|
1560
|
+
instructions: [
|
|
1561
|
+
"Analyze the user's request to understand the task type",
|
|
1562
|
+
"Select the SINGLE most appropriate agent from the list below",
|
|
1563
|
+
"Delegate the task to that agent using @agent-name",
|
|
1564
|
+
"If no agent fits, handle the task directly or suggest which agent might be needed"
|
|
1565
|
+
],
|
|
1566
|
+
examplesTitle: "Examples",
|
|
1567
|
+
examples: [
|
|
1568
|
+
'User: "Review this GraphQL resolver" \u2192 Delegate to the GraphQL-related agent',
|
|
1569
|
+
'User: "Write tests for this endpoint" \u2192 Delegate to the test-writing agent',
|
|
1570
|
+
'User: "Check if this follows our patterns" \u2192 Delegate to the pattern/style enforcement agent'
|
|
1571
|
+
]
|
|
1572
|
+
},
|
|
1573
|
+
ja: {
|
|
1574
|
+
skillName: "proteus",
|
|
1575
|
+
skillDescription: "\u30E6\u30FC\u30B6\u30FC\u306E\u30CB\u30FC\u30BA\u306B\u57FA\u3065\u3044\u3066\u3001\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u5C02\u7528\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u30BF\u30B9\u30AF\u3092\u59D4\u8B72\u3057\u307E\u3059",
|
|
1576
|
+
roleTitle: "\u5F79\u5272",
|
|
1577
|
+
roleDescription: "\u3042\u306A\u305F\u306F\u30BF\u30B9\u30AF\u30EB\u30FC\u30BF\u30FC\u3067\u3059\u3002\u30E6\u30FC\u30B6\u30FC\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u5206\u6790\u3057\u3001\u6700\u3082\u9069\u5207\u306A\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u5C02\u7528\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u59D4\u8B72\u3057\u307E\u3059\u3002\u5404\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306F\u3053\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u7279\u5B9A\u306E\u5074\u9762\u306B\u7279\u5316\u3057\u3066\u3044\u307E\u3059\u3002",
|
|
1578
|
+
availableAgentsTitle: "\u5229\u7528\u53EF\u80FD\u306A\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8",
|
|
1579
|
+
instructionsTitle: "\u624B\u9806",
|
|
1580
|
+
instructions: [
|
|
1581
|
+
"\u30E6\u30FC\u30B6\u30FC\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u5206\u6790\u3057\u3066\u30BF\u30B9\u30AF\u306E\u7A2E\u985E\u3092\u7406\u89E3\u3059\u308B",
|
|
1582
|
+
"\u4EE5\u4E0B\u306E\u30EA\u30B9\u30C8\u304B\u3089\u6700\u3082\u9069\u5207\u306A\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u30921\u3064\u9078\u629E\u3059\u308B",
|
|
1583
|
+
"@agent-name \u3092\u4F7F\u7528\u3057\u3066\u305D\u306E\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u30BF\u30B9\u30AF\u3092\u59D4\u8B72\u3059\u308B",
|
|
1584
|
+
"\u9069\u5207\u306A\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u304C\u306A\u3044\u5834\u5408\u306F\u3001\u76F4\u63A5\u30BF\u30B9\u30AF\u3092\u51E6\u7406\u3059\u308B\u304B\u3001\u5FC5\u8981\u306A\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3092\u63D0\u6848\u3059\u308B"
|
|
1585
|
+
],
|
|
1586
|
+
examplesTitle: "\u4F7F\u7528\u4F8B",
|
|
1587
|
+
examples: [
|
|
1588
|
+
"\u30E6\u30FC\u30B6\u30FC: \u300C\u3053\u306EGraphQL\u30EA\u30BE\u30EB\u30D0\u3092\u30EC\u30D3\u30E5\u30FC\u3057\u3066\u300D\u2192 GraphQL\u95A2\u9023\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u59D4\u8B72",
|
|
1589
|
+
"\u30E6\u30FC\u30B6\u30FC: \u300C\u3053\u306E\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u306E\u30C6\u30B9\u30C8\u3092\u66F8\u3044\u3066\u300D\u2192 \u30C6\u30B9\u30C8\u4F5C\u6210\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u59D4\u8B72",
|
|
1590
|
+
"\u30E6\u30FC\u30B6\u30FC: \u300C\u3053\u308C\u304C\u30D1\u30BF\u30FC\u30F3\u306B\u5F93\u3063\u3066\u3044\u308B\u304B\u78BA\u8A8D\u3057\u3066\u300D\u2192 \u30D1\u30BF\u30FC\u30F3/\u30B9\u30BF\u30A4\u30EB\u9069\u7528\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u59D4\u8B72"
|
|
1591
|
+
]
|
|
1592
|
+
},
|
|
1593
|
+
zh: {
|
|
1594
|
+
skillName: "proteus",
|
|
1595
|
+
skillDescription: "\u6839\u636E\u60A8\u7684\u9700\u6C42\u9009\u62E9\u5E76\u5C06\u4EFB\u52A1\u59D4\u6D3E\u7ED9\u9879\u76EE\u4E13\u7528\u4EE3\u7406",
|
|
1596
|
+
roleTitle: "\u89D2\u8272",
|
|
1597
|
+
roleDescription: "\u60A8\u662F\u4E00\u4E2A\u4EFB\u52A1\u8DEF\u7531\u5668\uFF0C\u5206\u6790\u7528\u6237\u8BF7\u6C42\u5E76\u5C06\u5176\u59D4\u6D3E\u7ED9\u6700\u5408\u9002\u7684\u9879\u76EE\u4E13\u7528\u4EE3\u7406\u3002\u6BCF\u4E2A\u4EE3\u7406\u90FD\u4E13\u95E8\u9488\u5BF9\u6B64\u9879\u76EE\u7684\u7279\u5B9A\u65B9\u9762\u3002",
|
|
1598
|
+
availableAgentsTitle: "\u53EF\u7528\u4EE3\u7406",
|
|
1599
|
+
instructionsTitle: "\u8BF4\u660E",
|
|
1600
|
+
instructions: [
|
|
1601
|
+
"\u5206\u6790\u7528\u6237\u8BF7\u6C42\u4EE5\u4E86\u89E3\u4EFB\u52A1\u7C7B\u578B",
|
|
1602
|
+
"\u4ECE\u4E0B\u9762\u7684\u5217\u8868\u4E2D\u9009\u62E9\u6700\u5408\u9002\u7684\u5355\u4E2A\u4EE3\u7406",
|
|
1603
|
+
"\u4F7F\u7528 @agent-name \u5C06\u4EFB\u52A1\u59D4\u6D3E\u7ED9\u8BE5\u4EE3\u7406",
|
|
1604
|
+
"\u5982\u679C\u6CA1\u6709\u5408\u9002\u7684\u4EE3\u7406\uFF0C\u76F4\u63A5\u5904\u7406\u4EFB\u52A1\u6216\u5EFA\u8BAE\u53EF\u80FD\u9700\u8981\u7684\u4EE3\u7406"
|
|
1605
|
+
],
|
|
1606
|
+
examplesTitle: "\u793A\u4F8B",
|
|
1607
|
+
examples: [
|
|
1608
|
+
'\u7528\u6237\uFF1A"\u5BA1\u67E5\u8FD9\u4E2AGraphQL\u89E3\u6790\u5668" \u2192 \u59D4\u6D3E\u7ED9GraphQL\u76F8\u5173\u4EE3\u7406',
|
|
1609
|
+
'\u7528\u6237\uFF1A"\u4E3A\u8FD9\u4E2A\u7AEF\u70B9\u7F16\u5199\u6D4B\u8BD5" \u2192 \u59D4\u6D3E\u7ED9\u6D4B\u8BD5\u7F16\u5199\u4EE3\u7406',
|
|
1610
|
+
'\u7528\u6237\uFF1A"\u68C0\u67E5\u8FD9\u662F\u5426\u7B26\u5408\u6211\u4EEC\u7684\u6A21\u5F0F" \u2192 \u59D4\u6D3E\u7ED9\u6A21\u5F0F/\u98CE\u683C\u6267\u884C\u4EE3\u7406'
|
|
1611
|
+
]
|
|
1612
|
+
},
|
|
1613
|
+
ko: {
|
|
1614
|
+
skillName: "proteus",
|
|
1615
|
+
skillDescription: "\uC0AC\uC6A9\uC790\uC758 \uC694\uAD6C\uC5D0 \uB530\uB77C \uD504\uB85C\uC81D\uD2B8 \uC804\uC6A9 \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC791\uC5C5\uC744 \uC704\uC784\uD569\uB2C8\uB2E4",
|
|
1616
|
+
roleTitle: "\uC5ED\uD560",
|
|
1617
|
+
roleDescription: "\uC0AC\uC6A9\uC790 \uC694\uCCAD\uC744 \uBD84\uC11D\uD558\uACE0 \uAC00\uC7A5 \uC801\uC808\uD55C \uD504\uB85C\uC81D\uD2B8 \uC804\uC6A9 \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC704\uC784\uD558\uB294 \uC791\uC5C5 \uB77C\uC6B0\uD130\uC785\uB2C8\uB2E4. \uAC01 \uC5D0\uC774\uC804\uD2B8\uB294 \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uD2B9\uC815 \uCE21\uBA74\uC5D0 \uD2B9\uD654\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
1618
|
+
availableAgentsTitle: "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uC5D0\uC774\uC804\uD2B8",
|
|
1619
|
+
instructionsTitle: "\uC9C0\uCE68",
|
|
1620
|
+
instructions: [
|
|
1621
|
+
"\uC0AC\uC6A9\uC790 \uC694\uCCAD\uC744 \uBD84\uC11D\uD558\uC5EC \uC791\uC5C5 \uC720\uD615 \uD30C\uC545",
|
|
1622
|
+
"\uC544\uB798 \uBAA9\uB85D\uC5D0\uC11C \uAC00\uC7A5 \uC801\uC808\uD55C \uC5D0\uC774\uC804\uD2B8 \uD558\uB098 \uC120\uD0DD",
|
|
1623
|
+
"@agent-name\uC744 \uC0AC\uC6A9\uD558\uC5EC \uD574\uB2F9 \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC791\uC5C5 \uC704\uC784",
|
|
1624
|
+
"\uC801\uC808\uD55C \uC5D0\uC774\uC804\uD2B8\uAC00 \uC5C6\uC73C\uBA74 \uC9C1\uC811 \uC791\uC5C5\uC744 \uCC98\uB9AC\uD558\uAC70\uB098 \uD544\uC694\uD55C \uC5D0\uC774\uC804\uD2B8 \uC81C\uC548"
|
|
1625
|
+
],
|
|
1626
|
+
examplesTitle: "\uC608\uC2DC",
|
|
1627
|
+
examples: [
|
|
1628
|
+
'\uC0AC\uC6A9\uC790: "\uC774 GraphQL \uB9AC\uC878\uBC84 \uAC80\uD1A0\uD574\uC918" \u2192 GraphQL \uAD00\uB828 \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC704\uC784',
|
|
1629
|
+
'\uC0AC\uC6A9\uC790: "\uC774 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uD14C\uC2A4\uD2B8 \uC791\uC131\uD574\uC918" \u2192 \uD14C\uC2A4\uD2B8 \uC791\uC131 \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC704\uC784',
|
|
1630
|
+
'\uC0AC\uC6A9\uC790: "\uC774\uAC83\uC774 \uD328\uD134\uC744 \uB530\uB974\uB294\uC9C0 \uD655\uC778\uD574\uC918" \u2192 \uD328\uD134/\uC2A4\uD0C0\uC77C \uC801\uC6A9 \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC704\uC784'
|
|
1631
|
+
]
|
|
1632
|
+
},
|
|
1633
|
+
es: {
|
|
1634
|
+
skillName: "proteus",
|
|
1635
|
+
skillDescription: "Selecciona y delega tareas a agentes espec\xEDficos del proyecto seg\xFAn sus necesidades",
|
|
1636
|
+
roleTitle: "Rol",
|
|
1637
|
+
roleDescription: "Eres un enrutador de tareas que analiza las solicitudes del usuario y las delega al agente espec\xEDfico del proyecto m\xE1s apropiado. Cada agente est\xE1 especializado en un aspecto particular de este proyecto.",
|
|
1638
|
+
availableAgentsTitle: "Agentes Disponibles",
|
|
1639
|
+
instructionsTitle: "Instrucciones",
|
|
1640
|
+
instructions: [
|
|
1641
|
+
"Analizar la solicitud del usuario para comprender el tipo de tarea",
|
|
1642
|
+
"Seleccionar el agente m\xE1s apropiado de la lista siguiente",
|
|
1643
|
+
"Delegar la tarea a ese agente usando @agent-name",
|
|
1644
|
+
"Si ning\xFAn agente es adecuado, manejar la tarea directamente o sugerir qu\xE9 agente podr\xEDa necesitarse"
|
|
1645
|
+
],
|
|
1646
|
+
examplesTitle: "Ejemplos",
|
|
1647
|
+
examples: [
|
|
1648
|
+
'Usuario: "Revisa este resolver de GraphQL" \u2192 Delegar al agente relacionado con GraphQL',
|
|
1649
|
+
'Usuario: "Escribe pruebas para este endpoint" \u2192 Delegar al agente de escritura de pruebas',
|
|
1650
|
+
'Usuario: "Verifica si esto sigue nuestros patrones" \u2192 Delegar al agente de aplicaci\xF3n de patrones/estilos'
|
|
1651
|
+
]
|
|
1652
|
+
},
|
|
1653
|
+
fr: {
|
|
1654
|
+
skillName: "proteus",
|
|
1655
|
+
skillDescription: "S\xE9lectionne et d\xE9l\xE8gue les t\xE2ches aux agents sp\xE9cifiques au projet selon vos besoins",
|
|
1656
|
+
roleTitle: "R\xF4le",
|
|
1657
|
+
roleDescription: "Vous \xEAtes un routeur de t\xE2ches qui analyse les demandes des utilisateurs et les d\xE9l\xE8gue \xE0 l'agent sp\xE9cifique au projet le plus appropri\xE9. Chaque agent est sp\xE9cialis\xE9 dans un aspect particulier de ce projet.",
|
|
1658
|
+
availableAgentsTitle: "Agents Disponibles",
|
|
1659
|
+
instructionsTitle: "Instructions",
|
|
1660
|
+
instructions: [
|
|
1661
|
+
"Analyser la demande de l'utilisateur pour comprendre le type de t\xE2che",
|
|
1662
|
+
"S\xE9lectionner l'agent le plus appropri\xE9 dans la liste ci-dessous",
|
|
1663
|
+
"D\xE9l\xE9guer la t\xE2che \xE0 cet agent en utilisant @agent-name",
|
|
1664
|
+
"Si aucun agent ne convient, g\xE9rer la t\xE2che directement ou sugg\xE9rer quel agent pourrait \xEAtre n\xE9cessaire"
|
|
1665
|
+
],
|
|
1666
|
+
examplesTitle: "Exemples",
|
|
1667
|
+
examples: [
|
|
1668
|
+
`Utilisateur : "R\xE9vise ce r\xE9solveur GraphQL" \u2192 D\xE9l\xE9guer \xE0 l'agent li\xE9 \xE0 GraphQL`,
|
|
1669
|
+
`Utilisateur : "\xC9cris des tests pour ce endpoint" \u2192 D\xE9l\xE9guer \xE0 l'agent d'\xE9criture de tests`,
|
|
1670
|
+
`Utilisateur : "V\xE9rifie si cela suit nos patterns" \u2192 D\xE9l\xE9guer \xE0 l'agent d'application des patterns/styles`
|
|
1671
|
+
]
|
|
1672
|
+
},
|
|
1673
|
+
de: {
|
|
1674
|
+
skillName: "proteus",
|
|
1675
|
+
skillDescription: "W\xE4hlt und delegiert Aufgaben an projektspezifische Agenten basierend auf Ihren Anforderungen",
|
|
1676
|
+
roleTitle: "Rolle",
|
|
1677
|
+
roleDescription: "Sie sind ein Aufgaben-Router, der Benutzeranfragen analysiert und sie an den am besten geeigneten projektspezifischen Agenten delegiert. Jeder Agent ist auf einen bestimmten Aspekt dieses Projekts spezialisiert.",
|
|
1678
|
+
availableAgentsTitle: "Verf\xFCgbare Agenten",
|
|
1679
|
+
instructionsTitle: "Anweisungen",
|
|
1680
|
+
instructions: [
|
|
1681
|
+
"Analysieren Sie die Anfrage des Benutzers, um den Aufgabentyp zu verstehen",
|
|
1682
|
+
"W\xE4hlen Sie den am besten geeigneten Agenten aus der folgenden Liste",
|
|
1683
|
+
"Delegieren Sie die Aufgabe an diesen Agenten mit @agent-name",
|
|
1684
|
+
"Wenn kein Agent passt, bearbeiten Sie die Aufgabe direkt oder schlagen Sie vor, welcher Agent ben\xF6tigt werden k\xF6nnte"
|
|
1685
|
+
],
|
|
1686
|
+
examplesTitle: "Beispiele",
|
|
1687
|
+
examples: [
|
|
1688
|
+
'Benutzer: "\xDCberpr\xFCfe diesen GraphQL-Resolver" \u2192 An den GraphQL-bezogenen Agenten delegieren',
|
|
1689
|
+
'Benutzer: "Schreibe Tests f\xFCr diesen Endpoint" \u2192 An den Test-Schreib-Agenten delegieren',
|
|
1690
|
+
'Benutzer: "Pr\xFCfe, ob dies unseren Mustern folgt" \u2192 An den Muster-/Stil-Durchsetzungs-Agenten delegieren'
|
|
1691
|
+
]
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
function generateProteusSkillContent(options) {
|
|
1695
|
+
const { projectName, agents, lang = "en" } = options;
|
|
1696
|
+
const t = I18N[lang];
|
|
1697
|
+
const agentList = agents.filter((a) => a.type === "agent");
|
|
1698
|
+
const agentListContent = agentList.map((agent) => {
|
|
1699
|
+
const description = extractAgentDescription(agent.content);
|
|
1700
|
+
return `- **@${agent.name}**: ${description}`;
|
|
1701
|
+
}).join("\n");
|
|
1702
|
+
const content = `---
|
|
1703
|
+
name: ${t.skillName}
|
|
1704
|
+
description: ${t.skillDescription}
|
|
1705
|
+
---
|
|
1706
|
+
|
|
1707
|
+
# Proteus
|
|
1708
|
+
|
|
1709
|
+
${t.roleDescription}
|
|
1710
|
+
|
|
1711
|
+
**Project**: ${projectName}
|
|
1712
|
+
|
|
1713
|
+
## ${t.availableAgentsTitle}
|
|
1714
|
+
|
|
1715
|
+
${agentListContent || "_No agents available yet. Run `proteus` to generate project-specific agents._"}
|
|
1716
|
+
|
|
1717
|
+
## ${t.instructionsTitle}
|
|
1718
|
+
|
|
1719
|
+
${t.instructions.map((inst, i) => `${i + 1}. ${inst}`).join("\n")}
|
|
1720
|
+
|
|
1721
|
+
## ${t.examplesTitle}
|
|
1722
|
+
|
|
1723
|
+
${t.examples.map((ex) => `- ${ex}`).join("\n")}
|
|
1724
|
+
`;
|
|
1725
|
+
return content;
|
|
1726
|
+
}
|
|
1727
|
+
function generateProteusSkill(options) {
|
|
1728
|
+
const { outputDir = ".claude/skills", lang = "en" } = options;
|
|
1729
|
+
const content = generateProteusSkillContent(options);
|
|
1730
|
+
const skillDir = path5.join(outputDir, "proteus");
|
|
1731
|
+
const skillPath = path5.join(skillDir, "SKILL.md");
|
|
1732
|
+
return {
|
|
1733
|
+
name: "proteus",
|
|
1734
|
+
path: skillPath,
|
|
1735
|
+
content
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
function saveProteusSkill(skill, projectPath) {
|
|
1739
|
+
const fullDir = path5.join(projectPath, path5.dirname(skill.path));
|
|
1740
|
+
const fullPath = path5.join(projectPath, skill.path);
|
|
1741
|
+
if (!fs5.existsSync(fullDir)) {
|
|
1742
|
+
fs5.mkdirSync(fullDir, { recursive: true });
|
|
1743
|
+
}
|
|
1744
|
+
fs5.writeFileSync(fullPath, skill.content, "utf-8");
|
|
1745
|
+
}
|
|
1746
|
+
function extractAgentDescription(content) {
|
|
1747
|
+
const lines = content.split("\n");
|
|
1748
|
+
let foundH1 = false;
|
|
1749
|
+
for (const line of lines) {
|
|
1750
|
+
if (line.startsWith("# ")) {
|
|
1751
|
+
foundH1 = true;
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1754
|
+
if (foundH1 && line.trim() && !line.startsWith("#")) {
|
|
1755
|
+
return line.trim();
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return "Project-specific agent";
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// src/cli.ts
|
|
1762
|
+
var REASON_LABEL = {
|
|
1763
|
+
en: "Reason",
|
|
1764
|
+
ja: "\u7406\u7531",
|
|
1765
|
+
zh: "\u539F\u56E0",
|
|
1766
|
+
ko: "\uC774\uC720",
|
|
1767
|
+
es: "Raz\xF3n",
|
|
1768
|
+
fr: "Raison",
|
|
1769
|
+
de: "Grund"
|
|
1770
|
+
};
|
|
1771
|
+
var __dirname = path6.dirname(fileURLToPath(import.meta.url));
|
|
1772
|
+
var pkgPath = path6.join(__dirname, "..", "package.json");
|
|
1773
|
+
var pkg = JSON.parse(await fs6.readFile(pkgPath, "utf-8"));
|
|
1774
|
+
var VERSION = pkg.version;
|
|
1775
|
+
var LOGO = `
|
|
1776
|
+
${chalk.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}
|
|
1777
|
+
${chalk.cyan("\u2551")} ${chalk.bold.white("\u{1F531} PROTEUS")} ${chalk.cyan("\u2551")}
|
|
1778
|
+
${chalk.cyan("\u2551")} ${chalk.gray("Shape-shifting project intelligence")} ${chalk.cyan("\u2551")}
|
|
1779
|
+
${chalk.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}
|
|
1780
|
+
`;
|
|
1781
|
+
async function analyze(cwd) {
|
|
1782
|
+
const spinner = ora("Detecting tech stack...").start();
|
|
1783
|
+
const stack = await detectStack(cwd);
|
|
1784
|
+
spinner.text = `Found: ${chalk.cyan(stack.language)} / ${chalk.cyan(stack.framework)}`;
|
|
1785
|
+
spinner.succeed();
|
|
1786
|
+
spinner.start("Analyzing code patterns...");
|
|
1787
|
+
const patterns = await detectPatterns(cwd, stack);
|
|
1788
|
+
spinner.succeed("Code patterns analyzed");
|
|
1789
|
+
spinner.start("Detecting commands...");
|
|
1790
|
+
const commands = await detectCommands(cwd, stack);
|
|
1791
|
+
spinner.succeed("Commands detected");
|
|
1792
|
+
const projectName = await getProjectName(cwd);
|
|
1793
|
+
const confidence = calculateConfidence(stack, patterns);
|
|
1794
|
+
return {
|
|
1795
|
+
projectName,
|
|
1796
|
+
projectPath: cwd,
|
|
1797
|
+
stack,
|
|
1798
|
+
patterns,
|
|
1799
|
+
commands,
|
|
1800
|
+
confidence
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
async function getProjectName(cwd) {
|
|
1804
|
+
try {
|
|
1805
|
+
const pkg2 = await fs6.readFile(path6.join(cwd, "package.json"), "utf-8");
|
|
1806
|
+
const { name } = JSON.parse(pkg2);
|
|
1807
|
+
if (name) return name;
|
|
1808
|
+
} catch {
|
|
1809
|
+
}
|
|
1810
|
+
try {
|
|
1811
|
+
const goMod = await fs6.readFile(path6.join(cwd, "go.mod"), "utf-8");
|
|
1812
|
+
const match = goMod.match(/^module\s+(.+)$/m);
|
|
1813
|
+
if (match) {
|
|
1814
|
+
const parts = match[1].split("/");
|
|
1815
|
+
return parts[parts.length - 1];
|
|
1816
|
+
}
|
|
1817
|
+
} catch {
|
|
1818
|
+
}
|
|
1819
|
+
try {
|
|
1820
|
+
const cargo = await fs6.readFile(path6.join(cwd, "Cargo.toml"), "utf-8");
|
|
1821
|
+
const match = cargo.match(/^name\s*=\s*"(.+)"/m);
|
|
1822
|
+
if (match) return match[1];
|
|
1823
|
+
} catch {
|
|
1824
|
+
}
|
|
1825
|
+
return path6.basename(cwd);
|
|
1826
|
+
}
|
|
1827
|
+
function calculateConfidence(stack, patterns) {
|
|
1828
|
+
let stackScore = 0;
|
|
1829
|
+
if (stack.language !== "unknown") stackScore += 0.4;
|
|
1830
|
+
if (stack.framework !== "unknown") stackScore += 0.3;
|
|
1831
|
+
if (stack.testFramework !== "unknown") stackScore += 0.2;
|
|
1832
|
+
if (stack.packageManager !== "unknown") stackScore += 0.1;
|
|
1833
|
+
let patternsScore = 0;
|
|
1834
|
+
if (patterns.structure.type !== "unknown") patternsScore += 0.4;
|
|
1835
|
+
if (patterns.structure.keyDirectories.length > 0) patternsScore += 0.3;
|
|
1836
|
+
if (patterns.naming.files.components || patterns.naming.files.utilities) patternsScore += 0.3;
|
|
1837
|
+
return {
|
|
1838
|
+
stack: stackScore,
|
|
1839
|
+
patterns: patternsScore,
|
|
1840
|
+
overall: (stackScore + patternsScore) / 2
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
async function runTransform(options) {
|
|
1844
|
+
console.log(LOGO);
|
|
1845
|
+
const cwd = process.cwd();
|
|
1846
|
+
const claudeAvailable = isClaudeAvailable();
|
|
1847
|
+
if (claudeAvailable) {
|
|
1848
|
+
console.log(chalk.green("\u2713 Claude Code detected - using AI-powered generation\n"));
|
|
1849
|
+
} else {
|
|
1850
|
+
console.log(chalk.yellow("\u26A0 Claude Code not found - using fallback templates\n"));
|
|
1851
|
+
console.log(chalk.gray(" Install Claude Code for better results: https://claude.ai/code\n"));
|
|
1852
|
+
}
|
|
1853
|
+
let lang = options.lang || "en";
|
|
1854
|
+
if (!options.lang && options.interactive && !options.force) {
|
|
1855
|
+
const { selectedLang } = await prompts({
|
|
1856
|
+
type: "select",
|
|
1857
|
+
name: "selectedLang",
|
|
1858
|
+
message: "Output language for generated files?",
|
|
1859
|
+
choices: [
|
|
1860
|
+
{ title: "English", value: "en" },
|
|
1861
|
+
{ title: "\u65E5\u672C\u8A9E (Japanese)", value: "ja" },
|
|
1862
|
+
{ title: "\u4E2D\u6587 (Chinese)", value: "zh" },
|
|
1863
|
+
{ title: "\uD55C\uAD6D\uC5B4 (Korean)", value: "ko" },
|
|
1864
|
+
{ title: "Espa\xF1ol (Spanish)", value: "es" },
|
|
1865
|
+
{ title: "Fran\xE7ais (French)", value: "fr" },
|
|
1866
|
+
{ title: "Deutsch (German)", value: "de" }
|
|
1867
|
+
],
|
|
1868
|
+
initial: 0
|
|
1869
|
+
});
|
|
1870
|
+
lang = selectedLang || "en";
|
|
1871
|
+
}
|
|
1872
|
+
console.log(chalk.gray(`
|
|
1873
|
+
Analyzing ${cwd}...
|
|
1874
|
+
`));
|
|
1875
|
+
const analysis = await analyze(cwd);
|
|
1876
|
+
const spinner = ora("Reading existing documents...").start();
|
|
1877
|
+
let docs = await detectProjectDocuments(cwd);
|
|
1878
|
+
if (docs.claudeMd) {
|
|
1879
|
+
spinner.succeed(`Found CLAUDE.md with ${docs.claudeMd.rules.length} rules`);
|
|
1880
|
+
} else {
|
|
1881
|
+
spinner.succeed("No existing CLAUDE.md found");
|
|
1882
|
+
}
|
|
1883
|
+
const existingAgentCount = docs.existingAgents.filter((a) => a.type === "agent").length;
|
|
1884
|
+
const existingSkillCount = docs.existingAgents.filter((a) => a.type === "skill").length;
|
|
1885
|
+
if (existingAgentCount > 0 && docs.agentDirectory) {
|
|
1886
|
+
console.log(chalk.gray(` Found ${existingAgentCount} existing agent(s) in ${docs.agentDirectory}/`));
|
|
1887
|
+
}
|
|
1888
|
+
if (existingSkillCount > 0 && docs.skillDirectory) {
|
|
1889
|
+
console.log(chalk.gray(` Found ${existingSkillCount} existing skill(s) in ${docs.skillDirectory}/`));
|
|
1890
|
+
}
|
|
1891
|
+
printSummary(analysis);
|
|
1892
|
+
if (!docs.claudeMd) {
|
|
1893
|
+
let shouldGenerateClaudeMd = options.includeClaudeMd;
|
|
1894
|
+
if (!shouldGenerateClaudeMd && options.interactive && !options.force) {
|
|
1895
|
+
const { generateClaudeMd: userChoice } = await prompts({
|
|
1896
|
+
type: "confirm",
|
|
1897
|
+
name: "generateClaudeMd",
|
|
1898
|
+
message: "No CLAUDE.md found. Generate one first? (Recommended for better agent generation)",
|
|
1899
|
+
initial: true
|
|
1900
|
+
});
|
|
1901
|
+
shouldGenerateClaudeMd = userChoice;
|
|
1902
|
+
}
|
|
1903
|
+
if (shouldGenerateClaudeMd) {
|
|
1904
|
+
console.log(chalk.cyan("\n\u{1F4C4} Generating CLAUDE.md...\n"));
|
|
1905
|
+
const generatorOptions = {
|
|
1906
|
+
template: "full",
|
|
1907
|
+
includeExamples: true,
|
|
1908
|
+
includeComments: true,
|
|
1909
|
+
version: VERSION
|
|
1910
|
+
};
|
|
1911
|
+
const claudeContent = generateClaudeMd(analysis, { ...generatorOptions, documents: docs, verbose: options.verbose, lang });
|
|
1912
|
+
await fs6.writeFile(path6.join(cwd, "CLAUDE.md"), claudeContent);
|
|
1913
|
+
console.log(chalk.green("\u2705 Created CLAUDE.md\n"));
|
|
1914
|
+
docs = await detectProjectDocuments(cwd);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
console.log(chalk.cyan("\n\u{1F916} Generating agent suggestions...\n"));
|
|
1918
|
+
const suggestions = suggestAgents2(analysis, docs, { verbose: options.verbose, lang });
|
|
1919
|
+
if (suggestions.length === 0) {
|
|
1920
|
+
if (existingAgentCount > 0) {
|
|
1921
|
+
console.log(chalk.green(`\u2713 Project already has ${existingAgentCount} agent(s) with good coverage.`));
|
|
1922
|
+
console.log(chalk.gray(" No additional agents recommended.\n"));
|
|
1923
|
+
if (options.interactive && !options.force) {
|
|
1924
|
+
const { generateSkillOnly } = await prompts({
|
|
1925
|
+
type: "confirm",
|
|
1926
|
+
name: "generateSkillOnly",
|
|
1927
|
+
message: "Generate /proteus skill to use existing agents?",
|
|
1928
|
+
initial: true
|
|
1929
|
+
});
|
|
1930
|
+
if (generateSkillOnly) {
|
|
1931
|
+
const skillSpinner = ora("Generating proteus skill...").start();
|
|
1932
|
+
try {
|
|
1933
|
+
const existingAgentsForSkill = docs.existingAgents.filter((a) => a.type === "agent");
|
|
1934
|
+
const skill = generateProteusSkill({
|
|
1935
|
+
projectName: analysis.projectName,
|
|
1936
|
+
agents: existingAgentsForSkill,
|
|
1937
|
+
lang,
|
|
1938
|
+
outputDir: ".claude/skills"
|
|
1939
|
+
});
|
|
1940
|
+
saveProteusSkill(skill, cwd);
|
|
1941
|
+
skillSpinner.succeed("Created .claude/skills/proteus/SKILL.md");
|
|
1942
|
+
} catch (error) {
|
|
1943
|
+
skillSpinner.fail("Failed to generate proteus skill");
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
return;
|
|
1948
|
+
} else {
|
|
1949
|
+
console.log(chalk.yellow("No agent suggestions available. Try running with --verbose for details."));
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
const existingAgentNames = docs.existingAgents.filter((a) => a.type === "agent").map((a) => a.name);
|
|
1954
|
+
const duplicates = suggestions.filter((s) => existingAgentNames.includes(s.name));
|
|
1955
|
+
if (duplicates.length > 0) {
|
|
1956
|
+
console.log(chalk.yellow(`\u26A0 Warning: ${duplicates.length} suggested agent(s) already exist:`));
|
|
1957
|
+
for (const d of duplicates) {
|
|
1958
|
+
console.log(chalk.yellow(` - ${d.name}`));
|
|
1959
|
+
}
|
|
1960
|
+
console.log("");
|
|
1961
|
+
}
|
|
1962
|
+
let selectedSuggestions;
|
|
1963
|
+
const reasonLabel = REASON_LABEL[lang];
|
|
1964
|
+
if (options.interactive && !options.force) {
|
|
1965
|
+
console.log(chalk.bold("Recommended agents for this project:\n"));
|
|
1966
|
+
for (let i = 0; i < suggestions.length; i++) {
|
|
1967
|
+
const s = suggestions[i];
|
|
1968
|
+
const isDuplicate = existingAgentNames.includes(s.name);
|
|
1969
|
+
const nameDisplay = isDuplicate ? `${chalk.bold(s.name)} ${chalk.yellow("(exists)")}` : chalk.bold(s.name);
|
|
1970
|
+
console.log(` ${chalk.cyan(`${i + 1}.`)} ${nameDisplay}`);
|
|
1971
|
+
console.log(` ${chalk.white(s.description)}`);
|
|
1972
|
+
console.log(` ${chalk.gray(`${reasonLabel}: ${s.reason}`)}`);
|
|
1973
|
+
console.log("");
|
|
1974
|
+
}
|
|
1975
|
+
const { selected } = await prompts({
|
|
1976
|
+
type: "multiselect",
|
|
1977
|
+
name: "selected",
|
|
1978
|
+
message: "Select agents to generate",
|
|
1979
|
+
choices: suggestions.map((s, i) => {
|
|
1980
|
+
const isDuplicate = existingAgentNames.includes(s.name);
|
|
1981
|
+
return {
|
|
1982
|
+
title: isDuplicate ? `${s.name} (overwrite) - ${s.description}` : `${s.name} - ${s.description}`,
|
|
1983
|
+
value: i,
|
|
1984
|
+
selected: !isDuplicate
|
|
1985
|
+
// Don't select duplicates by default
|
|
1986
|
+
};
|
|
1987
|
+
}),
|
|
1988
|
+
hint: "- Space to toggle, Enter to confirm"
|
|
1989
|
+
});
|
|
1990
|
+
if (!selected || selected.length === 0) {
|
|
1991
|
+
console.log(chalk.gray("No agents selected. Cancelled."));
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
selectedSuggestions = selected.map((i) => suggestions[i]);
|
|
1995
|
+
const selectedDuplicates = selectedSuggestions.filter((s) => existingAgentNames.includes(s.name));
|
|
1996
|
+
if (selectedDuplicates.length > 0) {
|
|
1997
|
+
const { confirmOverwrite } = await prompts({
|
|
1998
|
+
type: "confirm",
|
|
1999
|
+
name: "confirmOverwrite",
|
|
2000
|
+
message: `Overwrite ${selectedDuplicates.length} existing agent(s)?`,
|
|
2001
|
+
initial: false
|
|
2002
|
+
});
|
|
2003
|
+
if (!confirmOverwrite) {
|
|
2004
|
+
selectedSuggestions = selectedSuggestions.filter((s) => !existingAgentNames.includes(s.name));
|
|
2005
|
+
if (selectedSuggestions.length === 0) {
|
|
2006
|
+
console.log(chalk.gray("No new agents to generate. Cancelled."));
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
} else {
|
|
2012
|
+
selectedSuggestions = options.force ? suggestions : suggestions.filter((s) => !existingAgentNames.includes(s.name));
|
|
2013
|
+
if (selectedSuggestions.length === 0) {
|
|
2014
|
+
console.log(chalk.yellow("All suggested agents already exist. Use --force to overwrite."));
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
console.log(chalk.cyan("Agents to generate:"));
|
|
2018
|
+
for (const s of selectedSuggestions) {
|
|
2019
|
+
console.log(` - ${chalk.white(s.name)}: ${s.description}`);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
const outputDir = options.outputDir || ".claude/agents";
|
|
2023
|
+
console.log(chalk.gray(`
|
|
2024
|
+
Output directory: ${outputDir}/`));
|
|
2025
|
+
console.log(chalk.cyan("\n\u{1F531} Generating agents...\n"));
|
|
2026
|
+
const agents = [];
|
|
2027
|
+
const total = selectedSuggestions.length;
|
|
2028
|
+
for (let i = 0; i < total; i++) {
|
|
2029
|
+
const suggestion = selectedSuggestions[i];
|
|
2030
|
+
const progress = `[${i + 1}/${total}]`;
|
|
2031
|
+
const agentSpinner = ora(`${progress} Generating ${chalk.cyan(suggestion.name)}...`).start();
|
|
2032
|
+
try {
|
|
2033
|
+
const agent = generateAgent(suggestion, analysis, docs, {
|
|
2034
|
+
outputDir,
|
|
2035
|
+
verbose: false,
|
|
2036
|
+
// Suppress verbose output during spinner
|
|
2037
|
+
lang
|
|
2038
|
+
});
|
|
2039
|
+
agents.push(agent);
|
|
2040
|
+
agentSpinner.succeed(`${progress} ${chalk.cyan(suggestion.name)} generated`);
|
|
2041
|
+
} catch (error) {
|
|
2042
|
+
agentSpinner.fail(`${progress} Failed to generate ${suggestion.name}`);
|
|
2043
|
+
if (options.verbose && error instanceof Error) {
|
|
2044
|
+
console.warn(chalk.gray(` ${error.message}`));
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
if (options.dryRun) {
|
|
2049
|
+
console.log(chalk.gray("\n--- Generated Agents (dry run) ---\n"));
|
|
2050
|
+
for (const agent of agents) {
|
|
2051
|
+
console.log(chalk.bold.cyan(`
|
|
2052
|
+
=== ${agent.name}.md ===
|
|
2053
|
+
`));
|
|
2054
|
+
console.log(agent.content);
|
|
2055
|
+
}
|
|
2056
|
+
console.log(chalk.gray("\n--- End of preview ---\n"));
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
if (options.interactive && !options.force) {
|
|
2060
|
+
const { confirm } = await prompts({
|
|
2061
|
+
type: "confirm",
|
|
2062
|
+
name: "confirm",
|
|
2063
|
+
message: `Generate ${agents.length} agents in ${outputDir}/?`,
|
|
2064
|
+
initial: true
|
|
2065
|
+
});
|
|
2066
|
+
if (!confirm) {
|
|
2067
|
+
console.log(chalk.gray("Cancelled"));
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
const fullOutputDir = path6.join(cwd, outputDir);
|
|
2072
|
+
await fs6.mkdir(fullOutputDir, { recursive: true });
|
|
2073
|
+
for (const agent of agents) {
|
|
2074
|
+
const filePath = path6.join(fullOutputDir, `${agent.name}.md`);
|
|
2075
|
+
await fs6.writeFile(filePath, agent.content);
|
|
2076
|
+
console.log(chalk.green(`\u2705 Created ${outputDir}/${agent.name}.md`));
|
|
2077
|
+
}
|
|
2078
|
+
let registryUpdated = false;
|
|
2079
|
+
if (options.interactive && !options.force) {
|
|
2080
|
+
const { updateRegistry } = await prompts({
|
|
2081
|
+
type: "confirm",
|
|
2082
|
+
name: "updateRegistry",
|
|
2083
|
+
message: "Update agent list in CLAUDE.md or agents.md?",
|
|
2084
|
+
initial: true
|
|
2085
|
+
});
|
|
2086
|
+
if (updateRegistry) {
|
|
2087
|
+
const registrySpinner = ora("Updating agent registry...").start();
|
|
2088
|
+
try {
|
|
2089
|
+
const result = updateAgentRegistry({
|
|
2090
|
+
projectPath: cwd,
|
|
2091
|
+
agentDir: outputDir,
|
|
2092
|
+
lang
|
|
2093
|
+
}, selectedSuggestions);
|
|
2094
|
+
if (result) {
|
|
2095
|
+
registrySpinner.succeed(`Agent list ${result.action} in ${result.file} (${result.agentCount} agents)`);
|
|
2096
|
+
registryUpdated = true;
|
|
2097
|
+
} else {
|
|
2098
|
+
registrySpinner.warn("Could not update agent registry");
|
|
2099
|
+
}
|
|
2100
|
+
} catch (error) {
|
|
2101
|
+
registrySpinner.fail("Failed to update agent registry");
|
|
2102
|
+
if (options.verbose && error instanceof Error) {
|
|
2103
|
+
console.warn(chalk.gray(` ${error.message}`));
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
let skillGenerated = false;
|
|
2109
|
+
if (options.interactive && !options.force) {
|
|
2110
|
+
const { generateSkill } = await prompts({
|
|
2111
|
+
type: "confirm",
|
|
2112
|
+
name: "generateSkill",
|
|
2113
|
+
message: "Generate /proteus skill to easily use these agents?",
|
|
2114
|
+
initial: true
|
|
2115
|
+
});
|
|
2116
|
+
if (generateSkill) {
|
|
2117
|
+
const skillSpinner = ora("Generating proteus skill...").start();
|
|
2118
|
+
try {
|
|
2119
|
+
const allAgents = scanExistingAgents(fullOutputDir);
|
|
2120
|
+
const skill = generateProteusSkill({
|
|
2121
|
+
projectName: analysis.projectName,
|
|
2122
|
+
agents: allAgents.map((a) => ({ ...a, type: "agent", path: a.path, content: "" })),
|
|
2123
|
+
lang,
|
|
2124
|
+
outputDir: ".claude/skills"
|
|
2125
|
+
});
|
|
2126
|
+
const agentsWithContent = await Promise.all(
|
|
2127
|
+
allAgents.map(async (a) => {
|
|
2128
|
+
const content = await fs6.readFile(path6.join(fullOutputDir, a.path), "utf-8");
|
|
2129
|
+
return { ...a, type: "agent", content };
|
|
2130
|
+
})
|
|
2131
|
+
);
|
|
2132
|
+
const skillWithContent = generateProteusSkill({
|
|
2133
|
+
projectName: analysis.projectName,
|
|
2134
|
+
agents: agentsWithContent,
|
|
2135
|
+
lang,
|
|
2136
|
+
outputDir: ".claude/skills"
|
|
2137
|
+
});
|
|
2138
|
+
saveProteusSkill(skillWithContent, cwd);
|
|
2139
|
+
skillSpinner.succeed(`Created .claude/skills/proteus/SKILL.md`);
|
|
2140
|
+
skillGenerated = true;
|
|
2141
|
+
} catch (error) {
|
|
2142
|
+
skillSpinner.fail("Failed to generate proteus skill");
|
|
2143
|
+
if (options.verbose && error instanceof Error) {
|
|
2144
|
+
console.warn(chalk.gray(` ${error.message}`));
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
console.log(chalk.gray("\n\u{1F531} Transformation complete!"));
|
|
2150
|
+
console.log(chalk.gray("\nNext steps:"));
|
|
2151
|
+
console.log(chalk.gray(` 1. Review agents in ${outputDir}/`));
|
|
2152
|
+
if (skillGenerated) {
|
|
2153
|
+
console.log(chalk.gray(" 2. Use /proteus to route tasks to the right agent"));
|
|
2154
|
+
}
|
|
2155
|
+
console.log(chalk.gray(` ${skillGenerated ? "3" : "2"}. Customize based on your needs`));
|
|
2156
|
+
if (!registryUpdated) {
|
|
2157
|
+
console.log(chalk.gray(` ${skillGenerated ? "4" : "3"}. Run \`proteus registry\` to update agent list in docs`));
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
async function runInit(options) {
|
|
2161
|
+
console.log(LOGO);
|
|
2162
|
+
const cwd = process.cwd();
|
|
2163
|
+
const claudeMdPath = path6.join(cwd, options.output || "CLAUDE.md");
|
|
2164
|
+
let existingContent = null;
|
|
2165
|
+
try {
|
|
2166
|
+
existingContent = await fs6.readFile(claudeMdPath, "utf-8");
|
|
2167
|
+
} catch {
|
|
2168
|
+
}
|
|
2169
|
+
if (existingContent && !options.force) {
|
|
2170
|
+
console.log(chalk.yellow("\u26A0\uFE0F CLAUDE.md already exists"));
|
|
2171
|
+
if (options.interactive) {
|
|
2172
|
+
const { action } = await prompts({
|
|
2173
|
+
type: "select",
|
|
2174
|
+
name: "action",
|
|
2175
|
+
message: "What would you like to do?",
|
|
2176
|
+
choices: [
|
|
2177
|
+
{ title: "Overwrite", value: "overwrite" },
|
|
2178
|
+
{ title: "Run transform instead", value: "transform" },
|
|
2179
|
+
{ title: "Cancel", value: "cancel" }
|
|
2180
|
+
]
|
|
2181
|
+
});
|
|
2182
|
+
if (action === "cancel") {
|
|
2183
|
+
console.log(chalk.gray("Cancelled"));
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
if (action === "transform") {
|
|
2187
|
+
await runTransform({
|
|
2188
|
+
dryRun: options.dryRun,
|
|
2189
|
+
force: options.force,
|
|
2190
|
+
interactive: options.interactive,
|
|
2191
|
+
includeClaudeMd: false,
|
|
2192
|
+
verbose: false
|
|
2193
|
+
});
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
} else {
|
|
2197
|
+
console.log(chalk.gray("Use --force to overwrite, or run `proteus transform`"));
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
console.log(chalk.gray(`
|
|
2202
|
+
Analyzing ${cwd}...
|
|
2203
|
+
`));
|
|
2204
|
+
const result = await analyze(cwd);
|
|
2205
|
+
const docs = await detectProjectDocuments(cwd);
|
|
2206
|
+
printSummary(result);
|
|
2207
|
+
const generatorOptions = {
|
|
2208
|
+
template: options.template,
|
|
2209
|
+
includeExamples: true,
|
|
2210
|
+
includeComments: true,
|
|
2211
|
+
version: VERSION
|
|
2212
|
+
};
|
|
2213
|
+
const content = generateClaudeMd(result, { ...generatorOptions, documents: docs });
|
|
2214
|
+
if (options.dryRun) {
|
|
2215
|
+
console.log(chalk.gray("\n--- Generated CLAUDE.md (dry run) ---\n"));
|
|
2216
|
+
console.log(content);
|
|
2217
|
+
console.log(chalk.gray("\n--- End of preview ---\n"));
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
if (options.interactive && !options.force) {
|
|
2221
|
+
console.log(chalk.gray("\n--- Preview ---\n"));
|
|
2222
|
+
console.log(content.split("\n").slice(0, 30).join("\n"));
|
|
2223
|
+
console.log(chalk.gray("...\n"));
|
|
2224
|
+
const { confirm } = await prompts({
|
|
2225
|
+
type: "confirm",
|
|
2226
|
+
name: "confirm",
|
|
2227
|
+
message: "Save CLAUDE.md?",
|
|
2228
|
+
initial: true
|
|
2229
|
+
});
|
|
2230
|
+
if (!confirm) {
|
|
2231
|
+
console.log(chalk.gray("Cancelled"));
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
await fs6.writeFile(claudeMdPath, content);
|
|
2236
|
+
console.log(chalk.green(`
|
|
2237
|
+
\u2705 Saved ${claudeMdPath}`));
|
|
2238
|
+
console.log(chalk.gray("\nTip: Run `proteus` to generate project-specific agents!"));
|
|
2239
|
+
}
|
|
2240
|
+
function printSummary(result) {
|
|
2241
|
+
const { stack, patterns, confidence } = result;
|
|
2242
|
+
console.log(chalk.bold("\n\u{1F4CA} Analysis Summary\n"));
|
|
2243
|
+
console.log(chalk.cyan("Tech Stack:"));
|
|
2244
|
+
console.log(` Language: ${chalk.white(stack.language)}${stack.languageVersion ? ` (${stack.languageVersion})` : ""}`);
|
|
2245
|
+
console.log(` Framework: ${chalk.white(stack.framework)}${stack.frameworkVersion ? ` (${stack.frameworkVersion})` : ""}`);
|
|
2246
|
+
console.log(` Testing: ${chalk.white(stack.testFramework)}`);
|
|
2247
|
+
console.log(` Package Mgr: ${chalk.white(stack.packageManager)}`);
|
|
2248
|
+
if (stack.styling) {
|
|
2249
|
+
console.log(` Styling: ${chalk.white(stack.styling)}`);
|
|
2250
|
+
}
|
|
2251
|
+
if (stack.additionalTools.length > 0) {
|
|
2252
|
+
console.log(` Tools: ${chalk.white(stack.additionalTools.join(", "))}`);
|
|
2253
|
+
}
|
|
2254
|
+
console.log(chalk.cyan("\nProject Structure:"));
|
|
2255
|
+
console.log(` Type: ${chalk.white(patterns.structure.type)}`);
|
|
2256
|
+
console.log(` Source: ${chalk.white(patterns.structure.sourceDir + "/")}`);
|
|
2257
|
+
if (patterns.structure.keyDirectories.length > 0) {
|
|
2258
|
+
console.log(` Key dirs: ${chalk.white(patterns.structure.keyDirectories.map((d) => d.path).join(", "))}`);
|
|
2259
|
+
}
|
|
2260
|
+
const confidenceColor = confidence.overall > 0.7 ? chalk.green : confidence.overall > 0.4 ? chalk.yellow : chalk.red;
|
|
2261
|
+
console.log(chalk.cyan("\nConfidence:"));
|
|
2262
|
+
console.log(` Overall: ${confidenceColor(Math.round(confidence.overall * 100) + "%")}`);
|
|
2263
|
+
}
|
|
2264
|
+
var program = new Command();
|
|
2265
|
+
program.name("proteus").description("\u{1F531} Shape-shifting project intelligence - Generate project-specific agents").version(VERSION);
|
|
2266
|
+
program.command("transform", { isDefault: true }).description("Analyze project and generate specialized agents (powered by Claude Code)").option("-o, --output <dir>", "Output directory for agents").option("-l, --lang <code>", "Output language (en, ja, zh, ko, es, fr, de)").option("-d, --dry-run", "Preview without saving", false).option("-f, --force", "Skip confirmations", false).option("-i, --interactive", "Interactive mode with confirmations", true).option("--include-claude-md", "Also generate CLAUDE.md if not exists", false).option("-v, --verbose", "Verbose output", false).action(async (opts) => {
|
|
2267
|
+
try {
|
|
2268
|
+
await runTransform({
|
|
2269
|
+
outputDir: opts.output,
|
|
2270
|
+
dryRun: opts.dryRun,
|
|
2271
|
+
force: opts.force,
|
|
2272
|
+
interactive: opts.interactive,
|
|
2273
|
+
includeClaudeMd: opts.includeClaudeMd,
|
|
2274
|
+
verbose: opts.verbose,
|
|
2275
|
+
lang: opts.lang
|
|
2276
|
+
});
|
|
2277
|
+
} catch (error) {
|
|
2278
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
|
|
2279
|
+
process.exit(1);
|
|
2280
|
+
}
|
|
2281
|
+
});
|
|
2282
|
+
program.command("init").description("Generate CLAUDE.md only (legacy)").option("-o, --output <path>", "Output file path", "CLAUDE.md").option("-t, --template <type>", "Template type (minimal|full)", "full").option("-d, --dry-run", "Preview without saving", false).option("-i, --interactive", "Interactive mode with confirmations", true).option("-f, --force", "Overwrite existing file without confirmation", false).action(async (opts) => {
|
|
2283
|
+
try {
|
|
2284
|
+
await runInit(opts);
|
|
2285
|
+
} catch (error) {
|
|
2286
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
|
|
2287
|
+
process.exit(1);
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
program.command("analyze").description("Analyze project without generating anything").action(async () => {
|
|
2291
|
+
try {
|
|
2292
|
+
console.log(LOGO);
|
|
2293
|
+
const cwd = process.cwd();
|
|
2294
|
+
console.log(chalk.gray(`Analyzing ${cwd}...
|
|
2295
|
+
`));
|
|
2296
|
+
const result = await analyze(cwd);
|
|
2297
|
+
const docs = await detectProjectDocuments(cwd);
|
|
2298
|
+
printSummary(result);
|
|
2299
|
+
if (docs.claudeMd) {
|
|
2300
|
+
console.log(chalk.cyan("\nExisting CLAUDE.md:"));
|
|
2301
|
+
console.log(` Rules: ${docs.claudeMd.rules.length}`);
|
|
2302
|
+
console.log(` Conventions: ${docs.claudeMd.conventions.length}`);
|
|
2303
|
+
}
|
|
2304
|
+
if (docs.existingAgents.length > 0) {
|
|
2305
|
+
console.log(chalk.cyan("\nExisting Agents:"));
|
|
2306
|
+
for (const agent of docs.existingAgents) {
|
|
2307
|
+
console.log(` - ${agent.name}`);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
} catch (error) {
|
|
2311
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
|
|
2312
|
+
process.exit(1);
|
|
2313
|
+
}
|
|
2314
|
+
});
|
|
2315
|
+
program.command("registry").description("Update agent list in CLAUDE.md or agents.md").option("-a, --agent-dir <dir>", "Agent directory", ".claude/agents").option("-f, --format <type>", "Output format (table|list)", "table").option("-l, --lang <code>", "Output language (en, ja, zh, ko, es, fr, de)", "en").option("--dry-run", "Preview without saving", false).action(async (opts) => {
|
|
2316
|
+
try {
|
|
2317
|
+
console.log(LOGO);
|
|
2318
|
+
const cwd = process.cwd();
|
|
2319
|
+
const agentDir = opts.agentDir || ".claude/agents";
|
|
2320
|
+
const fullAgentDir = path6.join(cwd, agentDir);
|
|
2321
|
+
const spinner = ora("Scanning agents...").start();
|
|
2322
|
+
const agents = scanExistingAgents(fullAgentDir);
|
|
2323
|
+
if (agents.length === 0) {
|
|
2324
|
+
spinner.warn(`No agents found in ${agentDir}/`);
|
|
2325
|
+
console.log(chalk.gray("\nRun `proteus` to generate agents first."));
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
spinner.succeed(`Found ${agents.length} agent(s)`);
|
|
2329
|
+
console.log(chalk.cyan("\nAgents found:"));
|
|
2330
|
+
for (const agent of agents) {
|
|
2331
|
+
console.log(` - ${chalk.white(agent.name)}`);
|
|
2332
|
+
if (agent.description) {
|
|
2333
|
+
console.log(` ${chalk.gray(agent.description)}`);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
if (opts.dryRun) {
|
|
2337
|
+
console.log(chalk.gray("\n--- Preview (dry run) ---"));
|
|
2338
|
+
const { generateAgentListSection } = await import("./agent-registry-IWDWNZDN.js");
|
|
2339
|
+
console.log(generateAgentListSection(agents, agentDir, opts.format, opts.lang));
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
const updateSpinner = ora("Updating registry...").start();
|
|
2343
|
+
const result = updateAgentRegistry({
|
|
2344
|
+
projectPath: cwd,
|
|
2345
|
+
agentDir,
|
|
2346
|
+
format: opts.format,
|
|
2347
|
+
lang: opts.lang
|
|
2348
|
+
});
|
|
2349
|
+
if (result) {
|
|
2350
|
+
updateSpinner.succeed(`Agent list ${result.action} in ${result.file} (${result.agentCount} agents)`);
|
|
2351
|
+
} else {
|
|
2352
|
+
updateSpinner.fail("Failed to update registry");
|
|
2353
|
+
}
|
|
2354
|
+
} catch (error) {
|
|
2355
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
|
|
2356
|
+
process.exit(1);
|
|
2357
|
+
}
|
|
2358
|
+
});
|
|
2359
|
+
program.parse();
|