musubi-sdd 3.6.1 → 3.7.1

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.
@@ -8,8 +8,9 @@
8
8
  * - Technical debt detection
9
9
  * - Dependency analysis
10
10
  * - Security vulnerabilities
11
+ * - CodeGraph MCP integration for code structure analysis
11
12
  *
12
- * @version 0.5.0
13
+ * @version 3.6.1
13
14
  */
14
15
 
15
16
  const fs = require('fs-extra');
@@ -17,6 +18,7 @@ const path = require('path');
17
18
  const chalk = require('chalk');
18
19
  const { program } = require('commander');
19
20
  const { glob } = require('glob');
21
+ const { execSync, spawn } = require('child_process');
20
22
 
21
23
  // ============================================================================
22
24
  // Configuration
@@ -59,12 +61,14 @@ const CONFIG = {
59
61
  program
60
62
  .name('musubi-analyze')
61
63
  .description('Analyze codebase for quality, complexity, and technical debt')
62
- .version('0.5.0')
63
- .option('-t, --type <type>', 'Analysis type: quality, dependencies, security, stuck, all', 'all')
64
+ .version('3.6.1')
65
+ .option('-t, --type <type>', 'Analysis type: quality, dependencies, security, stuck, codegraph, all', 'all')
64
66
  .option('-o, --output <file>', 'Output file for analysis report')
65
67
  .option('--json', 'Output in JSON format')
66
68
  .option('--threshold <level>', 'Quality threshold: low, medium, high', 'medium')
67
69
  .option('--detect-stuck', 'Detect stuck agent patterns (repetitive errors, circular edits)')
70
+ .option('--codegraph', 'Run CodeGraph MCP index and update steering/memories/codegraph.md')
71
+ .option('--codegraph-full', 'Run full CodeGraph MCP index (not incremental)')
68
72
  .option('-v, --verbose', 'Verbose output')
69
73
  .parse(process.argv);
70
74
 
@@ -406,6 +410,255 @@ async function generateReport(analysisData) {
406
410
  log(`Report saved to: ${reportPath}`, 'success');
407
411
  }
408
412
 
413
+ // ============================================================================
414
+ // CodeGraph MCP Integration
415
+ // ============================================================================
416
+
417
+ /**
418
+ * Check if CodeGraph MCP is installed
419
+ * @returns {boolean} True if codegraph-mcp is available
420
+ */
421
+ function isCodeGraphInstalled() {
422
+ try {
423
+ execSync('codegraph-mcp --version', { stdio: 'pipe' });
424
+ return true;
425
+ } catch {
426
+ return false;
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Run CodeGraph MCP index command
432
+ * @param {boolean} full - Whether to run full index (not incremental)
433
+ * @returns {Promise<Object>} Index results
434
+ */
435
+ async function runCodeGraphIndex(full = false) {
436
+ return new Promise((resolve, reject) => {
437
+ const args = ['index', '.'];
438
+ if (full) {
439
+ args.push('--full');
440
+ }
441
+
442
+ log(`Running CodeGraph MCP index (${full ? 'full' : 'incremental'})...`, 'analyze');
443
+
444
+ const proc = spawn('codegraph-mcp', args, {
445
+ cwd: process.cwd(),
446
+ stdio: ['inherit', 'pipe', 'pipe']
447
+ });
448
+
449
+ let stdout = '';
450
+ let stderr = '';
451
+
452
+ proc.stdout.on('data', (data) => {
453
+ stdout += data.toString();
454
+ if (options.verbose) {
455
+ process.stdout.write(data);
456
+ }
457
+ });
458
+
459
+ proc.stderr.on('data', (data) => {
460
+ stderr += data.toString();
461
+ });
462
+
463
+ proc.on('close', (code) => {
464
+ if (code === 0) {
465
+ resolve({ success: true, output: stdout });
466
+ } else {
467
+ reject(new Error(`CodeGraph index failed: ${stderr}`));
468
+ }
469
+ });
470
+ });
471
+ }
472
+
473
+ /**
474
+ * Get CodeGraph MCP statistics
475
+ * @returns {Promise<Object>} Statistics object
476
+ */
477
+ async function getCodeGraphStats() {
478
+ return new Promise((resolve, reject) => {
479
+ const proc = spawn('codegraph-mcp', ['stats', '.'], {
480
+ cwd: process.cwd(),
481
+ stdio: ['inherit', 'pipe', 'pipe']
482
+ });
483
+
484
+ let stdout = '';
485
+
486
+ proc.stdout.on('data', (data) => {
487
+ stdout += data.toString();
488
+ });
489
+
490
+ proc.on('close', (code) => {
491
+ if (code === 0) {
492
+ // Parse the stats output
493
+ const stats = parseCodeGraphStats(stdout);
494
+ resolve(stats);
495
+ } else {
496
+ reject(new Error('Failed to get CodeGraph stats'));
497
+ }
498
+ });
499
+ });
500
+ }
501
+
502
+ /**
503
+ * Parse CodeGraph MCP stats output
504
+ * @param {string} output - Raw stats output
505
+ * @returns {Object} Parsed statistics
506
+ */
507
+ function parseCodeGraphStats(output) {
508
+ const stats = {
509
+ entities: 0,
510
+ relations: 0,
511
+ communities: 0,
512
+ files: 0,
513
+ entityTypes: {}
514
+ };
515
+
516
+ const lines = output.split('\n');
517
+ let inEntityTypes = false;
518
+
519
+ for (const line of lines) {
520
+ if (line.startsWith('Entities:')) {
521
+ stats.entities = parseInt(line.split(':')[1].trim(), 10);
522
+ } else if (line.startsWith('Relations:')) {
523
+ stats.relations = parseInt(line.split(':')[1].trim(), 10);
524
+ } else if (line.startsWith('Communities:')) {
525
+ stats.communities = parseInt(line.split(':')[1].trim(), 10);
526
+ } else if (line.startsWith('Files:')) {
527
+ stats.files = parseInt(line.split(':')[1].trim(), 10);
528
+ } else if (line.includes('Entities by type:')) {
529
+ inEntityTypes = true;
530
+ } else if (inEntityTypes && line.trim().startsWith('-')) {
531
+ const match = line.match(/-\s*(\w+):\s*(\d+)/);
532
+ if (match) {
533
+ stats.entityTypes[match[1]] = parseInt(match[2], 10);
534
+ }
535
+ }
536
+ }
537
+
538
+ return stats;
539
+ }
540
+
541
+ /**
542
+ * Generate CodeGraph report in steering/memories/codegraph.md
543
+ * @param {Object} stats - CodeGraph statistics
544
+ */
545
+ async function generateCodeGraphReport(stats) {
546
+ const reportPath = 'steering/memories/codegraph.md';
547
+ const timestamp = new Date().toISOString();
548
+ const version = require('../package.json').version;
549
+
550
+ let report = `# CodeGraph MCP Index Report
551
+
552
+ **Generated**: ${timestamp}
553
+ **Version**: MUSUBI v${version}
554
+ **Indexed by**: CodeGraph MCP Server
555
+
556
+ ---
557
+
558
+ ## Graph Statistics
559
+
560
+ | Metric | Value |
561
+ |--------|-------|
562
+ | Entities | ${stats.entities.toLocaleString()} |
563
+ | Relations | ${stats.relations.toLocaleString()} |
564
+ | Communities | ${stats.communities} |
565
+ | Files Indexed | ${stats.files.toLocaleString()} |
566
+
567
+ ## Entity Types
568
+
569
+ | Type | Count |
570
+ |------|-------|
571
+ `;
572
+
573
+ for (const [type, count] of Object.entries(stats.entityTypes)) {
574
+ report += `| ${type} | ${count.toLocaleString()} |\n`;
575
+ }
576
+
577
+ report += `
578
+ ---
579
+
580
+ ## Usage
581
+
582
+ CodeGraph MCP provides AI assistants with deep code understanding:
583
+
584
+ \`\`\`bash
585
+ # Re-index (incremental)
586
+ codegraph-mcp index .
587
+
588
+ # Full re-index
589
+ codegraph-mcp index . --full
590
+
591
+ # View stats
592
+ codegraph-mcp stats .
593
+
594
+ # Start MCP server
595
+ codegraph-mcp serve --repo .
596
+ \`\`\`
597
+
598
+ ---
599
+
600
+ *Generated by MUSUBI v${version} using CodeGraph MCP Server*
601
+ `;
602
+
603
+ fs.ensureDirSync(path.dirname(reportPath));
604
+ fs.writeFileSync(reportPath, report);
605
+
606
+ log(`CodeGraph report saved to: ${reportPath}`, 'success');
607
+
608
+ return reportPath;
609
+ }
610
+
611
+ /**
612
+ * Run CodeGraph MCP analysis
613
+ * @param {boolean} full - Whether to run full index
614
+ * @returns {Promise<Object>} Analysis results
615
+ */
616
+ async function analyzeCodeGraph(full = false) {
617
+ log('CodeGraph MCP Analysis', 'analyze');
618
+ console.log();
619
+
620
+ // Check if CodeGraph MCP is installed
621
+ if (!isCodeGraphInstalled()) {
622
+ log('CodeGraph MCP is not installed. Install with:', 'warning');
623
+ console.log(chalk.cyan(' pipx install codegraph-mcp-server'));
624
+ console.log();
625
+ return null;
626
+ }
627
+
628
+ try {
629
+ // Run index
630
+ await runCodeGraphIndex(full);
631
+ console.log();
632
+
633
+ // Get statistics
634
+ const stats = await getCodeGraphStats();
635
+
636
+ // Display stats
637
+ console.log(chalk.bold('CodeGraph Statistics:'));
638
+ console.log(` Entities: ${chalk.green(stats.entities.toLocaleString())}`);
639
+ console.log(` Relations: ${chalk.green(stats.relations.toLocaleString())}`);
640
+ console.log(` Communities: ${chalk.green(stats.communities)}`);
641
+ console.log(` Files: ${chalk.green(stats.files.toLocaleString())}`);
642
+ console.log();
643
+
644
+ if (Object.keys(stats.entityTypes).length > 0) {
645
+ console.log(chalk.bold('Entity Types:'));
646
+ for (const [type, count] of Object.entries(stats.entityTypes)) {
647
+ console.log(` ${type}: ${chalk.cyan(count.toLocaleString())}`);
648
+ }
649
+ console.log();
650
+ }
651
+
652
+ // Generate report
653
+ await generateCodeGraphReport(stats);
654
+
655
+ return stats;
656
+ } catch (error) {
657
+ log(`CodeGraph analysis failed: ${error.message}`, 'error');
658
+ return null;
659
+ }
660
+ }
661
+
409
662
  // ============================================================================
410
663
  // Stuck Detection Analysis
411
664
  // ============================================================================
@@ -620,6 +873,20 @@ async function main() {
620
873
  const analysisData = {};
621
874
 
622
875
  try {
876
+ // Handle --codegraph or --codegraph-full options
877
+ if (options.codegraph || options.codegraphFull || options.type === 'codegraph') {
878
+ analysisData.codegraph = await analyzeCodeGraph(options.codegraphFull);
879
+
880
+ if (options.json && analysisData.codegraph) {
881
+ console.log(JSON.stringify(analysisData.codegraph, null, 2));
882
+ }
883
+
884
+ if (options.type === 'codegraph') {
885
+ log('CodeGraph analysis complete!', 'success');
886
+ process.exit(0);
887
+ }
888
+ }
889
+
623
890
  // Handle --detect-stuck option
624
891
  if (options.detectStuck || options.type === 'stuck') {
625
892
  analysisData.stuck = await analyzeStuckPatterns();
@@ -645,6 +912,11 @@ async function main() {
645
912
  analysisData.security = await analyzeSecurity();
646
913
  }
647
914
 
915
+ // Run CodeGraph analysis in 'all' mode if codegraph-mcp is available
916
+ if (options.type === 'all' && isCodeGraphInstalled() && !analysisData.codegraph) {
917
+ analysisData.codegraph = await analyzeCodeGraph(false);
918
+ }
919
+
648
920
  // Generate report if output specified
649
921
  if (options.output || options.type === 'all') {
650
922
  await generateReport(analysisData);