@itz4blitz/agentful 0.1.0 → 0.1.5

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.
@@ -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
+ }