sdd-mcp-server 1.3.4 → 1.3.6

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.
@@ -26,18 +26,33 @@ export async function analyzeProject(projectPath) {
26
26
  packageManager: 'npm'
27
27
  };
28
28
 
29
+ console.log(`Analyzing project at: ${projectPath}`);
30
+
29
31
  try {
30
32
  // Check for package.json
31
33
  const packageJsonPath = path.join(projectPath, 'package.json');
32
34
  if (fs.existsSync(packageJsonPath)) {
33
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
34
- analysis.name = packageJson.name || analysis.name;
35
- analysis.description = packageJson.description || analysis.description;
36
- analysis.version = packageJson.version || analysis.version;
37
- analysis.type = packageJson.type === 'module' ? 'ES Module' : 'CommonJS';
38
- analysis.dependencies = Object.keys(packageJson.dependencies || {});
39
- analysis.devDependencies = Object.keys(packageJson.devDependencies || {});
40
- analysis.scripts = packageJson.scripts || {};
35
+ console.log('Found package.json, parsing...');
36
+ try {
37
+ const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
38
+ const packageJson = JSON.parse(packageJsonContent);
39
+
40
+ // Extract project information with better fallbacks
41
+ analysis.name = packageJson.name || getDirectoryBasedName(projectPath);
42
+ analysis.description = packageJson.description || generateSmartDescription(analysis.name);
43
+ analysis.version = packageJson.version || '1.0.0';
44
+ analysis.type = packageJson.type === 'module' ? 'ES Module' : 'CommonJS';
45
+ analysis.dependencies = Object.keys(packageJson.dependencies || {});
46
+ analysis.devDependencies = Object.keys(packageJson.devDependencies || {});
47
+ analysis.scripts = packageJson.scripts || {};
48
+
49
+ console.log(`Extracted: name=${analysis.name}, deps=${analysis.dependencies.length}, devDeps=${analysis.devDependencies.length}`);
50
+ } catch (parseError) {
51
+ console.log('Error parsing package.json:', parseError.message);
52
+ // Use fallbacks if package.json is malformed
53
+ analysis.name = getDirectoryBasedName(projectPath);
54
+ analysis.description = generateSmartDescription(analysis.name);
55
+ }
41
56
 
42
57
  // Detect framework
43
58
  if (analysis.dependencies.includes('express') || analysis.devDependencies.includes('express')) {
@@ -87,6 +102,61 @@ export async function analyzeProject(projectPath) {
87
102
  } else if (analysis.scripts.build?.includes('rollup')) {
88
103
  analysis.buildTool = 'Rollup';
89
104
  }
105
+ } else {
106
+ console.log('No package.json found, using directory-based fallbacks');
107
+ // No package.json found, use directory-based fallbacks
108
+ analysis.name = getDirectoryBasedName(projectPath);
109
+ analysis.description = generateSmartDescription(analysis.name);
110
+ }
111
+
112
+ // Check for Java/Maven/Gradle projects
113
+ const pomPath = path.join(projectPath, 'pom.xml');
114
+ const gradlePath = path.join(projectPath, 'build.gradle');
115
+ const gradleKtsPath = path.join(projectPath, 'build.gradle.kts');
116
+ if (fs.existsSync(pomPath) || fs.existsSync(gradlePath) || fs.existsSync(gradleKtsPath)) {
117
+ analysis.language = 'java';
118
+ if (fs.existsSync(pomPath)) {
119
+ analysis.packageManager = 'maven';
120
+ analysis.buildTool = 'Maven';
121
+ try {
122
+ const pom = fs.readFileSync(pomPath, 'utf8');
123
+ if (/spring-boot/i.test(pom) || /org\.springframework\.boot/i.test(pom)) {
124
+ analysis.framework = 'Spring Boot';
125
+ analysis.architecture = 'Spring Boot Application';
126
+ }
127
+ // Detect modules in aggregator POM
128
+ const moduleMatches = pom.match(/<module>[^<]+<\/module>/g) || [];
129
+ if (moduleMatches.length > 1) {
130
+ analysis.architecture = 'Microservices (Spring Boot)';
131
+ }
132
+ if (/junit|jupiter/i.test(pom)) {
133
+ analysis.testFramework = 'JUnit';
134
+ analysis.hasTests = true;
135
+ }
136
+ } catch {}
137
+ } else {
138
+ analysis.packageManager = 'gradle';
139
+ analysis.buildTool = 'Gradle';
140
+ try {
141
+ const gradle = fs.readFileSync(fs.existsSync(gradlePath) ? gradlePath : gradleKtsPath, 'utf8');
142
+ if (/org\.springframework\.boot|spring-boot/i.test(gradle)) {
143
+ analysis.framework = 'Spring Boot';
144
+ analysis.architecture = 'Spring Boot Application';
145
+ }
146
+ if (/subprojects\s*\{|include\s+\(/i.test(gradle)) {
147
+ analysis.architecture = 'Microservices (Spring Boot)';
148
+ }
149
+ if (/junit|jupiter|testImplementation\s+['"]org\.junit/i.test(gradle)) {
150
+ analysis.testFramework = 'JUnit';
151
+ analysis.hasTests = true;
152
+ }
153
+ } catch {}
154
+ }
155
+ // Detect conventional test dirs
156
+ if (fs.existsSync(path.join(projectPath, 'src', 'test', 'java'))) {
157
+ analysis.hasTests = true;
158
+ if (!analysis.testFramework) analysis.testFramework = 'JUnit';
159
+ }
90
160
  }
91
161
 
92
162
  // Check for yarn or pnpm
@@ -143,8 +213,93 @@ export async function analyzeProject(projectPath) {
143
213
 
144
214
  } catch (error) {
145
215
  console.error('Error analyzing project:', error);
216
+ // Even if analysis fails, provide meaningful fallbacks
217
+ analysis.name = getDirectoryBasedName(projectPath);
218
+ analysis.description = generateSmartDescription(analysis.name);
146
219
  }
147
220
 
221
+ // Validate and improve analysis results before returning
222
+ const finalAnalysis = validateAndImproveAnalysis(analysis, projectPath);
223
+ console.log(`Final analysis: ${finalAnalysis.name} - ${finalAnalysis.architecture}`);
224
+
225
+ return finalAnalysis;
226
+ }
227
+
228
+ /**
229
+ * Extract project name from directory path
230
+ */
231
+ function getDirectoryBasedName(projectPath) {
232
+ const dirName = path.basename(projectPath);
233
+ // Convert kebab-case, snake_case to proper names
234
+ return dirName
235
+ .replace(/[-_]/g, ' ')
236
+ .split(' ')
237
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
238
+ .join(' ');
239
+ }
240
+
241
+ /**
242
+ * Generate smart description based on project name and structure
243
+ */
244
+ function generateSmartDescription(projectName) {
245
+ const name = projectName.toLowerCase();
246
+
247
+ if (name.includes('api')) {
248
+ return `RESTful API service for ${projectName.replace(/api/i, '').trim()} application`;
249
+ }
250
+ if (name.includes('server')) {
251
+ return `Server application for ${projectName.replace(/server/i, '').trim()} services`;
252
+ }
253
+ if (name.includes('client')) {
254
+ return `Client application for ${projectName.replace(/client/i, '').trim()} interaction`;
255
+ }
256
+ if (name.includes('mcp')) {
257
+ return 'Model Context Protocol (MCP) server for AI tool integration';
258
+ }
259
+ if (name.includes('bot')) {
260
+ return `Bot application for automated ${projectName.replace(/bot/i, '').trim()} tasks`;
261
+ }
262
+ if (name.includes('web')) {
263
+ return `Web application for ${projectName.replace(/web/i, '').trim()} services`;
264
+ }
265
+ if (name.includes('tool')) {
266
+ return `Development tool for ${projectName.replace(/tool/i, '').trim()} workflows`;
267
+ }
268
+
269
+ return `${projectName} application providing core functionality and services`;
270
+ }
271
+
272
+ /**
273
+ * Validate analysis results and improve them with smart fallbacks
274
+ */
275
+ function validateAndImproveAnalysis(analysis, projectPath) {
276
+ // Improve architecture detection based on available information
277
+ if (analysis.architecture === 'unknown') {
278
+ if (analysis.dependencies.includes('@modelcontextprotocol/sdk')) {
279
+ analysis.architecture = 'Model Context Protocol Server';
280
+ } else if (analysis.dependencies.includes('express')) {
281
+ analysis.architecture = 'REST API Server';
282
+ } else if (analysis.dependencies.includes('react')) {
283
+ analysis.architecture = 'Frontend Application';
284
+ } else if (analysis.hasTests && analysis.directories.length > 3) {
285
+ analysis.architecture = 'Full-stack Application';
286
+ } else if (analysis.scripts.build) {
287
+ analysis.architecture = 'Build-based Application';
288
+ } else {
289
+ analysis.architecture = 'Node.js Application';
290
+ }
291
+ }
292
+
293
+ // Ensure version is meaningful
294
+ if (analysis.version === '0.0.0' && analysis.dependencies.length > 0) {
295
+ analysis.version = '1.0.0';
296
+ }
297
+
298
+ // Ensure type is meaningful
299
+ if (analysis.type === 'unknown' && analysis.dependencies.length > 0) {
300
+ analysis.type = 'Application';
301
+ }
302
+
148
303
  return analysis;
149
304
  }
150
305
 
@@ -152,6 +307,12 @@ export async function analyzeProject(projectPath) {
152
307
  * Generates dynamic product.md content based on project analysis
153
308
  */
154
309
  export function generateProductDocument(analysis) {
310
+ // Validate analysis has meaningful content
311
+ if (isAnalysisGeneric(analysis)) {
312
+ console.log('Analysis appears generic, enhancing with intelligent defaults');
313
+ analysis = enhanceGenericAnalysis(analysis);
314
+ }
315
+
155
316
  const features = extractFeatures(analysis);
156
317
  const valueProps = generateValuePropositions(analysis);
157
318
  const targetUsers = identifyTargetUsers(analysis);
@@ -185,6 +346,54 @@ ${generateTechnicalAdvantages(analysis).map(a => `- ${a}`).join('\n')}
185
346
  `;
186
347
  }
187
348
 
349
+ /**
350
+ * Check if analysis contains only generic/default values
351
+ */
352
+ function isAnalysisGeneric(analysis) {
353
+ return analysis.name === 'Unknown Project' ||
354
+ analysis.description === 'No description available' ||
355
+ analysis.version === '0.0.0' ||
356
+ analysis.architecture === 'unknown';
357
+ }
358
+
359
+ /**
360
+ * Enhance generic analysis with intelligent defaults
361
+ */
362
+ function enhanceGenericAnalysis(analysis) {
363
+ const enhanced = { ...analysis };
364
+
365
+ // If still using defaults, make educated guesses based on directory structure and files
366
+ if (enhanced.name === 'Unknown Project') {
367
+ enhanced.name = getDirectoryBasedName(process.cwd());
368
+ }
369
+
370
+ if (enhanced.description === 'No description available') {
371
+ enhanced.description = generateSmartDescription(enhanced.name);
372
+ }
373
+
374
+ if (enhanced.version === '0.0.0') {
375
+ enhanced.version = '1.0.0';
376
+ }
377
+
378
+ if (enhanced.architecture === 'unknown') {
379
+ // Make intelligent architecture guesses
380
+ const name = enhanced.name.toLowerCase();
381
+ if (name.includes('server') || name.includes('api')) {
382
+ enhanced.architecture = 'Server Application';
383
+ } else if (name.includes('client') || name.includes('frontend')) {
384
+ enhanced.architecture = 'Frontend Application';
385
+ } else if (name.includes('mcp')) {
386
+ enhanced.architecture = 'Model Context Protocol Server';
387
+ } else if (enhanced.directories.includes('src')) {
388
+ enhanced.architecture = 'Source-based Application';
389
+ } else {
390
+ enhanced.architecture = 'Node.js Application';
391
+ }
392
+ }
393
+
394
+ return enhanced;
395
+ }
396
+
188
397
  /**
189
398
  * Generates dynamic tech.md content based on project analysis
190
399
  */
@@ -197,8 +406,8 @@ export function generateTechDocument(analysis) {
197
406
 
198
407
  ## Architecture
199
408
  **Type**: ${analysis.architecture}
200
- **Language**: ${analysis.language === 'typescript' ? 'TypeScript' : 'JavaScript'}
201
- **Module System**: ${analysis.type}
409
+ **Language**: ${analysis.language === 'typescript' ? 'TypeScript' : analysis.language === 'java' ? 'Java' : analysis.language === 'python' ? 'Python' : analysis.language === 'go' ? 'Go' : analysis.language === 'ruby' ? 'Ruby' : analysis.language === 'php' ? 'PHP' : analysis.language === 'rust' ? 'Rust' : analysis.language === 'csharp' ? 'C#' : analysis.language === 'scala' ? 'Scala' : 'JavaScript'}
410
+ ${(analysis.language === 'javascript' || analysis.language === 'typescript') ? `**Module System**: ${analysis.type} ` : ''}
202
411
  ${analysis.framework ? `**Framework**: ${analysis.framework}` : ''}
203
412
  ${analysis.buildTool ? `**Build Tool**: ${analysis.buildTool}` : ''}
204
413
 
@@ -208,9 +417,8 @@ ${architecture}
208
417
  ${techStack.map(t => `- **${t.name}**: ${t.description}`).join('\n')}
209
418
 
210
419
  ## Development Environment
211
- - **Node Version**: ${getNodeVersion()}
212
- - **Package Manager**: ${analysis.packageManager}
213
- - **Language**: ${analysis.language === 'typescript' ? 'TypeScript with type safety' : 'JavaScript'}
420
+ ${analysis.language === 'java' ? `- **JDK**: ${getJavaVersion(projectPathFromCwd())}\n` : analysis.language === 'go' ? `- **Go**: ${getGoVersion(projectPathFromCwd())}\n` : analysis.language === 'python' ? `- **Python**: ${getPythonVersion(projectPathFromCwd())}\n` : analysis.language === 'ruby' ? `- **Ruby**: ${getRubyVersion(projectPathFromCwd())}\n` : analysis.language === 'php' ? `- **PHP**: ${getPhpVersion(projectPathFromCwd())}\n` : analysis.language === 'rust' ? `- **Rust**: ${getRustToolchain(projectPathFromCwd())}\n` : analysis.language === 'csharp' ? `- **.NET SDK**: ${getDotnetTarget(projectPathFromCwd())}\n` : `- **Node Version**: ${getNodeVersion()}\n`}- **Package Manager/Build**: ${analysis.packageManager}
421
+ - **Language**: ${analysis.language === 'typescript' ? 'TypeScript with type safety' : analysis.language ? analysis.language[0].toUpperCase() + analysis.language.slice(1) : 'JavaScript'}
214
422
  ${analysis.testFramework ? `- **Testing**: ${analysis.testFramework}` : ''}
215
423
 
216
424
  ## Dependencies Analysis
@@ -301,7 +509,31 @@ function extractFeatures(analysis) {
301
509
  features.push('Spec-driven development workflow');
302
510
  }
303
511
 
304
- return features.length > 0 ? features : ['Core application functionality'];
512
+ // Ensure we always have meaningful features
513
+ if (features.length === 0) {
514
+ // Add intelligent default features based on project characteristics
515
+ features.push('Core application functionality');
516
+
517
+ if (analysis.architecture.includes('Server')) {
518
+ features.push('Server-side request processing');
519
+ features.push('API endpoint management');
520
+ }
521
+
522
+ if (analysis.architecture.includes('Frontend')) {
523
+ features.push('User interface components');
524
+ features.push('Client-side interaction handling');
525
+ }
526
+
527
+ if (analysis.directories.includes('src')) {
528
+ features.push('Modular source code organization');
529
+ }
530
+
531
+ if (analysis.language === 'typescript' || analysis.language === 'javascript') {
532
+ features.push('JavaScript/Node.js runtime environment');
533
+ }
534
+ }
535
+
536
+ return features;
305
537
  }
306
538
 
307
539
  function generateValuePropositions(analysis) {
@@ -335,6 +567,28 @@ function generateValuePropositions(analysis) {
335
567
  });
336
568
  }
337
569
 
570
+ // Ensure we always have meaningful value propositions
571
+ if (props.length === 0) {
572
+ props.push({
573
+ title: 'Development Efficiency',
574
+ description: 'Streamlined development process with modern tooling'
575
+ });
576
+
577
+ if (analysis.architecture.includes('Server')) {
578
+ props.push({
579
+ title: 'Scalable Architecture',
580
+ description: 'Server-based design supports multiple clients and scaling'
581
+ });
582
+ }
583
+
584
+ if (analysis.directories.includes('src')) {
585
+ props.push({
586
+ title: 'Maintainable Codebase',
587
+ description: 'Organized source structure facilitates long-term maintenance'
588
+ });
589
+ }
590
+ }
591
+
338
592
  return props;
339
593
  }
340
594
 
@@ -416,27 +670,25 @@ function generateTechnicalAdvantages(analysis) {
416
670
 
417
671
  function buildTechStack(analysis) {
418
672
  const stack = [];
419
-
420
673
  // Core runtime
421
- stack.push({
422
- name: 'Node.js',
423
- description: 'JavaScript runtime for server-side execution'
424
- });
425
-
426
- // Language
674
+ if (analysis.language === 'java') stack.push({ name: 'Java', description: 'JDK runtime for backend services' });
675
+ else if (analysis.language === 'python') stack.push({ name: 'Python', description: 'Python runtime for applications and APIs' });
676
+ else if (analysis.language === 'go') stack.push({ name: 'Go', description: 'Go toolchain for building static binaries' });
677
+ else if (analysis.language === 'ruby') stack.push({ name: 'Ruby', description: 'Ruby runtime for web applications' });
678
+ else if (analysis.language === 'php') stack.push({ name: 'PHP', description: 'PHP runtime for web applications' });
679
+ else if (analysis.language === 'rust') stack.push({ name: 'Rust', description: 'Rust toolchain for systems and APIs' });
680
+ else if (analysis.language === 'csharp') stack.push({ name: 'C#/.NET', description: '.NET runtime and SDK' });
681
+ else if (analysis.language === 'scala') stack.push({ name: 'Scala', description: 'JVM language for backend systems' });
682
+ else stack.push({ name: 'Node.js', description: 'JavaScript runtime for server-side execution' });
683
+
684
+ // Language/Framework highlights
427
685
  if (analysis.language === 'typescript') {
428
- stack.push({
429
- name: 'TypeScript',
430
- description: 'Typed superset of JavaScript for enhanced developer experience'
431
- });
686
+ stack.push({ name: 'TypeScript', description: 'Typed superset of JavaScript for enhanced developer experience' });
687
+ } else if (analysis.language === 'java') {
688
+ stack.push({ name: 'Spring Boot', description: 'Opinionated framework for building production-ready services' });
432
689
  }
433
-
434
- // Framework
435
690
  if (analysis.framework) {
436
- stack.push({
437
- name: analysis.framework,
438
- description: getFrameworkDescription(analysis.framework)
439
- });
691
+ stack.push({ name: analysis.framework, description: getFrameworkDescription(analysis.framework) });
440
692
  }
441
693
 
442
694
  // Testing
@@ -469,33 +721,79 @@ function buildTechStack(analysis) {
469
721
  }
470
722
 
471
723
  function extractDevCommands(analysis) {
472
- if (Object.keys(analysis.scripts).length === 0) {
473
- return 'No npm scripts defined';
474
- }
475
-
476
724
  let commands = '```bash\n';
477
-
478
- // Common commands in order of importance
479
- const commandOrder = ['dev', 'start', 'build', 'test', 'lint', 'typecheck', 'coverage'];
480
-
481
- for (const cmd of commandOrder) {
482
- if (analysis.scripts[cmd]) {
483
- commands += `${analysis.packageManager} run ${cmd} # ${describeCommand(cmd, analysis.scripts[cmd])}\n`;
484
- }
485
- }
486
-
487
- // Add other commands
488
- for (const [cmd, script] of Object.entries(analysis.scripts)) {
489
- if (!commandOrder.includes(cmd)) {
490
- commands += `${analysis.packageManager} run ${cmd} # ${script.substring(0, 50)}${script.length > 50 ? '...' : ''}\n`;
491
- }
725
+ switch (analysis.language) {
726
+ case 'java':
727
+ if (analysis.packageManager === 'maven') {
728
+ commands += 'mvn clean install # Build project\n';
729
+ commands += 'mvn test # Run tests\n';
730
+ if (analysis.framework === 'Spring Boot') commands += 'mvn spring-boot:run # Run application\n';
731
+ } else {
732
+ commands += 'gradle build # Build project\n';
733
+ commands += 'gradle test # Run tests\n';
734
+ if (analysis.framework === 'Spring Boot') commands += 'gradle bootRun # Run application\n';
735
+ }
736
+ break;
737
+ case 'python':
738
+ commands += 'pip install -r requirements.txt # Install deps\n';
739
+ commands += (analysis.framework === 'Django') ? 'python manage.py runserver # Run server\n' :
740
+ (analysis.framework === 'FastAPI' || analysis.framework === 'Flask') ? 'uvicorn app:app --reload # Run dev server\n' : '';
741
+ commands += 'pytest # Run tests\n';
742
+ break;
743
+ case 'go':
744
+ commands += 'go build ./... # Build\n';
745
+ commands += 'go test ./... # Tests\n';
746
+ commands += 'go run ./cmd/... # Run (example)\n';
747
+ break;
748
+ case 'ruby':
749
+ commands += 'bundle install # Install deps\n';
750
+ commands += (analysis.framework === 'Rails') ? 'rails server # Run server\n' : '';
751
+ commands += (analysis.testFramework === 'RSpec') ? 'rspec # Run tests\n' : 'rake test # Run tests\n';
752
+ break;
753
+ case 'php':
754
+ commands += 'composer install # Install deps\n';
755
+ commands += (analysis.framework === 'Laravel') ? 'php artisan serve # Run server\n' : '';
756
+ commands += 'vendor/bin/phpunit # Run tests\n';
757
+ break;
758
+ case 'rust':
759
+ commands += 'cargo build # Build\n';
760
+ commands += 'cargo test # Tests\n';
761
+ commands += 'cargo run # Run\n';
762
+ break;
763
+ case 'csharp':
764
+ commands += 'dotnet build # Build\n';
765
+ commands += 'dotnet test # Tests\n';
766
+ commands += 'dotnet run # Run\n';
767
+ break;
768
+ case 'scala':
769
+ commands += 'sbt compile # Build\n';
770
+ commands += 'sbt test # Tests\n';
771
+ commands += 'sbt run # Run\n';
772
+ break;
773
+ default:
774
+ if (Object.keys(analysis.scripts).length === 0) return 'No npm scripts defined';
775
+ const order = ['dev', 'start', 'build', 'test', 'lint', 'typecheck', 'coverage'];
776
+ for (const cmd of order) {
777
+ if (analysis.scripts[cmd]) commands += `${analysis.packageManager} run ${cmd} # ${describeCommand(cmd, analysis.scripts[cmd])}\n`;
778
+ }
779
+ for (const [cmd, script] of Object.entries(analysis.scripts)) {
780
+ if (!order.includes(cmd)) commands += `${analysis.packageManager} run ${cmd} # ${script.substring(0, 50)}${script.length > 50 ? '...' : ''}\n`;
781
+ }
492
782
  }
493
-
494
783
  commands += '```';
495
784
  return commands;
496
785
  }
497
786
 
498
787
  function describeArchitecture(analysis) {
788
+ if (analysis.architecture.includes('Spring Boot')) {
789
+ return `
790
+ ### Spring Boot Service Architecture
791
+ The project uses Spring Boot conventions:
792
+ - **Configuration**: application.yml/properties per service
793
+ - **Layers**: Controller → Service → Repository
794
+ - **Build**: ${analysis.buildTool || 'Maven/Gradle'} with ${analysis.testFramework || 'JUnit'} tests
795
+ ${analysis.architecture.includes('Microservices') ? '- **Topology**: Multiple modules/services (microservices)\n' : ''}`;
796
+ }
499
797
  if (analysis.architecture === 'Domain-Driven Design (DDD)') {
500
798
  return `
501
799
  ### Domain-Driven Design Architecture
@@ -647,7 +945,15 @@ function buildDirectoryTree(analysis) {
647
945
  tree += `├── Dockerfile # Container configuration\n`;
648
946
  }
649
947
 
650
- tree += `├── package.json # Project configuration\n`;
948
+ if (analysis.language === 'java') tree += `├── pom.xml or build.gradle # Build configuration\n`;
949
+ else if (analysis.language === 'python') tree += `├── pyproject.toml / requirements.txt # Python config\n`;
950
+ else if (analysis.language === 'go') tree += `├── go.mod # Go modules\n`;
951
+ else if (analysis.language === 'ruby') tree += `├── Gemfile # Ruby dependencies\n`;
952
+ else if (analysis.language === 'php') tree += `├── composer.json # PHP dependencies\n`;
953
+ else if (analysis.language === 'rust') tree += `├── Cargo.toml # Rust package config\n`;
954
+ else if (analysis.language === 'csharp') tree += `├── *.csproj # .NET project file\n`;
955
+ else if (analysis.language === 'scala') tree += `├── build.sbt # SBT build\n`;
956
+ else tree += `├── package.json # Project configuration\n`;
651
957
 
652
958
  if (analysis.language === 'typescript') {
653
959
  tree += `├── tsconfig.json # TypeScript configuration\n`;
@@ -929,4 +1235,101 @@ function getNodeVersion() {
929
1235
  // Ignore
930
1236
  }
931
1237
  return '>= 18.0.0';
932
- }
1238
+ }
1239
+ function getGoVersion(projectPath) {
1240
+ try {
1241
+ const gomod = path.join(projectPath, 'go.mod');
1242
+ if (fs.existsSync(gomod)) {
1243
+ const content = fs.readFileSync(gomod, 'utf8');
1244
+ const m = content.match(/^go\s+([0-9.]+)/m);
1245
+ if (m) return `Go ${m[1]}`;
1246
+ }
1247
+ } catch {}
1248
+ return 'Go (version unknown)';
1249
+ }
1250
+ function getPythonVersion(projectPath) {
1251
+ try {
1252
+ const pyproject = path.join(projectPath, 'pyproject.toml');
1253
+ if (fs.existsSync(pyproject)) {
1254
+ const txt = fs.readFileSync(pyproject, 'utf8');
1255
+ const m = txt.match(/python\s*[=><~!]*\s*['"]([^'"]+)['"]/i);
1256
+ if (m) return `Python ${m[1]}`;
1257
+ }
1258
+ const vfile = path.join(projectPath, '.python-version');
1259
+ if (fs.existsSync(vfile)) {
1260
+ return `Python ${fs.readFileSync(vfile, 'utf8').trim()}`;
1261
+ }
1262
+ } catch {}
1263
+ return 'Python (version unknown)';
1264
+ }
1265
+ function getRubyVersion(projectPath) {
1266
+ try {
1267
+ const rv = path.join(projectPath, '.ruby-version');
1268
+ if (fs.existsSync(rv)) return `Ruby ${fs.readFileSync(rv, 'utf8').trim()}`;
1269
+ const gem = path.join(projectPath, 'Gemfile');
1270
+ if (fs.existsSync(gem)) {
1271
+ const txt = fs.readFileSync(gem, 'utf8');
1272
+ const m = txt.match(/ruby\s+['"]([^'"]+)['"]/i);
1273
+ if (m) return `Ruby ${m[1]}`;
1274
+ }
1275
+ } catch {}
1276
+ return 'Ruby (version unknown)';
1277
+ }
1278
+ function getPhpVersion(projectPath) {
1279
+ try {
1280
+ const composer = path.join(projectPath, 'composer.json');
1281
+ if (fs.existsSync(composer)) {
1282
+ const pkg = JSON.parse(fs.readFileSync(composer, 'utf8'));
1283
+ const req = pkg.require || {};
1284
+ if (req.php) return `PHP ${req.php}`;
1285
+ }
1286
+ } catch {}
1287
+ return 'PHP (version unknown)';
1288
+ }
1289
+ function getRustToolchain(projectPath) {
1290
+ try {
1291
+ const tool = path.join(projectPath, 'rust-toolchain');
1292
+ if (fs.existsSync(tool)) return `Rust ${fs.readFileSync(tool, 'utf8').trim()}`;
1293
+ const cargo = path.join(projectPath, 'Cargo.toml');
1294
+ if (fs.existsSync(cargo)) {
1295
+ const txt = fs.readFileSync(cargo, 'utf8');
1296
+ const m = txt.match(/edition\s*=\s*"(\d{4})"/);
1297
+ if (m) return `Rust (edition ${m[1]})`;
1298
+ }
1299
+ } catch {}
1300
+ return 'Rust (toolchain unknown)';
1301
+ }
1302
+ function getDotnetTarget(projectPath) {
1303
+ try {
1304
+ const files = fs.readdirSync(projectPath).filter(f => f.endsWith('.csproj'));
1305
+ for (const f of files) {
1306
+ const txt = fs.readFileSync(path.join(projectPath, f), 'utf8');
1307
+ const m = txt.match(/<TargetFramework>([^<]+)<\/TargetFramework>/);
1308
+ if (m) return m[1];
1309
+ }
1310
+ } catch {}
1311
+ return '.NET (target unknown)';
1312
+ }
1313
+
1314
+ function projectPathFromCwd() {
1315
+ try { return process.cwd(); } catch { return '.'; }
1316
+ }
1317
+
1318
+ function getJavaVersion(projectPath) {
1319
+ try {
1320
+ const pomPath = path.join(projectPath, 'pom.xml');
1321
+ if (fs.existsSync(pomPath)) {
1322
+ const pom = fs.readFileSync(pomPath, 'utf8');
1323
+ const m = pom.match(/<maven\.compiler\.source>([^<]+)<\/maven\.compiler\.source>/);
1324
+ const v = m?.[1] || (pom.match(/<java\.version>([^<]+)<\/java\.version>/)?.[1]);
1325
+ if (v) return `JDK ${v}`;
1326
+ }
1327
+ const gradlePath = fs.existsSync(path.join(projectPath, 'build.gradle.kts')) ? path.join(projectPath, 'build.gradle.kts') : path.join(projectPath, 'build.gradle');
1328
+ if (fs.existsSync(gradlePath)) {
1329
+ const gradle = fs.readFileSync(gradlePath, 'utf8');
1330
+ const m = gradle.match(/sourceCompatibility\s*=\s*['"]([^'"]+)['"]/i) || gradle.match(/sourceCompatibility\s+['"]([^'"]+)['"]/i);
1331
+ if (m?.[1]) return `JDK ${m[1]}`;
1332
+ }
1333
+ } catch {}
1334
+ return 'JDK (version unknown)';
1335
+ }