@sun-asterisk/sunlint 1.3.34 → 1.3.35

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.
Files changed (90) hide show
  1. package/core/architecture-integration.js +16 -7
  2. package/core/auto-performance-manager.js +1 -1
  3. package/core/cli-action-handler.js +92 -2
  4. package/core/cli-program.js +96 -138
  5. package/core/file-targeting-service.js +62 -4
  6. package/core/git-utils.js +19 -12
  7. package/core/github-annotate-service.js +326 -11
  8. package/core/html-report-generator.js +326 -731
  9. package/core/impact-integration.js +433 -0
  10. package/core/output-service.js +293 -21
  11. package/core/scoring-service.js +3 -2
  12. package/engines/arch-detect/core/analyzer.js +413 -0
  13. package/engines/arch-detect/core/index.js +22 -0
  14. package/engines/arch-detect/engine/hybrid-detector.js +176 -0
  15. package/engines/arch-detect/engine/index.js +24 -0
  16. package/engines/arch-detect/engine/rule-executor.js +228 -0
  17. package/engines/arch-detect/engine/score-calculator.js +214 -0
  18. package/engines/arch-detect/engine/violation-detector.js +616 -0
  19. package/engines/arch-detect/index.js +50 -0
  20. package/engines/arch-detect/rules/base-rule.js +187 -0
  21. package/engines/arch-detect/rules/index.js +35 -0
  22. package/engines/arch-detect/rules/layered/index.js +28 -0
  23. package/engines/arch-detect/rules/layered/l001-presentation-layer.js +237 -0
  24. package/engines/arch-detect/rules/layered/l002-business-layer.js +215 -0
  25. package/engines/arch-detect/rules/layered/l003-data-layer.js +229 -0
  26. package/engines/arch-detect/rules/layered/l004-model-layer.js +204 -0
  27. package/engines/arch-detect/rules/layered/l005-layer-separation.js +215 -0
  28. package/engines/arch-detect/rules/layered/l006-dependency-direction.js +221 -0
  29. package/engines/arch-detect/rules/layered/layered-rules-collection.js +445 -0
  30. package/engines/arch-detect/rules/modular/index.js +27 -0
  31. package/engines/arch-detect/rules/modular/m001-feature-modules.js +238 -0
  32. package/engines/arch-detect/rules/modular/m002-core-module.js +169 -0
  33. package/engines/arch-detect/rules/modular/m003-module-declaration.js +186 -0
  34. package/engines/arch-detect/rules/modular/m004-public-api.js +171 -0
  35. package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +220 -0
  36. package/engines/arch-detect/rules/modular/modular-rules-collection.js +357 -0
  37. package/engines/arch-detect/rules/presentation/index.js +27 -0
  38. package/engines/arch-detect/rules/presentation/pr001-view-layer.js +221 -0
  39. package/engines/arch-detect/rules/presentation/pr002-presentation-logic.js +192 -0
  40. package/engines/arch-detect/rules/presentation/pr004-data-binding.js +187 -0
  41. package/engines/arch-detect/rules/presentation/pr006-router-layer.js +185 -0
  42. package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +181 -0
  43. package/engines/arch-detect/rules/presentation/presentation-rules-collection.js +507 -0
  44. package/engines/arch-detect/rules/project-scanner/index.js +31 -0
  45. package/engines/arch-detect/rules/project-scanner/ps001-project-root.js +213 -0
  46. package/engines/arch-detect/rules/project-scanner/ps002-language-detection.js +192 -0
  47. package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +339 -0
  48. package/engines/arch-detect/rules/project-scanner/ps004-build-system.js +171 -0
  49. package/engines/arch-detect/rules/project-scanner/ps005-source-directory.js +163 -0
  50. package/engines/arch-detect/rules/project-scanner/ps006-test-directory.js +184 -0
  51. package/engines/arch-detect/rules/project-scanner/ps007-documentation.js +149 -0
  52. package/engines/arch-detect/rules/project-scanner/ps008-cicd-detection.js +163 -0
  53. package/engines/arch-detect/rules/project-scanner/ps009-code-quality.js +152 -0
  54. package/engines/arch-detect/rules/project-scanner/ps010-statistics.js +180 -0
  55. package/engines/arch-detect/rules/rule-registry.js +111 -0
  56. package/engines/arch-detect/types/context.types.js +60 -0
  57. package/engines/arch-detect/types/enums.js +161 -0
  58. package/engines/arch-detect/types/index.js +25 -0
  59. package/engines/arch-detect/types/result.types.js +7 -0
  60. package/engines/arch-detect/types/rule.types.js +7 -0
  61. package/engines/arch-detect/utils/file-scanner.js +411 -0
  62. package/engines/arch-detect/utils/index.js +23 -0
  63. package/engines/arch-detect/utils/pattern-matcher.js +328 -0
  64. package/engines/impact/cli.js +106 -0
  65. package/engines/impact/config/default-config.js +54 -0
  66. package/engines/impact/core/change-detector.js +258 -0
  67. package/engines/impact/core/detectors/database-detector.js +1317 -0
  68. package/engines/impact/core/detectors/endpoint-detector.js +55 -0
  69. package/engines/impact/core/impact-analyzer.js +124 -0
  70. package/engines/impact/core/report-generator.js +462 -0
  71. package/engines/impact/core/utils/ast-parser.js +241 -0
  72. package/engines/impact/core/utils/dependency-graph.js +159 -0
  73. package/engines/impact/core/utils/file-utils.js +116 -0
  74. package/engines/impact/core/utils/git-utils.js +203 -0
  75. package/engines/impact/core/utils/logger.js +13 -0
  76. package/engines/impact/core/utils/method-call-graph.js +1192 -0
  77. package/engines/impact/index.js +135 -0
  78. package/engines/impact/package.json +29 -0
  79. package/package.json +18 -43
  80. package/scripts/build-release.sh +0 -0
  81. package/scripts/copy-impact-analyzer.js +135 -0
  82. package/scripts/install.sh +0 -0
  83. package/scripts/manual-release.sh +0 -0
  84. package/scripts/pre-release-test.sh +0 -0
  85. package/scripts/prepare-release.sh +0 -0
  86. package/scripts/quick-performance-test.js +0 -0
  87. package/scripts/setup-github-registry.sh +0 -0
  88. package/scripts/trigger-release.sh +0 -0
  89. package/scripts/verify-install.sh +0 -0
  90. package/templates/combined-report.html +1418 -0
