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,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codebase Analysis Module
|
|
3
|
+
*
|
|
4
|
+
* Finds relevant files, functions, and patterns for issue documentation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execCommand } from '../utils.js';
|
|
8
|
+
import { readFileSync, existsSync, statSync } from 'fs';
|
|
9
|
+
import { join, relative, extname } from 'path';
|
|
10
|
+
import { glob } from 'glob';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* File extensions to analyze by language
|
|
14
|
+
*/
|
|
15
|
+
const LANGUAGE_EXTENSIONS = {
|
|
16
|
+
javascript: ['.js', '.jsx', '.mjs', '.cjs'],
|
|
17
|
+
typescript: ['.ts', '.tsx', '.mts', '.cts'],
|
|
18
|
+
python: ['.py', '.pyw'],
|
|
19
|
+
rust: ['.rs'],
|
|
20
|
+
go: ['.go'],
|
|
21
|
+
java: ['.java'],
|
|
22
|
+
csharp: ['.cs'],
|
|
23
|
+
cpp: ['.cpp', '.cc', '.cxx', '.c', '.h', '.hpp'],
|
|
24
|
+
ruby: ['.rb'],
|
|
25
|
+
php: ['.php'],
|
|
26
|
+
swift: ['.swift'],
|
|
27
|
+
kotlin: ['.kt', '.kts'],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Common source directories to search
|
|
32
|
+
*/
|
|
33
|
+
const SOURCE_DIRS = [
|
|
34
|
+
'src',
|
|
35
|
+
'lib',
|
|
36
|
+
'app',
|
|
37
|
+
'apps',
|
|
38
|
+
'packages',
|
|
39
|
+
'components',
|
|
40
|
+
'views',
|
|
41
|
+
'pages',
|
|
42
|
+
'services',
|
|
43
|
+
'utils',
|
|
44
|
+
'helpers',
|
|
45
|
+
'hooks',
|
|
46
|
+
'store',
|
|
47
|
+
'stores',
|
|
48
|
+
'api',
|
|
49
|
+
'routes',
|
|
50
|
+
'routers',
|
|
51
|
+
'controllers',
|
|
52
|
+
'models',
|
|
53
|
+
'schemas',
|
|
54
|
+
'types',
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Directories to ignore
|
|
59
|
+
*/
|
|
60
|
+
const IGNORE_DIRS = [
|
|
61
|
+
'node_modules',
|
|
62
|
+
'.git',
|
|
63
|
+
'dist',
|
|
64
|
+
'build',
|
|
65
|
+
'out',
|
|
66
|
+
'.next',
|
|
67
|
+
'.nuxt',
|
|
68
|
+
'coverage',
|
|
69
|
+
'__pycache__',
|
|
70
|
+
'.pytest_cache',
|
|
71
|
+
'venv',
|
|
72
|
+
'.venv',
|
|
73
|
+
'target',
|
|
74
|
+
'vendor',
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Search for files matching keywords
|
|
79
|
+
*/
|
|
80
|
+
export async function searchFiles(keywords, options = {}) {
|
|
81
|
+
const { cwd = process.cwd(), maxResults = 20, extensions } = options;
|
|
82
|
+
|
|
83
|
+
const results = [];
|
|
84
|
+
const searchPatterns = keywords.map((k) => k.toLowerCase());
|
|
85
|
+
|
|
86
|
+
// Determine which extensions to search
|
|
87
|
+
let extsToSearch = [];
|
|
88
|
+
if (extensions) {
|
|
89
|
+
extsToSearch = extensions;
|
|
90
|
+
} else {
|
|
91
|
+
// Search all code extensions
|
|
92
|
+
for (const exts of Object.values(LANGUAGE_EXTENSIONS)) {
|
|
93
|
+
extsToSearch.push(...exts);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Build glob pattern
|
|
98
|
+
const extPattern =
|
|
99
|
+
extsToSearch.length === 1
|
|
100
|
+
? extsToSearch[0]
|
|
101
|
+
: `{${extsToSearch.join(',')}}`;
|
|
102
|
+
|
|
103
|
+
const ignorePattern = IGNORE_DIRS.map((d) => `**/${d}/**`);
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const files = await glob(`**/*${extPattern}`, {
|
|
107
|
+
cwd,
|
|
108
|
+
ignore: ignorePattern,
|
|
109
|
+
nodir: true,
|
|
110
|
+
absolute: false,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Score files by keyword matches
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
const fileLower = file.toLowerCase();
|
|
116
|
+
let score = 0;
|
|
117
|
+
|
|
118
|
+
for (const pattern of searchPatterns) {
|
|
119
|
+
// File path contains keyword
|
|
120
|
+
if (fileLower.includes(pattern)) {
|
|
121
|
+
score += 10;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// File name starts with keyword
|
|
125
|
+
const fileName = file.split(/[/\\]/).pop().toLowerCase();
|
|
126
|
+
if (fileName.startsWith(pattern)) {
|
|
127
|
+
score += 5;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (score > 0) {
|
|
132
|
+
results.push({ file, score });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Sort by score and limit
|
|
137
|
+
results.sort((a, b) => b.score - a.score);
|
|
138
|
+
return results.slice(0, maxResults).map((r) => r.file);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Search file contents using grep/ripgrep
|
|
146
|
+
*/
|
|
147
|
+
export function searchContent(pattern, options = {}) {
|
|
148
|
+
const { cwd = process.cwd(), maxResults = 20, contextLines = 2 } = options;
|
|
149
|
+
|
|
150
|
+
// Try ripgrep first (faster)
|
|
151
|
+
let cmd = `rg -n -C ${contextLines} --max-count 50 "${pattern}"`;
|
|
152
|
+
let result = execCommand(cmd, { cwd });
|
|
153
|
+
|
|
154
|
+
// Fall back to grep
|
|
155
|
+
if (!result.success) {
|
|
156
|
+
cmd = `grep -rn -C ${contextLines} "${pattern}" .`;
|
|
157
|
+
result = execCommand(cmd, { cwd });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (result.success && result.output) {
|
|
161
|
+
const matches = parseGrepOutput(result.output);
|
|
162
|
+
return matches.slice(0, maxResults);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Parse grep/ripgrep output into structured results
|
|
170
|
+
*/
|
|
171
|
+
function parseGrepOutput(output) {
|
|
172
|
+
const results = [];
|
|
173
|
+
const lines = output.split('\n');
|
|
174
|
+
|
|
175
|
+
let currentFile = null;
|
|
176
|
+
let currentMatches = [];
|
|
177
|
+
|
|
178
|
+
for (const line of lines) {
|
|
179
|
+
// Match format: file:line:content or file-line-content (context)
|
|
180
|
+
const match = line.match(/^([^:]+):(\d+)[:-](.*)$/);
|
|
181
|
+
|
|
182
|
+
if (match) {
|
|
183
|
+
const [, file, lineNum, content] = match;
|
|
184
|
+
|
|
185
|
+
if (file !== currentFile) {
|
|
186
|
+
if (currentFile && currentMatches.length > 0) {
|
|
187
|
+
results.push({
|
|
188
|
+
file: currentFile,
|
|
189
|
+
matches: currentMatches,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
currentFile = file;
|
|
193
|
+
currentMatches = [];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
currentMatches.push({
|
|
197
|
+
line: parseInt(lineNum, 10),
|
|
198
|
+
content: content.trim(),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Don't forget the last file
|
|
204
|
+
if (currentFile && currentMatches.length > 0) {
|
|
205
|
+
results.push({
|
|
206
|
+
file: currentFile,
|
|
207
|
+
matches: currentMatches,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return results;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Find function/class definitions in a file
|
|
216
|
+
*/
|
|
217
|
+
export function findDefinitions(filePath, options = {}) {
|
|
218
|
+
const { cwd = process.cwd() } = options;
|
|
219
|
+
const fullPath = join(cwd, filePath);
|
|
220
|
+
|
|
221
|
+
if (!existsSync(fullPath)) {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const ext = extname(filePath).toLowerCase();
|
|
226
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
227
|
+
const lines = content.split('\n');
|
|
228
|
+
const definitions = [];
|
|
229
|
+
|
|
230
|
+
// Patterns for different languages
|
|
231
|
+
const patterns = {
|
|
232
|
+
// JavaScript/TypeScript
|
|
233
|
+
'.js': [
|
|
234
|
+
/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/,
|
|
235
|
+
/^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\(?/,
|
|
236
|
+
/^(?:export\s+)?class\s+(\w+)/,
|
|
237
|
+
/^\s*(\w+)\s*\([^)]*\)\s*\{/, // Method in object
|
|
238
|
+
],
|
|
239
|
+
'.ts': [
|
|
240
|
+
/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/,
|
|
241
|
+
/^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\(?/,
|
|
242
|
+
/^(?:export\s+)?class\s+(\w+)/,
|
|
243
|
+
/^(?:export\s+)?interface\s+(\w+)/,
|
|
244
|
+
/^(?:export\s+)?type\s+(\w+)/,
|
|
245
|
+
],
|
|
246
|
+
'.py': [
|
|
247
|
+
/^(?:async\s+)?def\s+(\w+)/,
|
|
248
|
+
/^class\s+(\w+)/,
|
|
249
|
+
],
|
|
250
|
+
'.go': [
|
|
251
|
+
/^func\s+(?:\([^)]+\)\s+)?(\w+)/,
|
|
252
|
+
/^type\s+(\w+)\s+(?:struct|interface)/,
|
|
253
|
+
],
|
|
254
|
+
'.rs': [
|
|
255
|
+
/^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/,
|
|
256
|
+
/^(?:pub\s+)?struct\s+(\w+)/,
|
|
257
|
+
/^(?:pub\s+)?enum\s+(\w+)/,
|
|
258
|
+
/^(?:pub\s+)?trait\s+(\w+)/,
|
|
259
|
+
],
|
|
260
|
+
'.java': [
|
|
261
|
+
/^(?:public|private|protected)?\s*(?:static\s+)?(?:class|interface|enum)\s+(\w+)/,
|
|
262
|
+
/^(?:public|private|protected)?\s*(?:static\s+)?[\w<>,\s]+\s+(\w+)\s*\(/,
|
|
263
|
+
],
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// Use JavaScript patterns for JSX/TSX
|
|
267
|
+
const extPatterns =
|
|
268
|
+
patterns[ext] ||
|
|
269
|
+
patterns['.js'.includes(ext) ? '.js' : '.ts'.includes(ext) ? '.ts' : '.js'];
|
|
270
|
+
|
|
271
|
+
if (!extPatterns) {
|
|
272
|
+
return definitions;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
for (let i = 0; i < lines.length; i++) {
|
|
276
|
+
const line = lines[i];
|
|
277
|
+
|
|
278
|
+
for (const pattern of extPatterns) {
|
|
279
|
+
const match = line.match(pattern);
|
|
280
|
+
if (match && match[1]) {
|
|
281
|
+
definitions.push({
|
|
282
|
+
name: match[1],
|
|
283
|
+
line: i + 1,
|
|
284
|
+
type: guessDefinitionType(line),
|
|
285
|
+
snippet: line.trim(),
|
|
286
|
+
});
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return definitions;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Guess the type of definition from the line
|
|
297
|
+
*/
|
|
298
|
+
function guessDefinitionType(line) {
|
|
299
|
+
const lower = line.toLowerCase();
|
|
300
|
+
|
|
301
|
+
if (lower.includes('class ')) return 'class';
|
|
302
|
+
if (lower.includes('interface ')) return 'interface';
|
|
303
|
+
if (lower.includes('type ')) return 'type';
|
|
304
|
+
if (lower.includes('struct ')) return 'struct';
|
|
305
|
+
if (lower.includes('enum ')) return 'enum';
|
|
306
|
+
if (lower.includes('trait ')) return 'trait';
|
|
307
|
+
if (lower.includes('function ') || lower.includes('def ') || lower.includes('fn '))
|
|
308
|
+
return 'function';
|
|
309
|
+
if (lower.includes('const ')) return 'constant';
|
|
310
|
+
|
|
311
|
+
return 'unknown';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Extract a code snippet from a file
|
|
316
|
+
*/
|
|
317
|
+
export function extractSnippet(filePath, startLine, endLine, options = {}) {
|
|
318
|
+
const { cwd = process.cwd(), maxLines = 30 } = options;
|
|
319
|
+
const fullPath = join(cwd, filePath);
|
|
320
|
+
|
|
321
|
+
if (!existsSync(fullPath)) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
326
|
+
const lines = content.split('\n');
|
|
327
|
+
|
|
328
|
+
// Clamp line numbers
|
|
329
|
+
const start = Math.max(0, startLine - 1);
|
|
330
|
+
const end = Math.min(lines.length, endLine || start + maxLines);
|
|
331
|
+
|
|
332
|
+
const snippet = lines.slice(start, end).join('\n');
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
file: filePath,
|
|
336
|
+
startLine: start + 1,
|
|
337
|
+
endLine: end,
|
|
338
|
+
content: snippet,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Analyze codebase for issue context
|
|
344
|
+
*/
|
|
345
|
+
export async function analyzeForIssue(keywords, options = {}) {
|
|
346
|
+
const { cwd = process.cwd(), maxFiles = 5, maxFunctions = 10 } = options;
|
|
347
|
+
|
|
348
|
+
const analysis = {
|
|
349
|
+
relevantFiles: [],
|
|
350
|
+
keyFunctions: [],
|
|
351
|
+
patterns: [],
|
|
352
|
+
codeSnippets: [],
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// 1. Search for relevant files
|
|
356
|
+
const files = await searchFiles(keywords, { cwd, maxResults: maxFiles * 2 });
|
|
357
|
+
|
|
358
|
+
// 2. For each file, find relevant definitions
|
|
359
|
+
for (const file of files.slice(0, maxFiles)) {
|
|
360
|
+
const definitions = findDefinitions(file, { cwd });
|
|
361
|
+
|
|
362
|
+
analysis.relevantFiles.push({
|
|
363
|
+
file,
|
|
364
|
+
definitions: definitions.slice(0, 5),
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Add key functions
|
|
368
|
+
for (const def of definitions) {
|
|
369
|
+
if (
|
|
370
|
+
def.type === 'function' ||
|
|
371
|
+
def.type === 'class' ||
|
|
372
|
+
def.type === 'component'
|
|
373
|
+
) {
|
|
374
|
+
// Check if function name matches any keyword
|
|
375
|
+
const defLower = def.name.toLowerCase();
|
|
376
|
+
const isRelevant = keywords.some(
|
|
377
|
+
(k) => defLower.includes(k.toLowerCase()) || k.toLowerCase().includes(defLower)
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
if (isRelevant) {
|
|
381
|
+
analysis.keyFunctions.push({
|
|
382
|
+
file,
|
|
383
|
+
...def,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 3. Search content for specific patterns
|
|
391
|
+
for (const keyword of keywords) {
|
|
392
|
+
const contentMatches = searchContent(keyword, {
|
|
393
|
+
cwd,
|
|
394
|
+
maxResults: 5,
|
|
395
|
+
contextLines: 1,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
for (const match of contentMatches) {
|
|
399
|
+
analysis.patterns.push({
|
|
400
|
+
keyword,
|
|
401
|
+
file: match.file,
|
|
402
|
+
matches: match.matches.slice(0, 3),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// 4. Extract code snippets for key functions
|
|
408
|
+
for (const func of analysis.keyFunctions.slice(0, 3)) {
|
|
409
|
+
const snippet = extractSnippet(func.file, func.line, func.line + 15, { cwd });
|
|
410
|
+
if (snippet) {
|
|
411
|
+
analysis.codeSnippets.push(snippet);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return analysis;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Detect project type and tech stack
|
|
420
|
+
*/
|
|
421
|
+
export function detectProjectType(cwd = process.cwd()) {
|
|
422
|
+
const stack = {
|
|
423
|
+
languages: [],
|
|
424
|
+
frameworks: [],
|
|
425
|
+
tools: [],
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// Check package.json for JS/TS projects
|
|
429
|
+
const packageJsonPath = join(cwd, 'package.json');
|
|
430
|
+
if (existsSync(packageJsonPath)) {
|
|
431
|
+
try {
|
|
432
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
433
|
+
stack.languages.push('javascript');
|
|
434
|
+
|
|
435
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
436
|
+
|
|
437
|
+
if (deps.typescript) stack.languages.push('typescript');
|
|
438
|
+
if (deps.react) stack.frameworks.push('react');
|
|
439
|
+
if (deps.vue) stack.frameworks.push('vue');
|
|
440
|
+
if (deps.angular) stack.frameworks.push('angular');
|
|
441
|
+
if (deps.next) stack.frameworks.push('nextjs');
|
|
442
|
+
if (deps.express) stack.frameworks.push('express');
|
|
443
|
+
if (deps.fastify) stack.frameworks.push('fastify');
|
|
444
|
+
if (deps.jest || deps.vitest) stack.tools.push('testing');
|
|
445
|
+
if (deps.playwright) stack.tools.push('e2e-testing');
|
|
446
|
+
} catch {}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Check for Python
|
|
450
|
+
if (existsSync(join(cwd, 'requirements.txt')) || existsSync(join(cwd, 'pyproject.toml'))) {
|
|
451
|
+
stack.languages.push('python');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Check for Rust
|
|
455
|
+
if (existsSync(join(cwd, 'Cargo.toml'))) {
|
|
456
|
+
stack.languages.push('rust');
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Check for Go
|
|
460
|
+
if (existsSync(join(cwd, 'go.mod'))) {
|
|
461
|
+
stack.languages.push('go');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return stack;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Find similar patterns in codebase (for "patterns to follow")
|
|
469
|
+
*/
|
|
470
|
+
export async function findSimilarPatterns(pattern, options = {}) {
|
|
471
|
+
const { cwd = process.cwd(), maxResults = 3 } = options;
|
|
472
|
+
|
|
473
|
+
const matches = searchContent(pattern, { cwd, maxResults: maxResults * 2 });
|
|
474
|
+
|
|
475
|
+
// Filter to most relevant matches
|
|
476
|
+
return matches.slice(0, maxResults).map((match) => ({
|
|
477
|
+
file: match.file,
|
|
478
|
+
line: match.matches[0]?.line,
|
|
479
|
+
snippet: match.matches.map((m) => m.content).join('\n'),
|
|
480
|
+
}));
|
|
481
|
+
}
|