claude-cli-advanced-starter-pack 1.0.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/OVERVIEW.md +597 -0
- package/README.md +439 -0
- package/bin/gtask.js +282 -0
- package/bin/postinstall.js +53 -0
- package/package.json +69 -0
- package/src/agents/phase-dev-templates.js +1011 -0
- package/src/agents/templates.js +668 -0
- package/src/analysis/checklist-parser.js +414 -0
- package/src/analysis/codebase.js +481 -0
- package/src/cli/menu.js +958 -0
- package/src/commands/claude-audit.js +1482 -0
- package/src/commands/claude-settings.js +2243 -0
- package/src/commands/create-agent.js +681 -0
- package/src/commands/create-command.js +337 -0
- package/src/commands/create-hook.js +262 -0
- package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
- package/src/commands/create-phase-dev/documentation-generator.js +352 -0
- package/src/commands/create-phase-dev/post-completion.js +404 -0
- package/src/commands/create-phase-dev/scale-calculator.js +344 -0
- package/src/commands/create-phase-dev/wizard.js +492 -0
- package/src/commands/create-phase-dev.js +481 -0
- package/src/commands/create-skill.js +313 -0
- package/src/commands/create.js +446 -0
- package/src/commands/decompose.js +392 -0
- package/src/commands/detect-tech-stack.js +768 -0
- package/src/commands/explore-mcp/claude-md-updater.js +252 -0
- package/src/commands/explore-mcp/mcp-installer.js +346 -0
- package/src/commands/explore-mcp/mcp-registry.js +438 -0
- package/src/commands/explore-mcp.js +638 -0
- package/src/commands/gtask-init.js +641 -0
- package/src/commands/help.js +128 -0
- package/src/commands/init.js +1890 -0
- package/src/commands/install.js +250 -0
- package/src/commands/list.js +116 -0
- package/src/commands/roadmap.js +750 -0
- package/src/commands/setup-wizard.js +482 -0
- package/src/commands/setup.js +351 -0
- package/src/commands/sync.js +534 -0
- package/src/commands/test-run.js +456 -0
- package/src/commands/test-setup.js +456 -0
- package/src/commands/validate.js +67 -0
- package/src/config/tech-stack.defaults.json +182 -0
- package/src/config/tech-stack.schema.json +502 -0
- package/src/github/client.js +359 -0
- package/src/index.js +84 -0
- package/src/templates/claude-command.js +244 -0
- package/src/templates/issue-body.js +284 -0
- package/src/testing/config.js +411 -0
- package/src/utils/template-engine.js +398 -0
- package/src/utils/validate-templates.js +223 -0
- package/src/utils.js +396 -0
- package/templates/commands/ccasp-setup.template.md +113 -0
- package/templates/commands/context-audit.template.md +97 -0
- package/templates/commands/create-task-list.template.md +382 -0
- package/templates/commands/deploy-full.template.md +261 -0
- package/templates/commands/github-task-start.template.md +99 -0
- package/templates/commands/github-update.template.md +69 -0
- package/templates/commands/happy-start.template.md +117 -0
- package/templates/commands/phase-track.template.md +142 -0
- package/templates/commands/tunnel-start.template.md +127 -0
- package/templates/commands/tunnel-stop.template.md +106 -0
- package/templates/hooks/context-guardian.template.js +173 -0
- package/templates/hooks/deployment-orchestrator.template.js +219 -0
- package/templates/hooks/github-progress-hook.template.js +197 -0
- package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
- package/templates/hooks/phase-dev-enforcer.template.js +183 -0
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codebase Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Auto-detects tech stack by analyzing the user's project structure.
|
|
5
|
+
* Makes create-phase-dev work for ANY codebase, not just specific stacks.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Analyze codebase and detect tech stack
|
|
15
|
+
*
|
|
16
|
+
* @param {string} cwd - Working directory to analyze
|
|
17
|
+
* @returns {Object} Detected tech stack configuration
|
|
18
|
+
*/
|
|
19
|
+
export async function analyzeCodebase(cwd = process.cwd()) {
|
|
20
|
+
const spinner = ora('Analyzing codebase...').start();
|
|
21
|
+
|
|
22
|
+
const result = {
|
|
23
|
+
detected: true,
|
|
24
|
+
confidence: 'high',
|
|
25
|
+
frontend: detectFrontend(cwd),
|
|
26
|
+
backend: detectBackend(cwd),
|
|
27
|
+
database: detectDatabase(cwd),
|
|
28
|
+
testing: detectTesting(cwd),
|
|
29
|
+
deployment: detectDeployment(cwd),
|
|
30
|
+
services: detectServices(cwd),
|
|
31
|
+
projectStructure: detectProjectStructure(cwd),
|
|
32
|
+
packageManager: detectPackageManager(cwd),
|
|
33
|
+
monorepo: detectMonorepo(cwd),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Calculate overall confidence
|
|
37
|
+
const detections = [
|
|
38
|
+
result.frontend.detected,
|
|
39
|
+
result.backend.detected,
|
|
40
|
+
result.database.detected,
|
|
41
|
+
];
|
|
42
|
+
const detectedCount = detections.filter(Boolean).length;
|
|
43
|
+
|
|
44
|
+
if (detectedCount === 0) {
|
|
45
|
+
result.confidence = 'low';
|
|
46
|
+
result.detected = false;
|
|
47
|
+
} else if (detectedCount < 2) {
|
|
48
|
+
result.confidence = 'medium';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
spinner.succeed('Codebase analysis complete');
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Detect frontend framework
|
|
58
|
+
*/
|
|
59
|
+
function detectFrontend(cwd) {
|
|
60
|
+
const result = {
|
|
61
|
+
detected: false,
|
|
62
|
+
framework: null,
|
|
63
|
+
language: null,
|
|
64
|
+
bundler: null,
|
|
65
|
+
styling: null,
|
|
66
|
+
version: null,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Check package.json
|
|
70
|
+
const packageJsonPath = join(cwd, 'package.json');
|
|
71
|
+
if (existsSync(packageJsonPath)) {
|
|
72
|
+
try {
|
|
73
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
74
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
75
|
+
|
|
76
|
+
// Detect framework
|
|
77
|
+
if (deps.react) {
|
|
78
|
+
result.detected = true;
|
|
79
|
+
result.framework = 'react';
|
|
80
|
+
result.version = deps.react.replace(/[\^~]/, '');
|
|
81
|
+
} else if (deps.vue) {
|
|
82
|
+
result.detected = true;
|
|
83
|
+
result.framework = 'vue';
|
|
84
|
+
result.version = deps.vue.replace(/[\^~]/, '');
|
|
85
|
+
} else if (deps['@angular/core']) {
|
|
86
|
+
result.detected = true;
|
|
87
|
+
result.framework = 'angular';
|
|
88
|
+
result.version = deps['@angular/core'].replace(/[\^~]/, '');
|
|
89
|
+
} else if (deps.svelte) {
|
|
90
|
+
result.detected = true;
|
|
91
|
+
result.framework = 'svelte';
|
|
92
|
+
result.version = deps.svelte.replace(/[\^~]/, '');
|
|
93
|
+
} else if (deps.next) {
|
|
94
|
+
result.detected = true;
|
|
95
|
+
result.framework = 'nextjs';
|
|
96
|
+
result.version = deps.next.replace(/[\^~]/, '');
|
|
97
|
+
} else if (deps.nuxt) {
|
|
98
|
+
result.detected = true;
|
|
99
|
+
result.framework = 'nuxt';
|
|
100
|
+
result.version = deps.nuxt.replace(/[\^~]/, '');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Detect language
|
|
104
|
+
if (deps.typescript || existsSync(join(cwd, 'tsconfig.json'))) {
|
|
105
|
+
result.language = 'typescript';
|
|
106
|
+
} else {
|
|
107
|
+
result.language = 'javascript';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Detect bundler
|
|
111
|
+
if (deps.vite) {
|
|
112
|
+
result.bundler = 'vite';
|
|
113
|
+
} else if (deps.webpack) {
|
|
114
|
+
result.bundler = 'webpack';
|
|
115
|
+
} else if (deps.parcel) {
|
|
116
|
+
result.bundler = 'parcel';
|
|
117
|
+
} else if (deps.esbuild) {
|
|
118
|
+
result.bundler = 'esbuild';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Detect styling
|
|
122
|
+
if (deps.tailwindcss) {
|
|
123
|
+
result.styling = 'tailwind';
|
|
124
|
+
} else if (deps['styled-components']) {
|
|
125
|
+
result.styling = 'styled-components';
|
|
126
|
+
} else if (deps['@emotion/react']) {
|
|
127
|
+
result.styling = 'emotion';
|
|
128
|
+
} else if (deps.sass || deps['node-sass']) {
|
|
129
|
+
result.styling = 'sass';
|
|
130
|
+
}
|
|
131
|
+
} catch (e) {
|
|
132
|
+
// Ignore parse errors
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Detect backend framework
|
|
141
|
+
*/
|
|
142
|
+
function detectBackend(cwd) {
|
|
143
|
+
const result = {
|
|
144
|
+
detected: false,
|
|
145
|
+
framework: null,
|
|
146
|
+
language: null,
|
|
147
|
+
version: null,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Check for Python backends
|
|
151
|
+
const requirementsPath = join(cwd, 'requirements.txt');
|
|
152
|
+
const pyprojectPath = join(cwd, 'pyproject.toml');
|
|
153
|
+
|
|
154
|
+
if (existsSync(requirementsPath)) {
|
|
155
|
+
const content = readFileSync(requirementsPath, 'utf8').toLowerCase();
|
|
156
|
+
result.language = 'python';
|
|
157
|
+
|
|
158
|
+
if (content.includes('fastapi')) {
|
|
159
|
+
result.detected = true;
|
|
160
|
+
result.framework = 'fastapi';
|
|
161
|
+
} else if (content.includes('django')) {
|
|
162
|
+
result.detected = true;
|
|
163
|
+
result.framework = 'django';
|
|
164
|
+
} else if (content.includes('flask')) {
|
|
165
|
+
result.detected = true;
|
|
166
|
+
result.framework = 'flask';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (existsSync(pyprojectPath)) {
|
|
171
|
+
const content = readFileSync(pyprojectPath, 'utf8').toLowerCase();
|
|
172
|
+
result.language = 'python';
|
|
173
|
+
|
|
174
|
+
if (content.includes('fastapi')) {
|
|
175
|
+
result.detected = true;
|
|
176
|
+
result.framework = 'fastapi';
|
|
177
|
+
} else if (content.includes('django')) {
|
|
178
|
+
result.detected = true;
|
|
179
|
+
result.framework = 'django';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check for Node.js backends
|
|
184
|
+
const packageJsonPath = join(cwd, 'package.json');
|
|
185
|
+
if (existsSync(packageJsonPath) && !result.detected) {
|
|
186
|
+
try {
|
|
187
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
188
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
189
|
+
|
|
190
|
+
if (deps.express) {
|
|
191
|
+
result.detected = true;
|
|
192
|
+
result.framework = 'express';
|
|
193
|
+
result.language = 'node';
|
|
194
|
+
} else if (deps.fastify) {
|
|
195
|
+
result.detected = true;
|
|
196
|
+
result.framework = 'fastify';
|
|
197
|
+
result.language = 'node';
|
|
198
|
+
} else if (deps['@nestjs/core']) {
|
|
199
|
+
result.detected = true;
|
|
200
|
+
result.framework = 'nestjs';
|
|
201
|
+
result.language = 'node';
|
|
202
|
+
} else if (deps.koa) {
|
|
203
|
+
result.detected = true;
|
|
204
|
+
result.framework = 'koa';
|
|
205
|
+
result.language = 'node';
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
// Ignore parse errors
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Check for Go backends
|
|
213
|
+
const goModPath = join(cwd, 'go.mod');
|
|
214
|
+
if (existsSync(goModPath)) {
|
|
215
|
+
const content = readFileSync(goModPath, 'utf8').toLowerCase();
|
|
216
|
+
result.language = 'go';
|
|
217
|
+
|
|
218
|
+
if (content.includes('gin-gonic')) {
|
|
219
|
+
result.detected = true;
|
|
220
|
+
result.framework = 'gin';
|
|
221
|
+
} else if (content.includes('echo')) {
|
|
222
|
+
result.detected = true;
|
|
223
|
+
result.framework = 'echo';
|
|
224
|
+
} else if (content.includes('fiber')) {
|
|
225
|
+
result.detected = true;
|
|
226
|
+
result.framework = 'fiber';
|
|
227
|
+
} else {
|
|
228
|
+
result.detected = true;
|
|
229
|
+
result.framework = 'go-std';
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check for Rust backends
|
|
234
|
+
const cargoPath = join(cwd, 'Cargo.toml');
|
|
235
|
+
if (existsSync(cargoPath)) {
|
|
236
|
+
const content = readFileSync(cargoPath, 'utf8').toLowerCase();
|
|
237
|
+
result.language = 'rust';
|
|
238
|
+
|
|
239
|
+
if (content.includes('actix')) {
|
|
240
|
+
result.detected = true;
|
|
241
|
+
result.framework = 'actix';
|
|
242
|
+
} else if (content.includes('axum')) {
|
|
243
|
+
result.detected = true;
|
|
244
|
+
result.framework = 'axum';
|
|
245
|
+
} else if (content.includes('rocket')) {
|
|
246
|
+
result.detected = true;
|
|
247
|
+
result.framework = 'rocket';
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Check for Ruby backends
|
|
252
|
+
const gemfilePath = join(cwd, 'Gemfile');
|
|
253
|
+
if (existsSync(gemfilePath)) {
|
|
254
|
+
const content = readFileSync(gemfilePath, 'utf8').toLowerCase();
|
|
255
|
+
result.language = 'ruby';
|
|
256
|
+
|
|
257
|
+
if (content.includes('rails')) {
|
|
258
|
+
result.detected = true;
|
|
259
|
+
result.framework = 'rails';
|
|
260
|
+
} else if (content.includes('sinatra')) {
|
|
261
|
+
result.detected = true;
|
|
262
|
+
result.framework = 'sinatra';
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Detect database
|
|
271
|
+
*/
|
|
272
|
+
function detectDatabase(cwd) {
|
|
273
|
+
const result = {
|
|
274
|
+
detected: false,
|
|
275
|
+
type: null,
|
|
276
|
+
orm: null,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// Check package.json for Node ORMs
|
|
280
|
+
const packageJsonPath = join(cwd, 'package.json');
|
|
281
|
+
if (existsSync(packageJsonPath)) {
|
|
282
|
+
try {
|
|
283
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
284
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
285
|
+
|
|
286
|
+
if (deps.prisma || deps['@prisma/client']) {
|
|
287
|
+
result.detected = true;
|
|
288
|
+
result.orm = 'prisma';
|
|
289
|
+
// Prisma schema determines DB type
|
|
290
|
+
} else if (deps.sequelize) {
|
|
291
|
+
result.detected = true;
|
|
292
|
+
result.orm = 'sequelize';
|
|
293
|
+
} else if (deps.typeorm) {
|
|
294
|
+
result.detected = true;
|
|
295
|
+
result.orm = 'typeorm';
|
|
296
|
+
} else if (deps.mongoose) {
|
|
297
|
+
result.detected = true;
|
|
298
|
+
result.type = 'mongodb';
|
|
299
|
+
result.orm = 'mongoose';
|
|
300
|
+
} else if (deps.pg) {
|
|
301
|
+
result.detected = true;
|
|
302
|
+
result.type = 'postgresql';
|
|
303
|
+
} else if (deps.mysql || deps.mysql2) {
|
|
304
|
+
result.detected = true;
|
|
305
|
+
result.type = 'mysql';
|
|
306
|
+
} else if (deps['better-sqlite3'] || deps.sqlite3) {
|
|
307
|
+
result.detected = true;
|
|
308
|
+
result.type = 'sqlite';
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Check for Supabase (BaaS with PostgreSQL)
|
|
312
|
+
if (deps['@supabase/supabase-js'] || deps['@supabase/ssr']) {
|
|
313
|
+
result.detected = true;
|
|
314
|
+
result.type = 'postgresql';
|
|
315
|
+
result.baas = 'supabase';
|
|
316
|
+
}
|
|
317
|
+
} catch (e) {
|
|
318
|
+
// Ignore
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Check Python requirements
|
|
323
|
+
const requirementsPath = join(cwd, 'requirements.txt');
|
|
324
|
+
if (existsSync(requirementsPath)) {
|
|
325
|
+
const content = readFileSync(requirementsPath, 'utf8').toLowerCase();
|
|
326
|
+
|
|
327
|
+
if (content.includes('sqlalchemy')) {
|
|
328
|
+
result.detected = true;
|
|
329
|
+
result.orm = 'sqlalchemy';
|
|
330
|
+
} else if (content.includes('django')) {
|
|
331
|
+
result.detected = true;
|
|
332
|
+
result.orm = 'django-orm';
|
|
333
|
+
} else if (content.includes('tortoise')) {
|
|
334
|
+
result.detected = true;
|
|
335
|
+
result.orm = 'tortoise';
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (content.includes('psycopg') || content.includes('asyncpg')) {
|
|
339
|
+
result.type = 'postgresql';
|
|
340
|
+
} else if (content.includes('pymysql') || content.includes('mysqlclient')) {
|
|
341
|
+
result.type = 'mysql';
|
|
342
|
+
} else if (content.includes('pymongo')) {
|
|
343
|
+
result.type = 'mongodb';
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Check for docker-compose database services
|
|
348
|
+
const dockerComposePath = join(cwd, 'docker-compose.yml');
|
|
349
|
+
const dockerComposeAltPath = join(cwd, 'docker-compose.yaml');
|
|
350
|
+
const composePath = existsSync(dockerComposePath)
|
|
351
|
+
? dockerComposePath
|
|
352
|
+
: existsSync(dockerComposeAltPath)
|
|
353
|
+
? dockerComposeAltPath
|
|
354
|
+
: null;
|
|
355
|
+
|
|
356
|
+
if (composePath) {
|
|
357
|
+
const content = readFileSync(composePath, 'utf8').toLowerCase();
|
|
358
|
+
if (content.includes('postgres')) {
|
|
359
|
+
result.detected = true;
|
|
360
|
+
result.type = result.type || 'postgresql';
|
|
361
|
+
} else if (content.includes('mysql') || content.includes('mariadb')) {
|
|
362
|
+
result.detected = true;
|
|
363
|
+
result.type = result.type || 'mysql';
|
|
364
|
+
} else if (content.includes('mongo')) {
|
|
365
|
+
result.detected = true;
|
|
366
|
+
result.type = result.type || 'mongodb';
|
|
367
|
+
} else if (content.includes('redis')) {
|
|
368
|
+
result.cache = 'redis';
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return result;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Detect testing framework
|
|
377
|
+
*/
|
|
378
|
+
function detectTesting(cwd) {
|
|
379
|
+
const result = {
|
|
380
|
+
detected: false,
|
|
381
|
+
framework: null,
|
|
382
|
+
e2e: null,
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const packageJsonPath = join(cwd, 'package.json');
|
|
386
|
+
if (existsSync(packageJsonPath)) {
|
|
387
|
+
try {
|
|
388
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
389
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
390
|
+
|
|
391
|
+
// Unit testing
|
|
392
|
+
if (deps.vitest) {
|
|
393
|
+
result.detected = true;
|
|
394
|
+
result.framework = 'vitest';
|
|
395
|
+
} else if (deps.jest) {
|
|
396
|
+
result.detected = true;
|
|
397
|
+
result.framework = 'jest';
|
|
398
|
+
} else if (deps.mocha) {
|
|
399
|
+
result.detected = true;
|
|
400
|
+
result.framework = 'mocha';
|
|
401
|
+
} else if (deps.ava) {
|
|
402
|
+
result.detected = true;
|
|
403
|
+
result.framework = 'ava';
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// E2E testing
|
|
407
|
+
if (deps['@playwright/test'] || deps.playwright) {
|
|
408
|
+
result.e2e = 'playwright';
|
|
409
|
+
} else if (deps.cypress) {
|
|
410
|
+
result.e2e = 'cypress';
|
|
411
|
+
} else if (deps.puppeteer) {
|
|
412
|
+
result.e2e = 'puppeteer';
|
|
413
|
+
}
|
|
414
|
+
} catch (e) {
|
|
415
|
+
// Ignore
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Check for pytest (Python)
|
|
420
|
+
const requirementsPath = join(cwd, 'requirements.txt');
|
|
421
|
+
if (existsSync(requirementsPath)) {
|
|
422
|
+
const content = readFileSync(requirementsPath, 'utf8').toLowerCase();
|
|
423
|
+
if (content.includes('pytest')) {
|
|
424
|
+
result.detected = true;
|
|
425
|
+
result.framework = 'pytest';
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Check for config files
|
|
430
|
+
if (existsSync(join(cwd, 'vitest.config.ts')) || existsSync(join(cwd, 'vitest.config.js'))) {
|
|
431
|
+
result.detected = true;
|
|
432
|
+
result.framework = 'vitest';
|
|
433
|
+
}
|
|
434
|
+
if (existsSync(join(cwd, 'jest.config.js')) || existsSync(join(cwd, 'jest.config.ts'))) {
|
|
435
|
+
result.detected = true;
|
|
436
|
+
result.framework = 'jest';
|
|
437
|
+
}
|
|
438
|
+
if (existsSync(join(cwd, 'playwright.config.ts')) || existsSync(join(cwd, 'playwright.config.js'))) {
|
|
439
|
+
result.e2e = 'playwright';
|
|
440
|
+
}
|
|
441
|
+
if (existsSync(join(cwd, 'cypress.config.ts')) || existsSync(join(cwd, 'cypress.config.js'))) {
|
|
442
|
+
result.e2e = 'cypress';
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return result;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Detect deployment platform
|
|
450
|
+
*/
|
|
451
|
+
function detectDeployment(cwd) {
|
|
452
|
+
const result = {
|
|
453
|
+
detected: false,
|
|
454
|
+
platform: null,
|
|
455
|
+
containerized: false,
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// Check for platform-specific files
|
|
459
|
+
if (existsSync(join(cwd, 'vercel.json')) || existsSync(join(cwd, '.vercel'))) {
|
|
460
|
+
result.detected = true;
|
|
461
|
+
result.platform = 'vercel';
|
|
462
|
+
}
|
|
463
|
+
if (existsSync(join(cwd, 'netlify.toml'))) {
|
|
464
|
+
result.detected = true;
|
|
465
|
+
result.platform = 'netlify';
|
|
466
|
+
}
|
|
467
|
+
if (existsSync(join(cwd, 'railway.json')) || existsSync(join(cwd, 'railway.toml'))) {
|
|
468
|
+
result.detected = true;
|
|
469
|
+
result.platform = 'railway';
|
|
470
|
+
}
|
|
471
|
+
if (existsSync(join(cwd, 'fly.toml'))) {
|
|
472
|
+
result.detected = true;
|
|
473
|
+
result.platform = 'fly';
|
|
474
|
+
}
|
|
475
|
+
if (existsSync(join(cwd, 'render.yaml'))) {
|
|
476
|
+
result.detected = true;
|
|
477
|
+
result.platform = 'render';
|
|
478
|
+
}
|
|
479
|
+
if (existsSync(join(cwd, 'heroku.yml')) || existsSync(join(cwd, 'Procfile'))) {
|
|
480
|
+
result.detected = true;
|
|
481
|
+
result.platform = 'heroku';
|
|
482
|
+
}
|
|
483
|
+
if (existsSync(join(cwd, 'wrangler.toml'))) {
|
|
484
|
+
result.detected = true;
|
|
485
|
+
result.platform = 'cloudflare';
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Check for Docker
|
|
489
|
+
if (existsSync(join(cwd, 'Dockerfile')) || existsSync(join(cwd, 'docker-compose.yml'))) {
|
|
490
|
+
result.containerized = true;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Check for Kubernetes
|
|
494
|
+
if (existsSync(join(cwd, 'k8s')) || existsSync(join(cwd, 'kubernetes'))) {
|
|
495
|
+
result.detected = true;
|
|
496
|
+
result.platform = 'kubernetes';
|
|
497
|
+
result.containerized = true;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Check for AWS
|
|
501
|
+
if (existsSync(join(cwd, 'serverless.yml')) || existsSync(join(cwd, 'serverless.yaml'))) {
|
|
502
|
+
result.detected = true;
|
|
503
|
+
result.platform = 'aws-serverless';
|
|
504
|
+
}
|
|
505
|
+
if (existsSync(join(cwd, 'cdk.json'))) {
|
|
506
|
+
result.detected = true;
|
|
507
|
+
result.platform = 'aws-cdk';
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Detect external services and integrations
|
|
515
|
+
*/
|
|
516
|
+
function detectServices(cwd) {
|
|
517
|
+
const result = {
|
|
518
|
+
detected: false,
|
|
519
|
+
supabase: false,
|
|
520
|
+
n8n: false,
|
|
521
|
+
stripe: false,
|
|
522
|
+
auth0: false,
|
|
523
|
+
clerk: false,
|
|
524
|
+
resend: false,
|
|
525
|
+
twilio: false,
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// Check package.json for service SDKs
|
|
529
|
+
const packageJsonPath = join(cwd, 'package.json');
|
|
530
|
+
if (existsSync(packageJsonPath)) {
|
|
531
|
+
try {
|
|
532
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
533
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
534
|
+
|
|
535
|
+
// Supabase
|
|
536
|
+
if (deps['@supabase/supabase-js'] || deps['@supabase/ssr'] || deps['@supabase/auth-helpers-nextjs']) {
|
|
537
|
+
result.detected = true;
|
|
538
|
+
result.supabase = true;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// n8n (self-hosted workflow automation)
|
|
542
|
+
if (deps['n8n'] || deps['n8n-workflow'] || deps['n8n-core']) {
|
|
543
|
+
result.detected = true;
|
|
544
|
+
result.n8n = true;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Payment - Stripe
|
|
548
|
+
if (deps.stripe || deps['@stripe/stripe-js']) {
|
|
549
|
+
result.detected = true;
|
|
550
|
+
result.stripe = true;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Auth providers
|
|
554
|
+
if (deps['@auth0/auth0-react'] || deps['@auth0/nextjs-auth0']) {
|
|
555
|
+
result.detected = true;
|
|
556
|
+
result.auth0 = true;
|
|
557
|
+
}
|
|
558
|
+
if (deps['@clerk/nextjs'] || deps['@clerk/clerk-react']) {
|
|
559
|
+
result.detected = true;
|
|
560
|
+
result.clerk = true;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Email - Resend
|
|
564
|
+
if (deps.resend || deps['@react-email/components']) {
|
|
565
|
+
result.detected = true;
|
|
566
|
+
result.resend = true;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Twilio
|
|
570
|
+
if (deps.twilio) {
|
|
571
|
+
result.detected = true;
|
|
572
|
+
result.twilio = true;
|
|
573
|
+
}
|
|
574
|
+
} catch (e) {
|
|
575
|
+
// Ignore parse errors
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Check Python requirements for n8n webhooks or Supabase
|
|
580
|
+
const requirementsPath = join(cwd, 'requirements.txt');
|
|
581
|
+
if (existsSync(requirementsPath)) {
|
|
582
|
+
const content = readFileSync(requirementsPath, 'utf8').toLowerCase();
|
|
583
|
+
|
|
584
|
+
if (content.includes('supabase')) {
|
|
585
|
+
result.detected = true;
|
|
586
|
+
result.supabase = true;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Check for n8n config files (self-hosted deployments)
|
|
591
|
+
if (
|
|
592
|
+
existsSync(join(cwd, 'n8n.config.js')) ||
|
|
593
|
+
existsSync(join(cwd, '.n8n')) ||
|
|
594
|
+
existsSync(join(cwd, 'n8n-custom'))
|
|
595
|
+
) {
|
|
596
|
+
result.detected = true;
|
|
597
|
+
result.n8n = true;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Check docker-compose for n8n service
|
|
601
|
+
const dockerComposePath = join(cwd, 'docker-compose.yml');
|
|
602
|
+
const dockerComposeAltPath = join(cwd, 'docker-compose.yaml');
|
|
603
|
+
const composePath = existsSync(dockerComposePath)
|
|
604
|
+
? dockerComposePath
|
|
605
|
+
: existsSync(dockerComposeAltPath)
|
|
606
|
+
? dockerComposeAltPath
|
|
607
|
+
: null;
|
|
608
|
+
|
|
609
|
+
if (composePath) {
|
|
610
|
+
const content = readFileSync(composePath, 'utf8').toLowerCase();
|
|
611
|
+
if (content.includes('n8n') || content.includes('n8nio')) {
|
|
612
|
+
result.detected = true;
|
|
613
|
+
result.n8n = true;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Check env files for service references (without exposing values)
|
|
618
|
+
const envFiles = ['.env', '.env.local', '.env.example', '.env.sample'];
|
|
619
|
+
for (const envFile of envFiles) {
|
|
620
|
+
const envPath = join(cwd, envFile);
|
|
621
|
+
if (existsSync(envPath)) {
|
|
622
|
+
const content = readFileSync(envPath, 'utf8').toUpperCase();
|
|
623
|
+
|
|
624
|
+
if (content.includes('SUPABASE')) {
|
|
625
|
+
result.detected = true;
|
|
626
|
+
result.supabase = true;
|
|
627
|
+
}
|
|
628
|
+
if (content.includes('N8N')) {
|
|
629
|
+
result.detected = true;
|
|
630
|
+
result.n8n = true;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return result;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Detect project structure
|
|
640
|
+
*/
|
|
641
|
+
function detectProjectStructure(cwd) {
|
|
642
|
+
const result = {
|
|
643
|
+
hasClaudeDir: existsSync(join(cwd, '.claude')),
|
|
644
|
+
hasSrcDir: existsSync(join(cwd, 'src')),
|
|
645
|
+
hasAppsDir: existsSync(join(cwd, 'apps')),
|
|
646
|
+
hasPackagesDir: existsSync(join(cwd, 'packages')),
|
|
647
|
+
hasDocsDir: existsSync(join(cwd, 'docs')),
|
|
648
|
+
hasTestsDir: existsSync(join(cwd, 'tests')) || existsSync(join(cwd, '__tests__')),
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
return result;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Detect package manager
|
|
656
|
+
*/
|
|
657
|
+
function detectPackageManager(cwd) {
|
|
658
|
+
if (existsSync(join(cwd, 'pnpm-lock.yaml'))) {
|
|
659
|
+
return 'pnpm';
|
|
660
|
+
}
|
|
661
|
+
if (existsSync(join(cwd, 'yarn.lock'))) {
|
|
662
|
+
return 'yarn';
|
|
663
|
+
}
|
|
664
|
+
if (existsSync(join(cwd, 'bun.lockb'))) {
|
|
665
|
+
return 'bun';
|
|
666
|
+
}
|
|
667
|
+
if (existsSync(join(cwd, 'package-lock.json'))) {
|
|
668
|
+
return 'npm';
|
|
669
|
+
}
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Detect if it's a monorepo
|
|
675
|
+
*/
|
|
676
|
+
function detectMonorepo(cwd) {
|
|
677
|
+
const result = {
|
|
678
|
+
isMonorepo: false,
|
|
679
|
+
tool: null,
|
|
680
|
+
workspaces: [],
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
// Check for workspace configs
|
|
684
|
+
const packageJsonPath = join(cwd, 'package.json');
|
|
685
|
+
if (existsSync(packageJsonPath)) {
|
|
686
|
+
try {
|
|
687
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
688
|
+
if (pkg.workspaces) {
|
|
689
|
+
result.isMonorepo = true;
|
|
690
|
+
result.workspaces = Array.isArray(pkg.workspaces)
|
|
691
|
+
? pkg.workspaces
|
|
692
|
+
: pkg.workspaces.packages || [];
|
|
693
|
+
}
|
|
694
|
+
} catch (e) {
|
|
695
|
+
// Ignore
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Check for monorepo tools
|
|
700
|
+
if (existsSync(join(cwd, 'pnpm-workspace.yaml'))) {
|
|
701
|
+
result.isMonorepo = true;
|
|
702
|
+
result.tool = 'pnpm';
|
|
703
|
+
}
|
|
704
|
+
if (existsSync(join(cwd, 'lerna.json'))) {
|
|
705
|
+
result.isMonorepo = true;
|
|
706
|
+
result.tool = 'lerna';
|
|
707
|
+
}
|
|
708
|
+
if (existsSync(join(cwd, 'nx.json'))) {
|
|
709
|
+
result.isMonorepo = true;
|
|
710
|
+
result.tool = 'nx';
|
|
711
|
+
}
|
|
712
|
+
if (existsSync(join(cwd, 'turbo.json'))) {
|
|
713
|
+
result.isMonorepo = true;
|
|
714
|
+
result.tool = 'turborepo';
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return result;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Generate human-readable summary of detected stack
|
|
722
|
+
*/
|
|
723
|
+
export function generateStackSummary(analysis) {
|
|
724
|
+
const parts = [];
|
|
725
|
+
|
|
726
|
+
// Frontend
|
|
727
|
+
if (analysis.frontend.detected) {
|
|
728
|
+
let fe = analysis.frontend.framework;
|
|
729
|
+
if (analysis.frontend.version) {
|
|
730
|
+
fe += ` ${analysis.frontend.version}`;
|
|
731
|
+
}
|
|
732
|
+
if (analysis.frontend.language === 'typescript') {
|
|
733
|
+
fe += ' + TypeScript';
|
|
734
|
+
}
|
|
735
|
+
if (analysis.frontend.bundler) {
|
|
736
|
+
fe += ` + ${analysis.frontend.bundler}`;
|
|
737
|
+
}
|
|
738
|
+
if (analysis.frontend.styling) {
|
|
739
|
+
fe += ` + ${analysis.frontend.styling}`;
|
|
740
|
+
}
|
|
741
|
+
parts.push(`Frontend: ${fe}`);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Backend
|
|
745
|
+
if (analysis.backend.detected) {
|
|
746
|
+
let be = analysis.backend.framework;
|
|
747
|
+
if (analysis.backend.language) {
|
|
748
|
+
be = `${analysis.backend.framework} (${analysis.backend.language})`;
|
|
749
|
+
}
|
|
750
|
+
parts.push(`Backend: ${be}`);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Database
|
|
754
|
+
if (analysis.database.detected) {
|
|
755
|
+
let db = analysis.database.type || 'Unknown';
|
|
756
|
+
if (analysis.database.orm) {
|
|
757
|
+
db += ` + ${analysis.database.orm}`;
|
|
758
|
+
}
|
|
759
|
+
parts.push(`Database: ${db}`);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Testing
|
|
763
|
+
if (analysis.testing.detected) {
|
|
764
|
+
let test = analysis.testing.framework;
|
|
765
|
+
if (analysis.testing.e2e) {
|
|
766
|
+
test += ` + ${analysis.testing.e2e} (E2E)`;
|
|
767
|
+
}
|
|
768
|
+
parts.push(`Testing: ${test}`);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Deployment
|
|
772
|
+
if (analysis.deployment.detected) {
|
|
773
|
+
let deploy = analysis.deployment.platform;
|
|
774
|
+
if (analysis.deployment.containerized) {
|
|
775
|
+
deploy += ' (containerized)';
|
|
776
|
+
}
|
|
777
|
+
parts.push(`Deployment: ${deploy}`);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Services
|
|
781
|
+
if (analysis.services?.detected) {
|
|
782
|
+
const services = [];
|
|
783
|
+
if (analysis.services.supabase) services.push('Supabase');
|
|
784
|
+
if (analysis.services.n8n) services.push('n8n');
|
|
785
|
+
if (analysis.services.stripe) services.push('Stripe');
|
|
786
|
+
if (analysis.services.auth0) services.push('Auth0');
|
|
787
|
+
if (analysis.services.clerk) services.push('Clerk');
|
|
788
|
+
if (analysis.services.resend) services.push('Resend');
|
|
789
|
+
if (analysis.services.twilio) services.push('Twilio');
|
|
790
|
+
|
|
791
|
+
if (services.length > 0) {
|
|
792
|
+
parts.push(`Services: ${services.join(', ')}`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return parts.length > 0 ? parts.join('\n') : 'Unable to detect stack';
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Display analysis results
|
|
801
|
+
*/
|
|
802
|
+
export function displayAnalysisResults(analysis) {
|
|
803
|
+
console.log('');
|
|
804
|
+
console.log(chalk.cyan.bold('📊 Detected Tech Stack:'));
|
|
805
|
+
console.log('');
|
|
806
|
+
|
|
807
|
+
const summary = generateStackSummary(analysis);
|
|
808
|
+
console.log(chalk.white(summary));
|
|
809
|
+
|
|
810
|
+
console.log('');
|
|
811
|
+
console.log(chalk.dim(`Confidence: ${analysis.confidence}`));
|
|
812
|
+
console.log('');
|
|
813
|
+
}
|