@itz4blitz/agentful 0.1.0 → 0.1.4
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/.claude/agents/architect.md +283 -11
- package/.claude/agents/backend.md +282 -218
- package/.claude/agents/frontend.md +242 -319
- package/.claude/agents/orchestrator.md +27 -27
- package/.claude/agents/reviewer.md +1 -1
- package/.claude/agents/tester.md +375 -284
- package/.claude/commands/agentful-decide.md +104 -29
- package/.claude/commands/agentful-start.md +18 -16
- package/.claude/commands/agentful-status.md +28 -22
- package/.claude/commands/agentful-validate.md +42 -20
- package/.claude/commands/agentful.md +329 -0
- package/.claude/product/README.md +1 -1
- package/.claude/product/index.md +1 -1
- package/.claude/settings.json +4 -3
- package/.claude/skills/conversation/SKILL.md +1130 -0
- package/LICENSE +1 -1
- package/README.md +557 -222
- package/bin/cli.js +319 -36
- package/lib/agent-generator.js +685 -0
- package/lib/domain-detector.js +468 -0
- package/lib/domain-structure-generator.js +770 -0
- package/lib/index.js +40 -0
- package/lib/project-analyzer.js +701 -0
- package/lib/tech-stack-detector.js +1091 -0
- package/lib/template-engine.js +153 -0
- package/package.json +14 -5
- package/template/CLAUDE.md +62 -21
- package/template/PRODUCT.md +89 -1
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Project Analyzer Engine
|
|
5
|
+
* Comprehensive project analysis for agentful initialization
|
|
6
|
+
* Supports multi-language, multi-framework projects with confidence scoring
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { detectTechStack } from './tech-stack-detector.js';
|
|
13
|
+
import { detectDomains } from './domain-detector.js';
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = path.dirname(__filename);
|
|
17
|
+
|
|
18
|
+
// Cache configuration
|
|
19
|
+
const CACHE_VERSION = '1.0';
|
|
20
|
+
const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Main project analysis function
|
|
24
|
+
* @param {string} projectRoot - Root directory of the project
|
|
25
|
+
* @returns {Promise<Object>} Comprehensive analysis results
|
|
26
|
+
*/
|
|
27
|
+
export async function analyzeProject(projectRoot = process.cwd()) {
|
|
28
|
+
const startTime = Date.now();
|
|
29
|
+
|
|
30
|
+
// Check for cached analysis
|
|
31
|
+
const cachedAnalysis = await loadCachedAnalysis(projectRoot);
|
|
32
|
+
if (cachedAnalysis) {
|
|
33
|
+
return cachedAnalysis;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
// Phase 1: Quick scan (determine project type and language)
|
|
38
|
+
const quickScan = await performQuickScan(projectRoot);
|
|
39
|
+
|
|
40
|
+
// Phase 2: Deep analysis (dependencies, frameworks, patterns)
|
|
41
|
+
const deepAnalysis = await performDeepAnalysis(projectRoot, quickScan);
|
|
42
|
+
|
|
43
|
+
// Phase 3: Pattern mining (code samples, conventions)
|
|
44
|
+
const patternMining = await performPatternMining(projectRoot, quickScan);
|
|
45
|
+
|
|
46
|
+
// Phase 4: Domain detection
|
|
47
|
+
const domainDetection = await detectDomains(projectRoot, quickScan);
|
|
48
|
+
|
|
49
|
+
// Combine all analysis phases
|
|
50
|
+
const analysis = {
|
|
51
|
+
projectRoot,
|
|
52
|
+
analyzedAt: new Date().toISOString(),
|
|
53
|
+
analysisDuration: Date.now() - startTime,
|
|
54
|
+
cacheVersion: CACHE_VERSION,
|
|
55
|
+
|
|
56
|
+
// Project classification
|
|
57
|
+
projectType: quickScan.projectType,
|
|
58
|
+
language: deepAnalysis.language,
|
|
59
|
+
primaryLanguage: deepAnalysis.primaryLanguage,
|
|
60
|
+
|
|
61
|
+
// Technology stack
|
|
62
|
+
frameworks: deepAnalysis.frameworks,
|
|
63
|
+
techStack: deepAnalysis.techStack,
|
|
64
|
+
|
|
65
|
+
// Structure and organization
|
|
66
|
+
structure: quickScan.structure,
|
|
67
|
+
buildSystem: deepAnalysis.buildSystem,
|
|
68
|
+
packageManager: deepAnalysis.packageManager,
|
|
69
|
+
|
|
70
|
+
// Domains and features
|
|
71
|
+
domains: domainDetection.detected,
|
|
72
|
+
domainConfidence: domainDetection.confidence,
|
|
73
|
+
|
|
74
|
+
// Code patterns and conventions
|
|
75
|
+
patterns: patternMining.patterns,
|
|
76
|
+
conventions: patternMining.conventions,
|
|
77
|
+
|
|
78
|
+
// Samples for agent generation
|
|
79
|
+
samples: patternMining.samples,
|
|
80
|
+
|
|
81
|
+
// Metadata
|
|
82
|
+
confidence: calculateOverallConfidence(quickScan, deepAnalysis, patternMining),
|
|
83
|
+
warnings: generateWarnings(quickScan, deepAnalysis),
|
|
84
|
+
recommendations: generateRecommendations(quickScan, deepAnalysis)
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Cache the results
|
|
88
|
+
await cacheAnalysis(projectRoot, analysis);
|
|
89
|
+
|
|
90
|
+
return analysis;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
// Graceful error handling - return partial analysis
|
|
93
|
+
return {
|
|
94
|
+
projectRoot,
|
|
95
|
+
analyzedAt: new Date().toISOString(),
|
|
96
|
+
error: error.message,
|
|
97
|
+
partial: true,
|
|
98
|
+
projectType: 'unknown',
|
|
99
|
+
language: 'unknown',
|
|
100
|
+
frameworks: [],
|
|
101
|
+
structure: 'unknown',
|
|
102
|
+
domains: [],
|
|
103
|
+
patterns: {},
|
|
104
|
+
samples: {},
|
|
105
|
+
confidence: 0,
|
|
106
|
+
warnings: [`Analysis failed: ${error.message}`],
|
|
107
|
+
recommendations: ['Manual analysis required']
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Phase 1: Quick scan to determine project type and basic structure
|
|
114
|
+
*/
|
|
115
|
+
async function performQuickScan(projectRoot) {
|
|
116
|
+
const scan = {
|
|
117
|
+
projectType: 'unknown',
|
|
118
|
+
structure: 'unknown',
|
|
119
|
+
sourceDirectories: [],
|
|
120
|
+
hasTests: false,
|
|
121
|
+
hasDocs: false,
|
|
122
|
+
isMonorepo: false,
|
|
123
|
+
isEmpty: false
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const entries = fs.readdirSync(projectRoot, { withFileTypes: true });
|
|
128
|
+
|
|
129
|
+
// Check if empty
|
|
130
|
+
if (entries.length === 0) {
|
|
131
|
+
scan.isEmpty = true;
|
|
132
|
+
return scan;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Detect source directories
|
|
136
|
+
const sourceDirPatterns = [
|
|
137
|
+
'src', 'app', 'lib', 'server', 'client', 'packages',
|
|
138
|
+
'Controllers', 'Models', 'Views', 'Services', 'Repositories',
|
|
139
|
+
'cmd', 'internal', 'pkg', 'web', 'frontend', 'backend'
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
if (entry.isDirectory()) {
|
|
144
|
+
const name = entry.name.toLowerCase();
|
|
145
|
+
if (sourceDirPatterns.some(pattern => name === pattern.toLowerCase())) {
|
|
146
|
+
scan.sourceDirectories.push(entry.name);
|
|
147
|
+
}
|
|
148
|
+
if (name === 'test' || name === 'tests' || name === '__tests__') {
|
|
149
|
+
scan.hasTests = true;
|
|
150
|
+
}
|
|
151
|
+
if (name === 'docs' || name === 'documentation') {
|
|
152
|
+
scan.hasDocs = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Detect monorepo
|
|
158
|
+
const monorepoIndicators = [
|
|
159
|
+
'pnpm-workspace.yaml', 'turbo.json', 'nx.json',
|
|
160
|
+
'lerna.json', '.gitmodules', 'workspace.json'
|
|
161
|
+
];
|
|
162
|
+
scan.isMonorepo = monorepoIndicators.some(indicator =>
|
|
163
|
+
fs.existsSync(path.join(projectRoot, indicator))
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Determine project type based on structure
|
|
167
|
+
if (scan.sourceDirectories.length === 0) {
|
|
168
|
+
scan.structure = 'flat';
|
|
169
|
+
} else if (scan.sourceDirectories.includes('src')) {
|
|
170
|
+
scan.structure = 'src-based';
|
|
171
|
+
} else if (scan.sourceDirectories.includes('app')) {
|
|
172
|
+
scan.structure = 'app-based';
|
|
173
|
+
} else if (scan.isMonorepo) {
|
|
174
|
+
scan.structure = 'monorepo';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Determine project type
|
|
178
|
+
const hasPackageJson = fs.existsSync(path.join(projectRoot, 'package.json'));
|
|
179
|
+
const hasRequirements = fs.existsSync(path.join(projectRoot, 'requirements.txt')) ||
|
|
180
|
+
fs.existsSync(path.join(projectRoot, 'pyproject.toml'));
|
|
181
|
+
const hasGoMod = fs.existsSync(path.join(projectRoot, 'go.mod'));
|
|
182
|
+
const hasCargo = fs.existsSync(path.join(projectRoot, 'Cargo.toml'));
|
|
183
|
+
const hasCsproj = entries.some(e => e.name.endsWith('.csproj'));
|
|
184
|
+
const hasPom = fs.existsSync(path.join(projectRoot, 'pom.xml'));
|
|
185
|
+
|
|
186
|
+
if (hasPackageJson) {
|
|
187
|
+
scan.projectType = hasRequirements ? 'polyglot' : 'web-application';
|
|
188
|
+
} else if (hasRequirements) {
|
|
189
|
+
scan.projectType = 'python-application';
|
|
190
|
+
} else if (hasGoMod) {
|
|
191
|
+
scan.projectType = 'go-service';
|
|
192
|
+
} else if (hasCargo) {
|
|
193
|
+
scan.projectType = 'rust-application';
|
|
194
|
+
} else if (hasCsproj) {
|
|
195
|
+
scan.projectType = 'dotnet-application';
|
|
196
|
+
} else if (hasPom) {
|
|
197
|
+
scan.projectType = 'java-application';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
} catch (error) {
|
|
201
|
+
scan.warnings = [`Quick scan error: ${error.message}`];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return scan;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Phase 2: Deep analysis of dependencies, frameworks, and build system
|
|
209
|
+
*/
|
|
210
|
+
async function performDeepAnalysis(projectRoot, quickScan) {
|
|
211
|
+
const analysis = {
|
|
212
|
+
language: 'unknown',
|
|
213
|
+
primaryLanguage: 'unknown',
|
|
214
|
+
frameworks: [],
|
|
215
|
+
techStack: {},
|
|
216
|
+
buildSystem: 'unknown',
|
|
217
|
+
packageManager: 'unknown',
|
|
218
|
+
dependencies: [],
|
|
219
|
+
devDependencies: []
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
// Use tech stack detector for comprehensive analysis
|
|
224
|
+
const techStack = await detectTechStack(projectRoot);
|
|
225
|
+
|
|
226
|
+
analysis.language = techStack.language;
|
|
227
|
+
analysis.primaryLanguage = techStack.primaryLanguage;
|
|
228
|
+
analysis.frameworks = techStack.frameworks;
|
|
229
|
+
analysis.techStack = techStack;
|
|
230
|
+
analysis.buildSystem = techStack.buildSystem || 'unknown';
|
|
231
|
+
analysis.packageManager = techStack.packageManager || 'unknown';
|
|
232
|
+
analysis.dependencies = techStack.dependencies || [];
|
|
233
|
+
analysis.devDependencies = techStack.devDependencies || [];
|
|
234
|
+
|
|
235
|
+
} catch (error) {
|
|
236
|
+
analysis.warnings = [`Deep analysis error: ${error.message}`];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return analysis;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Phase 3: Pattern mining and code sampling
|
|
244
|
+
*/
|
|
245
|
+
async function performPatternMining(projectRoot, quickScan) {
|
|
246
|
+
const mining = {
|
|
247
|
+
patterns: {},
|
|
248
|
+
conventions: {},
|
|
249
|
+
samples: {}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
// Determine source directory to sample from
|
|
254
|
+
const sourceDirs = quickScan.sourceDirectories;
|
|
255
|
+
const primarySourceDir = sourceDirs[0] || '.';
|
|
256
|
+
const sourcePath = path.join(projectRoot, primarySourceDir);
|
|
257
|
+
|
|
258
|
+
if (!fs.existsSync(sourcePath)) {
|
|
259
|
+
mining.warnings = ['Source directory not found'];
|
|
260
|
+
return mining;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Sample files for pattern detection
|
|
264
|
+
const samples = await sampleProjectFiles(sourcePath, 5);
|
|
265
|
+
mining.samples = samples;
|
|
266
|
+
|
|
267
|
+
// Detect patterns from samples
|
|
268
|
+
mining.patterns = detectCodePatterns(samples);
|
|
269
|
+
|
|
270
|
+
// Detect conventions
|
|
271
|
+
mining.conventions = detectConventions(samples, quickScan);
|
|
272
|
+
|
|
273
|
+
} catch (error) {
|
|
274
|
+
mining.warnings = [`Pattern mining error: ${error.message}`];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return mining;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Sample representative files from the project
|
|
282
|
+
*/
|
|
283
|
+
async function sampleProjectFiles(sourcePath, maxFiles = 5) {
|
|
284
|
+
const samples = {
|
|
285
|
+
controllers: [],
|
|
286
|
+
models: [],
|
|
287
|
+
utilities: [],
|
|
288
|
+
components: [],
|
|
289
|
+
configs: [],
|
|
290
|
+
tests: []
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
// Walk the directory tree
|
|
295
|
+
const walkDir = (dir, maxDepth = 3, currentDepth = 0) => {
|
|
296
|
+
if (currentDepth >= maxDepth) return [];
|
|
297
|
+
|
|
298
|
+
const results = [];
|
|
299
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
300
|
+
|
|
301
|
+
for (const entry of entries) {
|
|
302
|
+
const fullPath = path.join(dir, entry.name);
|
|
303
|
+
|
|
304
|
+
if (entry.isDirectory()) {
|
|
305
|
+
// Skip node_modules and similar
|
|
306
|
+
if (!['node_modules', '.git', 'dist', 'build', 'target'].includes(entry.name)) {
|
|
307
|
+
results.push(...walkDir(fullPath, maxDepth, currentDepth + 1));
|
|
308
|
+
}
|
|
309
|
+
} else if (entry.isFile()) {
|
|
310
|
+
const ext = path.extname(entry.name);
|
|
311
|
+
const name = entry.name.toLowerCase();
|
|
312
|
+
|
|
313
|
+
// Categorize files
|
|
314
|
+
if (['.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.rs', '.java', '.cs'].includes(ext)) {
|
|
315
|
+
if (name.includes('controller') || name.includes('handler') || name.includes('route')) {
|
|
316
|
+
samples.controllers.push(fullPath);
|
|
317
|
+
} else if (name.includes('model') || name.includes('entity') || name.includes('schema')) {
|
|
318
|
+
samples.models.push(fullPath);
|
|
319
|
+
} else if (name.includes('util') || name.includes('helper') || name.includes('service')) {
|
|
320
|
+
samples.utilities.push(fullPath);
|
|
321
|
+
} else if (name.includes('component') || name.includes('view')) {
|
|
322
|
+
samples.components.push(fullPath);
|
|
323
|
+
} else if (name.includes('config') || name.includes('setting')) {
|
|
324
|
+
samples.configs.push(fullPath);
|
|
325
|
+
} else if (name.includes('test') || name.includes('spec')) {
|
|
326
|
+
samples.tests.push(fullPath);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
results.push(fullPath);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return results;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
walkDir(sourcePath);
|
|
338
|
+
|
|
339
|
+
// Read content of sampled files (limit to avoid memory issues)
|
|
340
|
+
const readSamples = async (filePath) => {
|
|
341
|
+
try {
|
|
342
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
343
|
+
const relativePath = path.relative(sourcePath, filePath);
|
|
344
|
+
return {
|
|
345
|
+
path: relativePath,
|
|
346
|
+
absolutePath: filePath,
|
|
347
|
+
content: content.length > 10000 ? content.substring(0, 10000) + '...' : content,
|
|
348
|
+
extension: path.extname(filePath)
|
|
349
|
+
};
|
|
350
|
+
} catch (error) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Read up to maxFiles from each category
|
|
356
|
+
const sampledFiles = {};
|
|
357
|
+
for (const [category, files] of Object.entries(samples)) {
|
|
358
|
+
const filesToRead = files.slice(0, Math.ceil(maxFiles / Object.keys(samples).length));
|
|
359
|
+
sampledFiles[category] = (await Promise.all(filesToRead.map(readSamples))).filter(Boolean);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return sampledFiles;
|
|
363
|
+
|
|
364
|
+
} catch (error) {
|
|
365
|
+
return { error: error.message };
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Detect code patterns from sampled files
|
|
371
|
+
*/
|
|
372
|
+
function detectCodePatterns(samples) {
|
|
373
|
+
const patterns = {
|
|
374
|
+
imports: [],
|
|
375
|
+
exports: [],
|
|
376
|
+
styling: [],
|
|
377
|
+
stateManagement: [],
|
|
378
|
+
apiPatterns: [],
|
|
379
|
+
testingFrameworks: []
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
const allFiles = Object.values(samples).flat();
|
|
384
|
+
|
|
385
|
+
for (const sample of allFiles) {
|
|
386
|
+
if (!sample || !sample.content) continue;
|
|
387
|
+
|
|
388
|
+
const content = sample.content;
|
|
389
|
+
const ext = sample.extension;
|
|
390
|
+
|
|
391
|
+
// Detect import patterns
|
|
392
|
+
if (['.js', '.ts', '.jsx', '.tsx'].includes(ext)) {
|
|
393
|
+
const es6Imports = content.match(/import\s+.*?from\s+['"]/g) || [];
|
|
394
|
+
patterns.imports.push(...es6Imports);
|
|
395
|
+
|
|
396
|
+
const commonJsImports = content.match(/require\(['"]/g) || [];
|
|
397
|
+
patterns.imports.push(...commonJsImports);
|
|
398
|
+
|
|
399
|
+
// Detect exports
|
|
400
|
+
const namedExports = content.match(/export\s+(const|function|class)/g) || [];
|
|
401
|
+
patterns.exports.push(...namedExports);
|
|
402
|
+
|
|
403
|
+
const defaultExports = content.match(/export\s+default/g) || [];
|
|
404
|
+
patterns.exports.push(...defaultExports);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Detect styling patterns
|
|
408
|
+
if (content.includes('styled-components') || content.includes('styled')) {
|
|
409
|
+
patterns.styling.push('styled-components');
|
|
410
|
+
}
|
|
411
|
+
if (content.includes('className') || content.includes('class=')) {
|
|
412
|
+
patterns.styling.push('css-classes');
|
|
413
|
+
}
|
|
414
|
+
if (content.includes('@emotion') || content.includes('css`')) {
|
|
415
|
+
patterns.styling.push('emotion');
|
|
416
|
+
}
|
|
417
|
+
if (content.includes('tailwind') || content.includes('tw-')) {
|
|
418
|
+
patterns.styling.push('tailwind');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Detect state management
|
|
422
|
+
if (content.includes('useState') || content.includes('useReducer')) {
|
|
423
|
+
patterns.stateManagement.push('react-hooks');
|
|
424
|
+
}
|
|
425
|
+
if (content.includes('createStore') || content.includes('configureStore')) {
|
|
426
|
+
patterns.stateManagement.push('redux');
|
|
427
|
+
}
|
|
428
|
+
if (content.includes('zustand')) {
|
|
429
|
+
patterns.stateManagement.push('zustand');
|
|
430
|
+
}
|
|
431
|
+
if (content.includes('observable') || content.includes('BehaviorSubject')) {
|
|
432
|
+
patterns.stateManagement.push('rxjs');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Detect API patterns
|
|
436
|
+
if (content.includes('fetch(') || content.includes('axios.') || content.includes('axios.get')) {
|
|
437
|
+
patterns.apiPatterns.push('http-client');
|
|
438
|
+
}
|
|
439
|
+
if (content.includes('graphql') || content.includes('gql`')) {
|
|
440
|
+
patterns.apiPatterns.push('graphql');
|
|
441
|
+
}
|
|
442
|
+
if (content.includes('app.get') || content.includes('app.post') || content.includes('router.')) {
|
|
443
|
+
patterns.apiPatterns.push('express-routes');
|
|
444
|
+
}
|
|
445
|
+
if (content.includes('@Get') || content.includes('@Post') || content.includes('@Controller')) {
|
|
446
|
+
patterns.apiPatterns.push('nestjs-decorators');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Detect testing frameworks
|
|
450
|
+
if (content.includes('describe(') && content.includes('it(')) {
|
|
451
|
+
patterns.testingFrameworks.push('jest');
|
|
452
|
+
}
|
|
453
|
+
if (content.includes('test(') || content.includes('describe(')) {
|
|
454
|
+
patterns.testingFrameworks.push('vitest');
|
|
455
|
+
}
|
|
456
|
+
if (content.includes('def test_') || content.includes('class Test')) {
|
|
457
|
+
patterns.testingFrameworks.push('pytest');
|
|
458
|
+
}
|
|
459
|
+
if (content.includes('func Test') || content.includes('t.Run(')) {
|
|
460
|
+
patterns.testingFrameworks.push('go-testing');
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Remove duplicates
|
|
465
|
+
for (const key of Object.keys(patterns)) {
|
|
466
|
+
patterns[key] = [...new Set(patterns[key])];
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
} catch (error) {
|
|
470
|
+
patterns.error = error.message;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return patterns;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Detect project conventions from samples
|
|
478
|
+
*/
|
|
479
|
+
function detectConventions(samples, quickScan) {
|
|
480
|
+
const conventions = {
|
|
481
|
+
naming: {},
|
|
482
|
+
fileOrganization: quickScan.structure,
|
|
483
|
+
importStyle: [],
|
|
484
|
+
codeStyle: []
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
const allFiles = Object.values(samples).flat();
|
|
489
|
+
|
|
490
|
+
for (const sample of allFiles) {
|
|
491
|
+
if (!sample || !sample.content) continue;
|
|
492
|
+
|
|
493
|
+
const content = sample.content;
|
|
494
|
+
const fileName = path.basename(sample.path);
|
|
495
|
+
|
|
496
|
+
// Detect naming conventions
|
|
497
|
+
if (fileName.includes('-')) {
|
|
498
|
+
conventions.naming.files = 'kebab-case';
|
|
499
|
+
} else if (fileName.includes('_')) {
|
|
500
|
+
conventions.naming.files = 'snake_case';
|
|
501
|
+
} else if (/[A-Z]/.test(fileName[0])) {
|
|
502
|
+
conventions.naming.files = 'PascalCase';
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Detect import style
|
|
506
|
+
if (content.includes('import')) {
|
|
507
|
+
conventions.importStyle.push('es6');
|
|
508
|
+
}
|
|
509
|
+
if (content.includes('require(')) {
|
|
510
|
+
conventions.importStyle.push('commonjs');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Detect code style
|
|
514
|
+
if (content.includes('async') && content.includes('await')) {
|
|
515
|
+
conventions.codeStyle.push('async-await');
|
|
516
|
+
}
|
|
517
|
+
if (content.includes('=>') || content.includes('=>')) {
|
|
518
|
+
conventions.codeStyle.push('arrow-functions');
|
|
519
|
+
}
|
|
520
|
+
if (content.includes('class ') && content.includes('extends')) {
|
|
521
|
+
conventions.codeStyle.push('class-based');
|
|
522
|
+
}
|
|
523
|
+
if (content.includes('function ') && !content.includes('=>')) {
|
|
524
|
+
conventions.codeStyle.push('functional');
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Remove duplicates
|
|
529
|
+
conventions.importStyle = [...new Set(conventions.importStyle)];
|
|
530
|
+
conventions.codeStyle = [...new Set(conventions.codeStyle)];
|
|
531
|
+
|
|
532
|
+
} catch (error) {
|
|
533
|
+
conventions.error = error.message;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return conventions;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Calculate overall confidence score
|
|
541
|
+
*/
|
|
542
|
+
function calculateOverallConfidence(quickScan, deepAnalysis, patternMining) {
|
|
543
|
+
let confidence = 0;
|
|
544
|
+
let factors = 0;
|
|
545
|
+
|
|
546
|
+
// Language detection confidence
|
|
547
|
+
if (deepAnalysis.language && deepAnalysis.language !== 'unknown') {
|
|
548
|
+
confidence += 0.3;
|
|
549
|
+
factors++;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Framework detection
|
|
553
|
+
if (deepAnalysis.frameworks.length > 0) {
|
|
554
|
+
confidence += 0.2;
|
|
555
|
+
factors++;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Structure detection
|
|
559
|
+
if (quickScan.structure && quickScan.structure !== 'unknown') {
|
|
560
|
+
confidence += 0.2;
|
|
561
|
+
factors++;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Pattern detection
|
|
565
|
+
if (patternMining.patterns && Object.keys(patternMining.patterns).length > 0) {
|
|
566
|
+
const patternCount = Object.values(patternMining.patterns)
|
|
567
|
+
.filter(arr => Array.isArray(arr) && arr.length > 0).length;
|
|
568
|
+
confidence += Math.min(patternCount * 0.1, 0.3);
|
|
569
|
+
factors++;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return Math.min(confidence, 1.0);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Generate warnings based on analysis
|
|
577
|
+
*/
|
|
578
|
+
function generateWarnings(quickScan, deepAnalysis) {
|
|
579
|
+
const warnings = [];
|
|
580
|
+
|
|
581
|
+
if (quickScan.isEmpty) {
|
|
582
|
+
warnings.push('Project appears to be empty');
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (deepAnalysis.language === 'unknown') {
|
|
586
|
+
warnings.push('Could not detect programming language');
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (!quickScan.hasTests) {
|
|
590
|
+
warnings.push('No test directory detected');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (quickScan.isMonorepo) {
|
|
594
|
+
warnings.push('Monorepo detected - analysis may be incomplete');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return warnings;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Generate recommendations based on analysis
|
|
602
|
+
*/
|
|
603
|
+
function generateRecommendations(quickScan, deepAnalysis) {
|
|
604
|
+
const recommendations = [];
|
|
605
|
+
|
|
606
|
+
if (!quickScan.hasTests) {
|
|
607
|
+
recommendations.push('Consider adding test coverage');
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (!quickScan.hasDocs) {
|
|
611
|
+
recommendations.push('Consider adding documentation');
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (deepAnalysis.language !== 'unknown' && deepAnalysis.frameworks.length === 0) {
|
|
615
|
+
recommendations.push('No frameworks detected - may need manual configuration');
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return recommendations;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Load cached analysis if available and fresh
|
|
623
|
+
*/
|
|
624
|
+
async function loadCachedAnalysis(projectRoot) {
|
|
625
|
+
try {
|
|
626
|
+
const cachePath = path.join(projectRoot, '.agentful', 'analysis-cache.json');
|
|
627
|
+
|
|
628
|
+
if (!fs.existsSync(cachePath)) {
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const cached = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
|
|
633
|
+
const cacheAge = Date.now() - new Date(cached.analyzedAt).getTime();
|
|
634
|
+
|
|
635
|
+
// Check if cache is still valid
|
|
636
|
+
if (cacheAge < CACHE_DURATION && cached.cacheVersion === CACHE_VERSION) {
|
|
637
|
+
return cached;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return null;
|
|
641
|
+
} catch (error) {
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Cache analysis results
|
|
648
|
+
*/
|
|
649
|
+
async function cacheAnalysis(projectRoot, analysis) {
|
|
650
|
+
try {
|
|
651
|
+
const agentfulDir = path.join(projectRoot, '.agentful');
|
|
652
|
+
|
|
653
|
+
if (!fs.existsSync(agentfulDir)) {
|
|
654
|
+
fs.mkdirSync(agentfulDir, { recursive: true });
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const cachePath = path.join(agentfulDir, 'analysis-cache.json');
|
|
658
|
+
fs.writeFileSync(cachePath, JSON.stringify(analysis, null, 2));
|
|
659
|
+
|
|
660
|
+
} catch (error) {
|
|
661
|
+
// Silent fail - caching is optional
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Export analysis results to architecture.json
|
|
667
|
+
*/
|
|
668
|
+
export async function exportToArchitectureJson(projectRoot, analysis) {
|
|
669
|
+
try {
|
|
670
|
+
const agentfulDir = path.join(projectRoot, '.agentful');
|
|
671
|
+
const archPath = path.join(agentfulDir, 'architecture.json');
|
|
672
|
+
|
|
673
|
+
const architecture = {
|
|
674
|
+
analysis_date: analysis.analyzedAt,
|
|
675
|
+
project_type: analysis.projectType,
|
|
676
|
+
detected_patterns: {
|
|
677
|
+
framework: analysis.frameworks[0] || 'unknown',
|
|
678
|
+
language: analysis.language,
|
|
679
|
+
primary_language: analysis.primaryLanguage,
|
|
680
|
+
structure: analysis.structure,
|
|
681
|
+
build_system: analysis.buildSystem,
|
|
682
|
+
package_manager: analysis.packageManager
|
|
683
|
+
},
|
|
684
|
+
tech_stack: analysis.techStack,
|
|
685
|
+
domains: analysis.domains,
|
|
686
|
+
patterns: analysis.patterns,
|
|
687
|
+
conventions: analysis.conventions,
|
|
688
|
+
generated_agents: [],
|
|
689
|
+
key_conventions_discovered: analysis.conventions.codeStyle || [],
|
|
690
|
+
confidence: analysis.confidence,
|
|
691
|
+
warnings: analysis.warnings,
|
|
692
|
+
recommendations: analysis.recommendations
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
fs.writeFileSync(archPath, JSON.stringify(architecture, null, 2));
|
|
696
|
+
|
|
697
|
+
return architecture;
|
|
698
|
+
} catch (error) {
|
|
699
|
+
throw new Error(`Failed to export architecture.json: ${error.message}`);
|
|
700
|
+
}
|
|
701
|
+
}
|