@@ -16,16 +16,15 @@ class ArchitectureIntegration {
16
16
 
17
17
  /**
18
18
  * Load architecture detection module
19
- * Tries bundled version first, then falls back to local development path
19
+ * Tries bundled (engines/) first, then npm package, then local dev paths
20
20
  */
21
21
  async loadArchitectureModule() {
22
22
  if (this.archModule) {
23
23
  return this.archModule;
24
24
  }
25
25
 
26
- // Try bundled version first (engines/arch-detect)
26
+ // Priority 1: Try bundled version (engines/arch-detect) - for published package
27
27
  const bundledPath = path.join(__dirname, '..', 'engines', 'arch-detect', 'index.js');
28
-
29
28
  if (fs.existsSync(bundledPath)) {
30
29
  try {
31
30
  this.archModule = require(bundledPath);
@@ -35,12 +34,23 @@ class ArchitectureIntegration {
35
34
  return this.archModule;
36
35
  } catch (error) {
37
36
  if (this.options.verbose) {
38
- console.log(chalk.yellow(`âš ī¸ Failed to load bundled module: ${error.message}`));
37
+ console.log(chalk.yellow(`âš ī¸ Failed to load bundled: ${error.message}`));
39
38
  }
40
39
  }
41
40
  }
42
41
 
43
- // Fallback: Try local development path
42
+ // Priority 2: Try npm package (workspace link) - for local development
43
+ try {
44
+ this.archModule = require('@sunlint/architecture-detection');
45
+ if (this.options.verbose) {
46
+ console.log(chalk.gray('đŸ“Ļ Loaded @sunlint/architecture-detection package'));
47
+ }
48
+ return this.archModule;
49
+ } catch (error) {
50
+ // Package not found, continue to fallback
51
+ }
52
+
53
+ // Priority 3: Try local development paths
44
54
  const devPaths = [
45
55
  path.join(__dirname, '..', '..', '..', '..', 'architecture-detection', 'dist', 'index.js'),
46
56
  path.join(__dirname, '..', '..', '..', 'architecture-detection', 'dist', 'index.js'),
@@ -63,8 +73,7 @@ class ArchitectureIntegration {
63
73
  }
64
74
 
65
75
  throw new Error(
66
- 'Architecture detection module not found. Run "npm run build" to bundle it, ' +
67
- 'or ensure architecture-detection is built in the parent directory.'
76
+ 'Architecture detection module not found. Run "npm run build" in sunlint directory.'
68
77
  );
69
78
  }
70
79
 
@@ -76,7 +76,7 @@ class AutoPerformanceManager {
76
76
  analyzeProject(options, targetFiles) {
77
77
  const fileCount = targetFiles.length;
78
78
  const inputPath = options.input || process.cwd();
79
-
79
+
80
80
  // Estimate project complexity
81
81
  const hasNodeModules = fs.existsSync(path.join(inputPath, 'node_modules'));
82
82
  const hasPackageJson = fs.existsSync(path.join(inputPath, 'package.json'));
@@ -15,6 +15,7 @@ const OutputService = require('./output-service');
15
15
  const GitUtils = require('./git-utils');
16
16
  const FileTargetingService = require('./file-targeting-service');
17
17
  const { ArchitectureIntegration } = require('./architecture-integration');
18
+ const { ImpactIntegration } = require('./impact-integration');
18
19
 
19
20
  // Legacy orchestrator for fallback
20
21
  // const LegacyOrchestrator = require('./legacy-analysis-orchestrator'); // Removed
@@ -83,8 +84,8 @@ class CliActionHandler {
83
84
  const startTime = Date.now();
84
85
  let results = null;
85
86
 
86
- // Run code quality analysis (unless --architecture is used alone)
87
- if (rulesToRun.length > 0 && !this.isArchitectureOnly()) {
87
+ // Run code quality analysis (unless --architecture or --impact is used alone)
88
+ if (rulesToRun.length > 0 && !this.isArchitectureOnly() && !this.isImpactOnly()) {
88
89
  results = await this.runModernAnalysis(rulesToRun, targetingResult.files, config);
89
90
  } else {
90
91
  results = { results: [], summary: { total: 0, errors: 0, warnings: 0 } };
@@ -96,6 +97,12 @@ class CliActionHandler {
96
97
  results.architecture = architectureResults;
97
98
  }
98
99
 
100
+ // Run impact analysis if requested
101
+ if (this.options.impact) {
102
+ const impactResults = await this.runImpactAnalysis();
103
+ results.impact = impactResults;
104
+ }
105
+
99
106
  const duration = Date.now() - startTime;
100
107
 
101
108
  // Output results
@@ -515,6 +522,22 @@ class CliActionHandler {
515
522
  */
516
523
  isArchitectureOnly() {
517
524
  return this.options.architecture &&
525
+ !this.options.impact &&
526
+ !this.options.all &&
527
+ !this.options.rule &&
528
+ !this.options.rules &&
529
+ !this.options.quality &&
530
+ !this.options.security &&
531
+ !this.options.category;
532
+ }
533
+
534
+ /**
535
+ * Check if only impact analysis was requested (no code quality rules)
536
+ * Following Rule C006: Verb-noun naming
537
+ */
538
+ isImpactOnly() {
539
+ return this.options.impact &&
540
+ !this.options.architecture &&
518
541
  !this.options.all &&
519
542
  !this.options.rule &&
520
543
  !this.options.rules &&
@@ -554,6 +577,73 @@ class CliActionHandler {
554
577
  return null;
555
578
  }
556
579
  }
580
+
581
+ /**
582
+ * Run impact analysis using impact-analyzer module
583
+ * Following Rule C006: Verb-noun naming
584
+ */
585
+ async runImpactAnalysis() {
586
+ if (!this.options.quiet) {
587
+ console.log(chalk.blue('\n🔍 Running impact analysis...'));
588
+ }
589
+
590
+ try {
591
+ const integration = new ImpactIntegration({
592
+ ...this.options,
593
+ impactBase: this.options.impactBase || 'HEAD~1',
594
+ impactReport: this.options.impactReport,
595
+ });
596
+
597
+ const projectPath = this.getProjectPath();
598
+ const results = await integration.analyze(projectPath);
599
+
600
+ // Display summary
601
+ if (!this.options.quiet && results.summary) {
602
+ const { summary } = results;
603
+ const severityColors = {
604
+ critical: chalk.red,
605
+ high: chalk.yellow,
606
+ medium: chalk.cyan,
607
+ low: chalk.green,
608
+ none: chalk.gray,
609
+ };
610
+ const colorFn = severityColors[summary.severity] || chalk.white;
611
+
612
+ console.log(chalk.gray(`\n Base: ${summary.baseRef}`));
613
+ console.log(chalk.gray(` Changes: ${summary.totalChanges} files`));
614
+ console.log(` Impact: ${colorFn(summary.impactScore + '/100')} (${colorFn(summary.severity.toUpperCase())})`);
615
+
616
+ if (summary.categories) {
617
+ const cats = Object.entries(summary.categories)
618
+ .filter(([, count]) => count > 0)
619
+ .map(([cat, count]) => `${cat}:${count}`)
620
+ .join(' ¡ ');
621
+ if (cats) {
622
+ console.log(chalk.gray(` Categories: ${cats}`));
623
+ }
624
+ }
625
+ }
626
+
627
+ // Save markdown report if requested
628
+ if (this.options.impactReport && results.markdownReport) {
629
+ const reportPath = await integration.saveReport(
630
+ results.markdownReport,
631
+ this.options.impactReport
632
+ );
633
+ if (!this.options.quiet) {
634
+ console.log(chalk.green(`\n📄 Impact report saved: ${reportPath}`));
635
+ }
636
+ }
637
+
638
+ return results;
639
+ } catch (error) {
640
+ console.error(chalk.yellow(`âš ī¸ Impact analysis failed: ${error.message}`));
641
+ if (this.options.verbose || this.options.debug) {
642
+ console.error(error.stack);
643
+ }
644
+ return null;
645
+ }
646
+ }
557
647
  }
558
648
 
559
649
  module.exports = CliActionHandler;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * CLI Program Definition
3
- * Following Rule C005: Single responsibility - only handle CLI structure
3
+ * Defines all CLI options and help text for SunLint
4
4
  */
5
5
 
6
6
  const { Command } = require('commander');
@@ -11,169 +11,127 @@ function createCliProgram() {
11
11
 
12
12
  program
13
13
  .name('sunlint')
14
- .description('â˜€ī¸ Sun Lint - Coding Standards Checker | Multi-rule Quality & Security Analysis')
14
+ .description('â˜€ī¸ SunLint - Multi-language Static Analysis Tool | Code Quality & Security')
15
15
  .version(version);
16
16
 
17
- // Rule selection options
17
+ // ──────────────────────────────────────────────────────────────
18
+ // Rule Selection
19
+ // ──────────────────────────────────────────────────────────────
18
20
  program
19
21
  .option('-r, --rule <rule>', 'Run single rule (e.g., C019)')
20
- .option('--rules <rules>', 'Run multiple rules (comma-separated, e.g., C019,C006,S005)')
22
+ .option('--rules <rules>', 'Run multiple rules (comma-separated)')
21
23
  .option('-a, --all', 'Run all available rules')
22
- .option('-c, --category <category>', 'Run rules by category (quality,security,logging,naming)')
24
+ .option('-c, --category <category>', 'Run rules by category (quality, security, logging, naming)')
23
25
  .option('--quality', 'Run all code quality rules')
24
26
  .option('--security', 'Run all secure coding rules');
25
27
 
26
- // Architecture Analysis options
27
- program
28
- .option('--architecture', 'Enable architecture pattern detection (layered, modular, mvvm, viper)')
29
- .option('--arch-patterns <patterns>', 'Target specific architecture patterns (comma-separated)')
30
- .option('--arch-report', 'Generate separate architecture MD report');
31
-
32
- // TypeScript specific options (Phase 1 focus)
33
- program
34
- .option('--typescript', 'Enable TypeScript-specific analysis')
35
- .option('--typescript-engine <engine>', 'TypeScript analysis engine (eslint,heuristic,hybrid)', 'heuristic')
36
- .option('--ensure-deps', 'Ensure ESLint dependencies are installed');
37
-
38
- // Input/Output options (v1.x: explicit --input required)
28
+ // ──────────────────────────────────────────────────────────────
29
+ // Input/Output
30
+ // ──────────────────────────────────────────────────────────────
39
31
  program
40
32
  .option('-i, --input <path>', 'Input file or directory to analyze (REQUIRED)')
41
- .option('-f, --format <format>', 'Output format (eslint,json,summary,table)', 'eslint')
42
- .option('-o, --output <file>', 'Output file path')
43
- .option('--output-summary <file>', 'Output summary report file path (JSON format for CI/CD)')
44
- .option('--upload-report [url]', 'Upload summary report to API endpoint after analysis (default: Sun* Coding Standards API)')
45
- .option('--config <file>', 'Configuration file path (default: auto-discover)')
46
- .option('--github-annotate [mode]', 'Annotate GitHub PR with comments, summary & HTML report artifact (modes: annotate, summary, all)');
47
-
48
- // File targeting options
33
+ .option('-f, --format <format>', 'Output format: eslint, json, summary, table', 'eslint')
34
+ .option('-o, --output <file>', 'Write output to file')
35
+ .option('--output-summary <file>', 'Write JSON summary for CI/CD')
36
+ .option('--output-html [file]', 'Generate HTML report (default: sunlint-report.html)')
37
+ .option('--config <file>', 'Configuration file path');
38
+
39
+ // ──────────────────────────────────────────────────────────────
40
+ // File Targeting
41
+ // ──────────────────────────────────────────────────────────────
49
42
  program
50
43
  .option('--include <patterns>', 'Include file patterns (comma-separated globs)')
51
44
  .option('--exclude <patterns>', 'Exclude file patterns (comma-separated globs)')
52
- .option('--languages <languages>', 'Target specific languages (comma-separated: typescript,dart,kotlin)')
53
- .option('--include-tests', 'Include test files in analysis (default: true)')
45
+ .option('--languages <langs>', 'Target languages: typescript, dart, kotlin, swift, java')
54
46
  .option('--exclude-tests', 'Exclude test files from analysis')
55
- .option('--only-source', 'Only analyze source files (exclude tests, configs, etc.)');
47
+ .option('--only-source', 'Only analyze source files (exclude tests, configs)');
56
48
 
57
- // CI/CD and Git integration options
49
+ // ──────────────────────────────────────────────────────────────
50
+ // Git Integration
51
+ // ──────────────────────────────────────────────────────────────
58
52
  program
59
- .option('--changed-files', 'Only analyze files changed in current branch (git diff)')
53
+ .option('--changed-files', 'Only analyze changed files (git diff)')
60
54
  .option('--staged-files', 'Only analyze staged files (git diff --cached)')
61
- .option('--diff-base <ref>', 'Compare against specific git reference (e.g., origin/main)')
62
- .option('--since <commit>', 'Only analyze files changed since specific commit')
63
- .option('--pr-mode', 'Enable PR mode (changed files + baseline comparison)')
64
- .option('--baseline <file>', 'Load baseline results to compare against')
65
- .option('--save-baseline <file>', 'Save current results as baseline')
66
- .option('--fail-on-new-violations', 'Exit with error only on new violations (not existing)');
67
-
68
- // Performance options (SIMPLIFIED)
55
+ .option('--diff-base <ref>', 'Compare against git ref (e.g., origin/main)');
56
+
57
+ // ──────────────────────────────────────────────────────────────
58
+ // GitHub Actions
59
+ // ──────────────────────────────────────────────────────────────
69
60
  program
70
- .option('--timeout <milliseconds>', 'Analysis timeout in milliseconds (default: auto)', '0')
71
- .option('--max-files <count>', 'Maximum files to analyze (default: auto-detect)', '0')
72
- .option('--performance <mode>', 'Performance mode: auto, fast, careful (default: auto)', 'auto');
61
+ .option('--github-annotate [mode]', 'GitHub PR annotations (modes: annotate, summary, all)')
62
+ .option('--upload-report [url]', 'Upload report to API (default: Sun* Coding Standards)');
73
63
 
74
- // Advanced options
64
+ // ──────────────────────────────────────────────────────────────
65
+ // Analysis Options
66
+ // ──────────────────────────────────────────────────────────────
67
+ program
68
+ .option('--typescript', 'Enable TypeScript-specific analysis')
69
+ .option('--architecture', 'Enable architecture pattern detection')
70
+ .option('--arch-patterns <patterns>', 'Target architecture patterns (comma-separated)')
71
+ .option('--arch-report', 'Generate architecture markdown report')
72
+ .option('--impact', 'Enable impact analysis (analyze code changes)')
73
+ .option('--impact-base <ref>', 'Base git ref for impact analysis (default: HEAD~1)')
74
+ .option('--impact-report <file>', 'Output impact report file (default: impact-report.md)')
75
+ .option('--engine <engine>', 'Analysis engine: auto, eslint, heuristic', 'auto')
76
+ .option('--eslint-integration', 'Merge with existing ESLint config')
77
+ .option('--no-eslint-integration', 'Disable ESLint integration');
78
+
79
+ // ──────────────────────────────────────────────────────────────
80
+ // Performance
81
+ // ──────────────────────────────────────────────────────────────
82
+ program
83
+ .option('--performance <mode>', 'Performance mode: auto, fast, careful', 'auto')
84
+ .option('--timeout <ms>', 'Analysis timeout in milliseconds', '0')
85
+ .option('--max-files <n>', 'Maximum files to analyze', '0')
86
+ .option('--max-semantic-files <n>', 'TypeScript symbol table limit (default: 1000)');
87
+
88
+ // ──────────────────────────────────────────────────────────────
89
+ // Output Control
90
+ // ──────────────────────────────────────────────────────────────
75
91
  program
76
- .option('--engine <engine>', 'Analysis engine (eslint,heuristic,auto)', 'auto')
77
- .option('--dry-run', 'Show what would be analyzed without running')
78
92
  .option('--verbose', 'Enable verbose logging')
79
93
  .option('--quiet', 'Suppress non-error output')
80
- .option('--debug', 'Enable debug mode')
81
94
  .option('--ai', 'Enable AI-powered analysis')
82
- .option('--no-ai', 'Force disable AI analysis')
83
- .option('--max-semantic-files <number>', 'Symbol table file limit for TypeScript analysis (default: 1000, -1 for unlimited)')
84
- .option('--list-engines', 'List available analysis engines');
85
-
86
- // ESLint Integration options
87
- program
88
- .option('--eslint-integration', 'Enable ESLint integration (merge with existing ESLint config)')
89
- .option('--no-eslint-integration', 'Disable ESLint integration')
90
- .option('--eslint-merge-rules', 'Merge SunLint and user ESLint rules (default: true)')
91
- .option('--eslint-preserve-config', 'Preserve user ESLint configuration (default: true)')
92
- .option('--eslint-run-after', 'Run ESLint after SunLint (instead of merged execution)');
95
+ .option('--no-ai', 'Disable AI analysis');
93
96
 
94
- // Help examples
97
+ // ──────────────────────────────────────────────────────────────
98
+ // Help Examples
99
+ // ──────────────────────────────────────────────────────────────
95
100
  program.addHelpText('after', `
96
101
  Examples:
97
- $ sunlint --rule=C019 --input=src
98
- $ sunlint --rule C019 --input src
99
- $ sunlint --rules=C019,C006,S005 --input=src
100
- $ sunlint --all --input=src
101
- $ sunlint --quality --input=src
102
- $ sunlint --security --input=src
103
- $ sunlint --category=logging --input=src
104
-
105
- File Targeting:
106
- $ sunlint --all --include="src/**/*.ts" --exclude="**/*.test.*" --input=.
107
-
108
- Performance (SIMPLIFIED):
109
- $ sunlint --all --input=src --performance=auto # Auto-detect best settings
110
- $ sunlint --all --input=src --performance=fast # Quick scan
111
- $ sunlint --all --input=src --performance=careful # Thorough analysis
112
- $ sunlint --all --input=src --timeout=60000 # Custom timeout (60s)
113
-
114
- File Limits (when needed):
115
- $ sunlint --all --input=src --max-files=500 # Limit total files analyzed
116
- $ sunlint --all --input=src --max-semantic-files=200 # Limit TypeScript symbol table
117
- $ sunlint --all --languages=typescript,dart --input=src
118
- $ sunlint --typescript --exclude-tests --input=src
119
- $ sunlint --all --only-source --include="src/**,lib/**" --input=.
120
-
121
- TypeScript Analysis (Phase 1):
122
- $ sunlint --typescript --input=src
123
- $ sunlint --rule=C006 --typescript --input=src
124
- $ sunlint --rules=C019,S005 --typescript --input=src
125
- $ sunlint --typescript-engine=eslint --input=src
126
-
127
- Version Strategy:
128
- v1.x: ESLint-first with SunLint fallback (current)
129
- v2.x: SunLint-first with ESLint integration (--eslint-integration)
130
-
131
- Engine Configuration:
132
- $ sunlint --all --input=src # Use config engine setting
133
- $ sunlint --all --input=src --engine=eslint # Force ESLint engine
134
- $ sunlint --all --input=src --engine=heuristic # Force Heuristic engine
135
-
136
- CI/CD Integration:
137
- $ sunlint --all --changed-files --format=summary --no-ai
138
- $ sunlint --all --changed-files --diff-base=origin/main --fail-on-new-violations
139
- $ sunlint --all --staged-files --format=summary
140
- $ sunlint --all --pr-mode --diff-base=origin/main
141
- $ sunlint --all --output-summary=report.json --upload-report
142
- $ sunlint --all --output-summary=report.json --upload-report=https://custom-api.com/reports
143
-
144
- GitHub Actions Integration:
145
- $ sunlint --all --input=src --github-annotate # Inline + summary + HTML artifact
146
- $ sunlint --all --input=src --github-annotate=annotate # Inline comments only
147
- $ sunlint --all --input=src --github-annotate=summary # Summary comment + HTML artifact
148
- $ sunlint --all --input=src --github-annotate=all # All features (default)
149
- $ sunlint --all --changed-files --github-annotate # With changed files only
150
-
151
- ESLint Integration:
152
- $ sunlint --typescript --eslint-integration --input=src
153
- $ sunlint --all --eslint-integration --eslint-merge-rules --input=src
154
- $ sunlint --all --eslint-integration --eslint-run-after --input=src
155
- $ sunlint --typescript --eslint-integration --changed-files
156
-
157
- Advanced File Targeting:
158
- $ sunlint --all --include="src/**/*.ts,lib/**/*.dart" --exclude="**/*.generated.*" --input=.
159
- $ sunlint --typescript --exclude="**/*.d.ts,**/*.test.*" --input=src
160
- $ sunlint --languages=typescript,dart --include="src/**,packages/**" --input=.
161
- $ sunlint --all --only-source --exclude-tests --languages=typescript --input=.
162
-
163
- Large Project Optimization:
164
- $ sunlint --all --input=. --max-semantic-files=500 # Conservative analysis
165
- $ sunlint --all --input=. --max-semantic-files=2000 # Comprehensive analysis
166
- $ sunlint --all --input=. --max-semantic-files=-1 # Unlimited (all files)
167
- $ sunlint --all --input=. --max-semantic-files=0 # Disable semantic analysis
168
- $ sunlint --all --changed-files --max-semantic-files=300 # Fast CI analysis
169
-
170
- Architecture Analysis:
171
- $ sunlint --all --architecture --input=src # Code quality + architecture
172
- $ sunlint --architecture --input=src # Architecture only
173
- $ sunlint --architecture --arch-report --input=src # Generate MD report
174
- $ sunlint --architecture --arch-patterns=mvvm,layered --input=src
175
-
176
- Sun* Engineering - Coding Standards Made Simple â˜€ī¸
102
+ sunlint --all --input=src # Analyze all rules
103
+ sunlint --rule=C019 --input=src # Single rule
104
+ sunlint --rules=C019,C006 --input=src # Multiple rules
105
+ sunlint --quality --input=src # Quality rules only
106
+ sunlint --security --input=src # Security rules only
107
+
108
+ Git Integration:
109
+ sunlint --all --changed-files --input=. # Changed files only
110
+ sunlint --all --staged-files --input=. # Staged files only
111
+ sunlint --all --diff-base=origin/main # Compare with main
112
+
113
+ GitHub Actions:
114
+ sunlint --all --github-annotate --input=src # PR annotations
115
+ sunlint --all --output-summary=report.json # JSON report for CI
116
+
117
+ Reports:
118
+ sunlint --all --output-html --input=src # Generate HTML report
119
+ sunlint --all --output-html=report.html # Custom HTML filename
120
+
121
+ Architecture:
122
+ sunlint --architecture --input=src # Detect patterns
123
+ sunlint --architecture --arch-report # Generate MD report
124
+
125
+ Impact Analysis:
126
+ sunlint --impact --input=src # Analyze code changes
127
+ sunlint --impact --impact-base=origin/main # Compare with main
128
+ sunlint --impact --impact-report=report.md # Custom output file
129
+
130
+ Performance:
131
+ sunlint --all --performance=fast --input=. # Quick scan
132
+ sunlint --all --max-files=500 --input=. # Limit files
133
+
134
+ â˜€ī¸ Sun* Engineering - Coding Standards Made Simple
177
135
  `);
178
136
 
179
137
  return program;
@@ -616,22 +616,80 @@ class FileTargetingService {
616
616
  return files;
617
617
  }
618
618
 
619
+ /**
620
+ * Directories to always skip during file collection (performance + safety)
621
+ * These are known heavy/problematic directories in Flutter/mobile projects
622
+ */
623
+ static SKIP_DIRECTORIES = new Set([
624
+ 'node_modules',
625
+ '.git',
626
+ '.dart_tool',
627
+ 'build',
628
+ 'Pods',
629
+ '.symlinks',
630
+ '.pub-cache',
631
+ '.gradle',
632
+ 'DerivedData',
633
+ '__pycache__',
634
+ '.venv',
635
+ 'vendor',
636
+ '.bundle'
637
+ ]);
638
+
619
639
  /**
620
640
  * Collect files from directory recursively
621
641
  * Rule C006: collectFilesFromDirectory - verb-noun naming
642
+ * Enhanced: Skip symlinks and known heavy directories to prevent stack overflow
622
643
  */
623
- async collectFilesFromDirectory(dirPath) {
644
+ async collectFilesFromDirectory(dirPath, visitedPaths = new Set()) {
624
645
  const files = [];
625
- const entries = fs.readdirSync(dirPath);
646
+
647
+ // Resolve real path to detect circular symlinks
648
+ let realPath;
649
+ try {
650
+ realPath = fs.realpathSync(dirPath);
651
+ } catch {
652
+ return files; // Cannot resolve path, skip
653
+ }
654
+
655
+ // Prevent infinite loops from circular symlinks
656
+ if (visitedPaths.has(realPath)) {
657
+ return files;
658
+ }
659
+ visitedPaths.add(realPath);
660
+
661
+ let entries;
662
+ try {
663
+ entries = fs.readdirSync(dirPath);
664
+ } catch {
665
+ return files; // Cannot read directory, skip
666
+ }
626
667
 
627
668
  for (const entry of entries) {
669
+ // Skip known heavy directories early (before stat call)
670
+ if (FileTargetingService.SKIP_DIRECTORIES.has(entry)) {
671
+ continue;
672
+ }
673
+
628
674
  const fullPath = path.join(dirPath, entry);
629
- const stats = fs.statSync(fullPath);
675
+
676
+ let stats;
677
+ try {
678
+ // Use lstatSync to detect symlinks without following them
679
+ stats = fs.lstatSync(fullPath);
680
+ } catch {
681
+ continue; // Cannot stat, skip this entry
682
+ }
683
+
684
+ // Skip symlinks to prevent circular references
685
+ if (stats.isSymbolicLink()) {
686
+ continue;
687
+ }
630
688
 
631
689
  if (stats.isFile()) {
632
690
  files.push(path.resolve(fullPath));
633
691
  } else if (stats.isDirectory()) {
634
- const subFiles = await this.collectFilesFromDirectory(fullPath);
692
+ const subFiles = await this.collectFilesFromDirectory(fullPath, visitedPaths);
635
693
  files.push(...subFiles);
636
694
  }
637
695
  }
package/core/git-utils.js CHANGED
@@ -186,13 +186,19 @@ class GitUtils {
186
186
  try {
187
187
  const { baseBranch, provider } = prContext;
188
188
 
189
- console.log(`🔍 Detecting changed files for PR (provider: ${provider}, base: ${baseBranch})`);
189
+ // Professional output header
190
+ console.log('');
191
+ console.log('┌─ Git Integration ────────────────────────────────────');
192
+ console.log(`│ Provider: ${provider.toUpperCase()}`);
193
+ console.log(`│ Base: ${baseBranch}`);
194
+ console.log('├──────────────────────────────────────────────────────');
190
195
 
191
196
  // Try to find the base branch reference
192
197
  let baseRef = this.findBaseRef(baseBranch, gitRoot);
193
198
 
194
199
  if (!baseRef) {
195
- console.log(`âš ī¸ Base ref not found locally, attempting to fetch origin/${baseBranch}...`);
200
+ console.log(`│ ⚠ Base ref not found locally`);
201
+ console.log(`│ → Fetching origin/${baseBranch}...`);
196
202
 
197
203
  // Try to fetch and create the ref
198
204
  const fetchSuccess = this.ensureBaseRefExists(`origin/${baseBranch}`, gitRoot);
@@ -200,11 +206,12 @@ class GitUtils {
200
206
  if (fetchSuccess) {
201
207
  baseRef = `origin/${baseBranch}`;
202
208
  } else {
209
+ console.log('└──────────────────────────────────────────────────────');
203
210
  throw new Error(`Cannot find or fetch base branch: ${baseBranch}`);
204
211
  }
205
212
  } else {
206
213
  // Ensure we have the latest
207
- console.log(`✅ Found base ref: ${baseRef}`);
214
+ console.log(`│ ✓ Base ref: ${baseRef}`);
208
215
  this.ensureBaseRefExists(baseRef, gitRoot);
209
216
  }
210
217
 
@@ -215,10 +222,10 @@ class GitUtils {
215
222
  cwd: gitRoot,
216
223
  encoding: 'utf8'
217
224
  }).trim();
218
- console.log(`✅ Found merge-base: ${mergeBase.substring(0, 8)}`);
225
+ console.log(`│ ✓ Merge-base: ${mergeBase.substring(0, 8)}`);
219
226
  } catch (error) {
220
227
  // If merge-base fails, fall back to direct comparison
221
- console.warn(`âš ī¸ Could not find merge-base, using direct diff with ${baseRef}`);
228
+ console.log(`│ ⚠ Merge-base not found, using direct diff`);
222
229
  mergeBase = baseRef;
223
230
  }
224
231
 
@@ -232,7 +239,10 @@ class GitUtils {
232
239
  .map(file => path.resolve(gitRoot, file))
233
240
  .filter(file => fs.existsSync(file));
234
241
 
235
- console.log(`✅ Found ${changedFiles.length} changed files in PR`);
242
+ console.log('├──────────────────────────────────────────────────────');
243
+ console.log(`│ Changed files: ${changedFiles.length}`);
244
+ console.log('└──────────────────────────────────────────────────────');
245
+ console.log('');
236
246
 
237
247
  return changedFiles;
238
248
  } catch (error) {
@@ -290,13 +300,11 @@ class GitUtils {
290
300
 
291
301
  if (remote === 'origin' || remote === 'upstream') {
292
302
  try {
293
- console.log(`âŦ‡ī¸ Fetching ${remote}/${branch}...`);
294
-
295
303
  // Check if this is a shallow repository (common in GitHub Actions)
296
304
  const isShallow = this.isShallowRepository(gitRoot);
297
305
 
298
306
  if (isShallow) {
299
- console.log(` â„šī¸ Detected shallow clone, fetching with additional history...`);
307
+ console.log(`│ → Shallow clone detected, deepening...`);
300
308
 
301
309
  // For shallow clones, we need to:
302
310
  // 1. Fetch the base branch
@@ -317,7 +325,6 @@ class GitUtils {
317
325
  });
318
326
  } catch (shallowError) {
319
327
  // If deepen fails, try direct fetch
320
- console.log(` â„šī¸ Trying direct fetch...`);
321
328
  execSync(`git fetch ${remote} ${branch}`, {
322
329
  cwd: gitRoot,
323
330
  stdio: 'pipe',
@@ -339,10 +346,10 @@ class GitUtils {
339
346
  stdio: 'ignore'
340
347
  });
341
348
 
342
- console.log(`✅ Successfully fetched ${baseRef}`);
349
+ console.log(`│ ✓ Fetched ${baseRef}`);
343
350
  return true;
344
351
  } catch (fetchError) {
345
- console.warn(`âš ī¸ Failed to fetch ${baseRef}: ${fetchError.message}`);
352
+ console.log(`│ ✗ Failed to fetch: ${fetchError.message}`);
346
353
  return false;
347
354
  }
348
355
  }