mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
feat: add severity-based sorting and filtering for violations (v0.5.2)
- Add CRITICAL/HIGH/MEDIUM/LOW severity levels to all violations - Sort violations by severity automatically (most critical first) - Add CLI flags: --min-severity and --only-critical - Group violations by severity in CLI output with color-coded headers - Update all violation interfaces to include severity field - Maintain 90%+ test coverage with all tests passing - Update CHANGELOG.md, ROADMAP.md, and package version to 0.5.2
This commit is contained in:
@@ -5,6 +5,53 @@ All notable changes to @samiyev/guardian will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.5.2] - 2025-11-24
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
**🎯 Severity-Based Prioritization**
|
||||||
|
|
||||||
|
Guardian now intelligently prioritizes violations by severity, helping teams focus on critical issues first!
|
||||||
|
|
||||||
|
- ✅ **Severity Levels**
|
||||||
|
- 🔴 **CRITICAL**: Circular dependencies, Repository pattern violations
|
||||||
|
- 🟠 **HIGH**: Dependency direction violations, Framework leaks, Entity exposures
|
||||||
|
- 🟡 **MEDIUM**: Naming violations, Architecture violations
|
||||||
|
- 🟢 **LOW**: Hardcoded values
|
||||||
|
|
||||||
|
- ✅ **Automatic Sorting**
|
||||||
|
- All violations automatically sorted by severity (most critical first)
|
||||||
|
- Applied in AnalyzeProject use case before returning results
|
||||||
|
- Consistent ordering across all detection types
|
||||||
|
|
||||||
|
- ✅ **CLI Filtering Options**
|
||||||
|
- `--min-severity <level>` - Show only violations at specified level and above
|
||||||
|
- `--only-critical` - Quick filter for critical issues only
|
||||||
|
- Examples:
|
||||||
|
- `guardian check src --only-critical`
|
||||||
|
- `guardian check src --min-severity high`
|
||||||
|
|
||||||
|
- ✅ **Enhanced CLI Output**
|
||||||
|
- Color-coded severity labels (🔴🟠🟡🟢)
|
||||||
|
- Visual severity group headers with separators
|
||||||
|
- Severity displayed for each violation
|
||||||
|
- Clear filtering messages when filters active
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated all violation interfaces to include `severity: SeverityLevel` field
|
||||||
|
- Improved CLI presentation with grouped severity display
|
||||||
|
- Enhanced developer experience with visual prioritization
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
- All 292 tests passing (100% pass rate)
|
||||||
|
- Coverage: 90.63% statements, 82.19% branches, 83.51% functions
|
||||||
|
- No breaking changes - fully backwards compatible
|
||||||
|
- Clean Architecture principles maintained
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.5.1] - 2025-11-24
|
## [0.5.1] - 2025-11-24
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This document outlines the current features and future plans for @puaros/guardian.
|
This document outlines the current features and future plans for @puaros/guardian.
|
||||||
|
|
||||||
## Current Version: 0.5.0 ✅ RELEASED
|
## Current Version: 0.5.2 ✅ RELEASED
|
||||||
|
|
||||||
**Released:** 2025-11-24
|
**Released:** 2025-11-24
|
||||||
|
|
||||||
@@ -159,6 +159,62 @@ class CreateUser {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Version 0.5.2 - Severity-Based Prioritization 🎯 ✅ RELEASED
|
||||||
|
|
||||||
|
**Released:** 2025-11-24
|
||||||
|
**Priority:** HIGH
|
||||||
|
|
||||||
|
Intelligently prioritize violations by severity to help teams focus on critical issues first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show only critical issues
|
||||||
|
guardian check src --only-critical
|
||||||
|
|
||||||
|
# Show high severity and above
|
||||||
|
guardian check src --min-severity high
|
||||||
|
```
|
||||||
|
|
||||||
|
**Severity Levels:**
|
||||||
|
- 🔴 **CRITICAL**: Circular dependencies, Repository pattern violations
|
||||||
|
- 🟠 **HIGH**: Dependency direction violations, Framework leaks, Entity exposures
|
||||||
|
- 🟡 **MEDIUM**: Naming violations, Architecture violations
|
||||||
|
- 🟢 **LOW**: Hardcoded values
|
||||||
|
|
||||||
|
**Implemented Features:**
|
||||||
|
- ✅ Automatic sorting by severity (most critical first)
|
||||||
|
- ✅ CLI flags: `--min-severity <level>` and `--only-critical`
|
||||||
|
- ✅ Color-coded severity labels in output (🔴🟠🟡🟢)
|
||||||
|
- ✅ Visual severity group headers with separators
|
||||||
|
- ✅ Filtering messages when filters active
|
||||||
|
- ✅ All violation interfaces include severity field
|
||||||
|
- ✅ 292 tests passing with 90%+ coverage
|
||||||
|
- ✅ Backwards compatible - no breaking changes
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Focus on critical architectural violations first
|
||||||
|
- Gradual technical debt reduction
|
||||||
|
- Better CI/CD integration (fail on critical only)
|
||||||
|
- Improved developer experience with visual prioritization
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version 0.5.1 - Code Quality Refactoring 🧹 ✅ RELEASED
|
||||||
|
|
||||||
|
**Released:** 2025-11-24
|
||||||
|
**Priority:** MEDIUM
|
||||||
|
|
||||||
|
Internal refactoring to eliminate hardcoded values and improve maintainability:
|
||||||
|
|
||||||
|
**Implemented Features:**
|
||||||
|
- ✅ Extracted 30+ constants from hardcoded strings
|
||||||
|
- ✅ New constants files: paths.ts, extended Messages.ts
|
||||||
|
- ✅ Reduced hardcoded values from 37 to 1 (97% improvement)
|
||||||
|
- ✅ Guardian passes its own checks (0 violations in src/)
|
||||||
|
- ✅ All 292 tests passing
|
||||||
|
- ✅ No breaking changes - fully backwards compatible
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Future Roadmap
|
## Future Roadmap
|
||||||
|
|
||||||
### Version 0.6.0 - Aggregate Boundary Validation 🔒
|
### Version 0.6.0 - Aggregate Boundary Validation 🔒
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@samiyev/guardian",
|
"name": "@samiyev/guardian",
|
||||||
"version": "0.5.1",
|
"version": "0.5.2",
|
||||||
"description": "Code quality guardian for vibe coders and enterprise teams - catch hardcodes, architecture violations, and circular deps. Enforce Clean Architecture at scale. Works with Claude, GPT, Copilot.",
|
"description": "Code quality guardian for vibe coders and enterprise teams - catch hardcodes, architecture violations, and circular deps. Enforce Clean Architecture at scale. Works with Claude, GPT, Copilot.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"puaros",
|
"puaros",
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import {
|
|||||||
REPOSITORY_VIOLATION_TYPES,
|
REPOSITORY_VIOLATION_TYPES,
|
||||||
RULES,
|
RULES,
|
||||||
SEVERITY_LEVELS,
|
SEVERITY_LEVELS,
|
||||||
|
SEVERITY_ORDER,
|
||||||
|
type SeverityLevel,
|
||||||
|
VIOLATION_SEVERITY_MAP,
|
||||||
} from "../../shared/constants"
|
} from "../../shared/constants"
|
||||||
|
|
||||||
export interface AnalyzeProjectRequest {
|
export interface AnalyzeProjectRequest {
|
||||||
@@ -47,6 +50,7 @@ export interface ArchitectureViolation {
|
|||||||
message: string
|
message: string
|
||||||
file: string
|
file: string
|
||||||
line?: number
|
line?: number
|
||||||
|
severity: SeverityLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HardcodeViolation {
|
export interface HardcodeViolation {
|
||||||
@@ -64,13 +68,14 @@ export interface HardcodeViolation {
|
|||||||
constantName: string
|
constantName: string
|
||||||
location: string
|
location: string
|
||||||
}
|
}
|
||||||
|
severity: SeverityLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CircularDependencyViolation {
|
export interface CircularDependencyViolation {
|
||||||
rule: typeof RULES.CIRCULAR_DEPENDENCY
|
rule: typeof RULES.CIRCULAR_DEPENDENCY
|
||||||
message: string
|
message: string
|
||||||
cycle: string[]
|
cycle: string[]
|
||||||
severity: typeof SEVERITY_LEVELS.ERROR
|
severity: SeverityLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NamingConventionViolation {
|
export interface NamingConventionViolation {
|
||||||
@@ -88,6 +93,7 @@ export interface NamingConventionViolation {
|
|||||||
actual: string
|
actual: string
|
||||||
message: string
|
message: string
|
||||||
suggestion?: string
|
suggestion?: string
|
||||||
|
severity: SeverityLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FrameworkLeakViolation {
|
export interface FrameworkLeakViolation {
|
||||||
@@ -100,6 +106,7 @@ export interface FrameworkLeakViolation {
|
|||||||
line?: number
|
line?: number
|
||||||
message: string
|
message: string
|
||||||
suggestion: string
|
suggestion: string
|
||||||
|
severity: SeverityLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityExposureViolation {
|
export interface EntityExposureViolation {
|
||||||
@@ -112,6 +119,7 @@ export interface EntityExposureViolation {
|
|||||||
methodName?: string
|
methodName?: string
|
||||||
message: string
|
message: string
|
||||||
suggestion: string
|
suggestion: string
|
||||||
|
severity: SeverityLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DependencyDirectionViolation {
|
export interface DependencyDirectionViolation {
|
||||||
@@ -123,6 +131,7 @@ export interface DependencyDirectionViolation {
|
|||||||
line?: number
|
line?: number
|
||||||
message: string
|
message: string
|
||||||
suggestion: string
|
suggestion: string
|
||||||
|
severity: SeverityLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RepositoryPatternViolation {
|
export interface RepositoryPatternViolation {
|
||||||
@@ -138,6 +147,7 @@ export interface RepositoryPatternViolation {
|
|||||||
details: string
|
details: string
|
||||||
message: string
|
message: string
|
||||||
suggestion: string
|
suggestion: string
|
||||||
|
severity: SeverityLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProjectMetrics {
|
export interface ProjectMetrics {
|
||||||
@@ -207,14 +217,24 @@ export class AnalyzeProject extends UseCase<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const violations = this.detectViolations(sourceFiles)
|
const violations = this.sortBySeverity(this.detectViolations(sourceFiles))
|
||||||
const hardcodeViolations = this.detectHardcode(sourceFiles)
|
const hardcodeViolations = this.sortBySeverity(this.detectHardcode(sourceFiles))
|
||||||
const circularDependencyViolations = this.detectCircularDependencies(dependencyGraph)
|
const circularDependencyViolations = this.sortBySeverity(
|
||||||
const namingViolations = this.detectNamingConventions(sourceFiles)
|
this.detectCircularDependencies(dependencyGraph),
|
||||||
const frameworkLeakViolations = this.detectFrameworkLeaks(sourceFiles)
|
)
|
||||||
const entityExposureViolations = this.detectEntityExposures(sourceFiles)
|
const namingViolations = this.sortBySeverity(this.detectNamingConventions(sourceFiles))
|
||||||
const dependencyDirectionViolations = this.detectDependencyDirections(sourceFiles)
|
const frameworkLeakViolations = this.sortBySeverity(
|
||||||
const repositoryPatternViolations = this.detectRepositoryPatternViolations(sourceFiles)
|
this.detectFrameworkLeaks(sourceFiles),
|
||||||
|
)
|
||||||
|
const entityExposureViolations = this.sortBySeverity(
|
||||||
|
this.detectEntityExposures(sourceFiles),
|
||||||
|
)
|
||||||
|
const dependencyDirectionViolations = this.sortBySeverity(
|
||||||
|
this.detectDependencyDirections(sourceFiles),
|
||||||
|
)
|
||||||
|
const repositoryPatternViolations = this.sortBySeverity(
|
||||||
|
this.detectRepositoryPatternViolations(sourceFiles),
|
||||||
|
)
|
||||||
const metrics = this.calculateMetrics(sourceFiles, totalFunctions, dependencyGraph)
|
const metrics = this.calculateMetrics(sourceFiles, totalFunctions, dependencyGraph)
|
||||||
|
|
||||||
return ResponseDto.ok({
|
return ResponseDto.ok({
|
||||||
@@ -294,6 +314,7 @@ export class AnalyzeProject extends UseCase<
|
|||||||
rule: RULES.CLEAN_ARCHITECTURE,
|
rule: RULES.CLEAN_ARCHITECTURE,
|
||||||
message: `Layer "${file.layer}" cannot import from "${importedLayer}"`,
|
message: `Layer "${file.layer}" cannot import from "${importedLayer}"`,
|
||||||
file: file.path.relative,
|
file: file.path.relative,
|
||||||
|
severity: VIOLATION_SEVERITY_MAP.ARCHITECTURE,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,6 +357,7 @@ export class AnalyzeProject extends UseCase<
|
|||||||
constantName: hardcoded.suggestConstantName(),
|
constantName: hardcoded.suggestConstantName(),
|
||||||
location: hardcoded.suggestLocation(file.layer),
|
location: hardcoded.suggestLocation(file.layer),
|
||||||
},
|
},
|
||||||
|
severity: VIOLATION_SEVERITY_MAP.HARDCODE,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,7 +377,7 @@ export class AnalyzeProject extends UseCase<
|
|||||||
rule: RULES.CIRCULAR_DEPENDENCY,
|
rule: RULES.CIRCULAR_DEPENDENCY,
|
||||||
message: `Circular dependency detected: ${cycleChain}`,
|
message: `Circular dependency detected: ${cycleChain}`,
|
||||||
cycle,
|
cycle,
|
||||||
severity: SEVERITY_LEVELS.ERROR,
|
severity: VIOLATION_SEVERITY_MAP.CIRCULAR_DEPENDENCY,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,6 +405,7 @@ export class AnalyzeProject extends UseCase<
|
|||||||
actual: violation.actual,
|
actual: violation.actual,
|
||||||
message: violation.getMessage(),
|
message: violation.getMessage(),
|
||||||
suggestion: violation.suggestion,
|
suggestion: violation.suggestion,
|
||||||
|
severity: VIOLATION_SEVERITY_MAP.NAMING_CONVENTION,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,6 +434,7 @@ export class AnalyzeProject extends UseCase<
|
|||||||
line: leak.line,
|
line: leak.line,
|
||||||
message: leak.getMessage(),
|
message: leak.getMessage(),
|
||||||
suggestion: leak.getSuggestion(),
|
suggestion: leak.getSuggestion(),
|
||||||
|
severity: VIOLATION_SEVERITY_MAP.FRAMEWORK_LEAK,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,6 +463,7 @@ export class AnalyzeProject extends UseCase<
|
|||||||
methodName: exposure.methodName,
|
methodName: exposure.methodName,
|
||||||
message: exposure.getMessage(),
|
message: exposure.getMessage(),
|
||||||
suggestion: exposure.getSuggestion(),
|
suggestion: exposure.getSuggestion(),
|
||||||
|
severity: VIOLATION_SEVERITY_MAP.ENTITY_EXPOSURE,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,6 +491,7 @@ export class AnalyzeProject extends UseCase<
|
|||||||
line: violation.line,
|
line: violation.line,
|
||||||
message: violation.getMessage(),
|
message: violation.getMessage(),
|
||||||
suggestion: violation.getSuggestion(),
|
suggestion: violation.getSuggestion(),
|
||||||
|
severity: VIOLATION_SEVERITY_MAP.DEPENDENCY_DIRECTION,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -499,6 +525,7 @@ export class AnalyzeProject extends UseCase<
|
|||||||
details: violation.details,
|
details: violation.details,
|
||||||
message: violation.getMessage(),
|
message: violation.getMessage(),
|
||||||
suggestion: violation.getSuggestion(),
|
suggestion: violation.getSuggestion(),
|
||||||
|
severity: VIOLATION_SEVERITY_MAP.REPOSITORY_PATTERN,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -528,4 +555,10 @@ export class AnalyzeProject extends UseCase<
|
|||||||
layerDistribution,
|
layerDistribution,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sortBySeverity<T extends { severity: SeverityLevel }>(violations: T[]): T[] {
|
||||||
|
return violations.sort((a, b) => {
|
||||||
|
return SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export const CLI_DESCRIPTIONS = {
|
|||||||
VERBOSE_OPTION: "Verbose output",
|
VERBOSE_OPTION: "Verbose output",
|
||||||
NO_HARDCODE_OPTION: "Skip hardcode detection",
|
NO_HARDCODE_OPTION: "Skip hardcode detection",
|
||||||
NO_ARCHITECTURE_OPTION: "Skip architecture checks",
|
NO_ARCHITECTURE_OPTION: "Skip architecture checks",
|
||||||
|
MIN_SEVERITY_OPTION: "Minimum severity level (critical, high, medium, low)",
|
||||||
|
ONLY_CRITICAL_OPTION: "Show only critical severity issues",
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const CLI_OPTIONS = {
|
export const CLI_OPTIONS = {
|
||||||
@@ -27,6 +29,8 @@ export const CLI_OPTIONS = {
|
|||||||
VERBOSE: "-v, --verbose",
|
VERBOSE: "-v, --verbose",
|
||||||
NO_HARDCODE: "--no-hardcode",
|
NO_HARDCODE: "--no-hardcode",
|
||||||
NO_ARCHITECTURE: "--no-architecture",
|
NO_ARCHITECTURE: "--no-architecture",
|
||||||
|
MIN_SEVERITY: "--min-severity <level>",
|
||||||
|
ONLY_CRITICAL: "--only-critical",
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const CLI_ARGUMENTS = {
|
export const CLI_ARGUMENTS = {
|
||||||
|
|||||||
@@ -11,6 +11,73 @@ import {
|
|||||||
CLI_OPTIONS,
|
CLI_OPTIONS,
|
||||||
DEFAULT_EXCLUDES,
|
DEFAULT_EXCLUDES,
|
||||||
} from "./constants"
|
} from "./constants"
|
||||||
|
import { SEVERITY_LEVELS, SEVERITY_ORDER, type SeverityLevel } from "../shared/constants"
|
||||||
|
|
||||||
|
const SEVERITY_LABELS: Record<SeverityLevel, string> = {
|
||||||
|
[SEVERITY_LEVELS.CRITICAL]: "🔴 CRITICAL",
|
||||||
|
[SEVERITY_LEVELS.HIGH]: "🟠 HIGH",
|
||||||
|
[SEVERITY_LEVELS.MEDIUM]: "🟡 MEDIUM",
|
||||||
|
[SEVERITY_LEVELS.LOW]: "🟢 LOW",
|
||||||
|
}
|
||||||
|
|
||||||
|
const SEVERITY_HEADER: Record<SeverityLevel, string> = {
|
||||||
|
[SEVERITY_LEVELS.CRITICAL]:
|
||||||
|
"\n═══════════════════════════════════════════\n🔴 CRITICAL SEVERITY\n═══════════════════════════════════════════",
|
||||||
|
[SEVERITY_LEVELS.HIGH]:
|
||||||
|
"\n═══════════════════════════════════════════\n🟠 HIGH SEVERITY\n═══════════════════════════════════════════",
|
||||||
|
[SEVERITY_LEVELS.MEDIUM]:
|
||||||
|
"\n═══════════════════════════════════════════\n🟡 MEDIUM SEVERITY\n═══════════════════════════════════════════",
|
||||||
|
[SEVERITY_LEVELS.LOW]:
|
||||||
|
"\n═══════════════════════════════════════════\n🟢 LOW SEVERITY\n═══════════════════════════════════════════",
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupBySeverity<T extends { severity: SeverityLevel }>(
|
||||||
|
violations: T[],
|
||||||
|
): Map<SeverityLevel, T[]> {
|
||||||
|
const grouped = new Map<SeverityLevel, T[]>()
|
||||||
|
|
||||||
|
for (const violation of violations) {
|
||||||
|
const existing = grouped.get(violation.severity) || []
|
||||||
|
existing.push(violation)
|
||||||
|
grouped.set(violation.severity, existing)
|
||||||
|
}
|
||||||
|
|
||||||
|
return grouped
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterBySeverity<T extends { severity: SeverityLevel }>(
|
||||||
|
violations: T[],
|
||||||
|
minSeverity?: SeverityLevel,
|
||||||
|
): T[] {
|
||||||
|
if (!minSeverity) {
|
||||||
|
return violations
|
||||||
|
}
|
||||||
|
|
||||||
|
const minSeverityOrder = SEVERITY_ORDER[minSeverity]
|
||||||
|
return violations.filter((v) => SEVERITY_ORDER[v.severity] <= minSeverityOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayGroupedViolations<T extends { severity: SeverityLevel }>(
|
||||||
|
violations: T[],
|
||||||
|
displayFn: (v: T, index: number) => void,
|
||||||
|
): void {
|
||||||
|
const grouped = groupBySeverity(violations)
|
||||||
|
const severities: SeverityLevel[] = [
|
||||||
|
SEVERITY_LEVELS.CRITICAL,
|
||||||
|
SEVERITY_LEVELS.HIGH,
|
||||||
|
SEVERITY_LEVELS.MEDIUM,
|
||||||
|
SEVERITY_LEVELS.LOW,
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const severity of severities) {
|
||||||
|
const items = grouped.get(severity)
|
||||||
|
if (items && items.length > 0) {
|
||||||
|
console.log(SEVERITY_HEADER[severity])
|
||||||
|
console.log(`Found ${String(items.length)} issue(s)\n`)
|
||||||
|
items.forEach(displayFn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const program = new Command()
|
const program = new Command()
|
||||||
|
|
||||||
@@ -24,6 +91,8 @@ program
|
|||||||
.option(CLI_OPTIONS.VERBOSE, CLI_DESCRIPTIONS.VERBOSE_OPTION, false)
|
.option(CLI_OPTIONS.VERBOSE, CLI_DESCRIPTIONS.VERBOSE_OPTION, false)
|
||||||
.option(CLI_OPTIONS.NO_HARDCODE, CLI_DESCRIPTIONS.NO_HARDCODE_OPTION)
|
.option(CLI_OPTIONS.NO_HARDCODE, CLI_DESCRIPTIONS.NO_HARDCODE_OPTION)
|
||||||
.option(CLI_OPTIONS.NO_ARCHITECTURE, CLI_DESCRIPTIONS.NO_ARCHITECTURE_OPTION)
|
.option(CLI_OPTIONS.NO_ARCHITECTURE, CLI_DESCRIPTIONS.NO_ARCHITECTURE_OPTION)
|
||||||
|
.option(CLI_OPTIONS.MIN_SEVERITY, CLI_DESCRIPTIONS.MIN_SEVERITY_OPTION)
|
||||||
|
.option(CLI_OPTIONS.ONLY_CRITICAL, CLI_DESCRIPTIONS.ONLY_CRITICAL_OPTION, false)
|
||||||
.action(async (path: string, options) => {
|
.action(async (path: string, options) => {
|
||||||
try {
|
try {
|
||||||
console.log(CLI_MESSAGES.ANALYZING)
|
console.log(CLI_MESSAGES.ANALYZING)
|
||||||
@@ -33,16 +102,52 @@ program
|
|||||||
exclude: options.exclude,
|
exclude: options.exclude,
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
let {
|
||||||
hardcodeViolations,
|
hardcodeViolations,
|
||||||
violations,
|
violations,
|
||||||
circularDependencyViolations,
|
circularDependencyViolations,
|
||||||
namingViolations,
|
namingViolations,
|
||||||
frameworkLeakViolations,
|
frameworkLeakViolations,
|
||||||
entityExposureViolations,
|
entityExposureViolations,
|
||||||
|
dependencyDirectionViolations,
|
||||||
|
repositoryPatternViolations,
|
||||||
metrics,
|
metrics,
|
||||||
} = result
|
} = result
|
||||||
|
|
||||||
|
const minSeverity: SeverityLevel | undefined = options.onlyCritical
|
||||||
|
? SEVERITY_LEVELS.CRITICAL
|
||||||
|
: options.minSeverity
|
||||||
|
? (options.minSeverity.toLowerCase() as SeverityLevel)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (minSeverity) {
|
||||||
|
violations = filterBySeverity(violations, minSeverity)
|
||||||
|
hardcodeViolations = filterBySeverity(hardcodeViolations, minSeverity)
|
||||||
|
circularDependencyViolations = filterBySeverity(
|
||||||
|
circularDependencyViolations,
|
||||||
|
minSeverity,
|
||||||
|
)
|
||||||
|
namingViolations = filterBySeverity(namingViolations, minSeverity)
|
||||||
|
frameworkLeakViolations = filterBySeverity(frameworkLeakViolations, minSeverity)
|
||||||
|
entityExposureViolations = filterBySeverity(entityExposureViolations, minSeverity)
|
||||||
|
dependencyDirectionViolations = filterBySeverity(
|
||||||
|
dependencyDirectionViolations,
|
||||||
|
minSeverity,
|
||||||
|
)
|
||||||
|
repositoryPatternViolations = filterBySeverity(
|
||||||
|
repositoryPatternViolations,
|
||||||
|
minSeverity,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (options.onlyCritical) {
|
||||||
|
console.log("\n🔴 Filtering: Showing only CRITICAL severity issues\n")
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`\n⚠️ Filtering: Showing ${minSeverity.toUpperCase()} severity and above\n`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Display metrics
|
// Display metrics
|
||||||
console.log(CLI_MESSAGES.METRICS_HEADER)
|
console.log(CLI_MESSAGES.METRICS_HEADER)
|
||||||
console.log(` ${CLI_LABELS.FILES_ANALYZED} ${String(metrics.totalFiles)}`)
|
console.log(` ${CLI_LABELS.FILES_ANALYZED} ${String(metrics.totalFiles)}`)
|
||||||
@@ -59,11 +164,12 @@ program
|
|||||||
// Architecture violations
|
// Architecture violations
|
||||||
if (options.architecture && violations.length > 0) {
|
if (options.architecture && violations.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
`${CLI_MESSAGES.VIOLATIONS_HEADER} ${String(violations.length)} ${CLI_LABELS.ARCHITECTURE_VIOLATIONS}\n`,
|
`\n${CLI_MESSAGES.VIOLATIONS_HEADER} ${String(violations.length)} ${CLI_LABELS.ARCHITECTURE_VIOLATIONS}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
violations.forEach((v, index) => {
|
displayGroupedViolations(violations, (v, index) => {
|
||||||
console.log(`${String(index + 1)}. ${v.file}`)
|
console.log(`${String(index + 1)}. ${v.file}`)
|
||||||
|
console.log(` Severity: ${SEVERITY_LABELS[v.severity]}`)
|
||||||
console.log(` Rule: ${v.rule}`)
|
console.log(` Rule: ${v.rule}`)
|
||||||
console.log(` ${v.message}`)
|
console.log(` ${v.message}`)
|
||||||
console.log("")
|
console.log("")
|
||||||
@@ -73,12 +179,12 @@ program
|
|||||||
// Circular dependency violations
|
// Circular dependency violations
|
||||||
if (options.architecture && circularDependencyViolations.length > 0) {
|
if (options.architecture && circularDependencyViolations.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
`${CLI_MESSAGES.CIRCULAR_DEPS_HEADER} ${String(circularDependencyViolations.length)} ${CLI_LABELS.CIRCULAR_DEPENDENCIES}\n`,
|
`\n${CLI_MESSAGES.CIRCULAR_DEPS_HEADER} ${String(circularDependencyViolations.length)} ${CLI_LABELS.CIRCULAR_DEPENDENCIES}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
circularDependencyViolations.forEach((cd, index) => {
|
displayGroupedViolations(circularDependencyViolations, (cd, index) => {
|
||||||
console.log(`${String(index + 1)}. ${cd.message}`)
|
console.log(`${String(index + 1)}. ${cd.message}`)
|
||||||
console.log(` Severity: ${cd.severity}`)
|
console.log(` Severity: ${SEVERITY_LABELS[cd.severity]}`)
|
||||||
console.log(" Cycle path:")
|
console.log(" Cycle path:")
|
||||||
cd.cycle.forEach((file, i) => {
|
cd.cycle.forEach((file, i) => {
|
||||||
console.log(` ${String(i + 1)}. ${file}`)
|
console.log(` ${String(i + 1)}. ${file}`)
|
||||||
@@ -93,11 +199,12 @@ program
|
|||||||
// Naming convention violations
|
// Naming convention violations
|
||||||
if (options.architecture && namingViolations.length > 0) {
|
if (options.architecture && namingViolations.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
`${CLI_MESSAGES.NAMING_VIOLATIONS_HEADER} ${String(namingViolations.length)} ${CLI_LABELS.NAMING_VIOLATIONS}\n`,
|
`\n${CLI_MESSAGES.NAMING_VIOLATIONS_HEADER} ${String(namingViolations.length)} ${CLI_LABELS.NAMING_VIOLATIONS}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
namingViolations.forEach((nc, index) => {
|
displayGroupedViolations(namingViolations, (nc, index) => {
|
||||||
console.log(`${String(index + 1)}. ${nc.file}`)
|
console.log(`${String(index + 1)}. ${nc.file}`)
|
||||||
|
console.log(` Severity: ${SEVERITY_LABELS[nc.severity]}`)
|
||||||
console.log(` File: ${nc.fileName}`)
|
console.log(` File: ${nc.fileName}`)
|
||||||
console.log(` Layer: ${nc.layer}`)
|
console.log(` Layer: ${nc.layer}`)
|
||||||
console.log(` Type: ${nc.type}`)
|
console.log(` Type: ${nc.type}`)
|
||||||
@@ -112,11 +219,12 @@ program
|
|||||||
// Framework leak violations
|
// Framework leak violations
|
||||||
if (options.architecture && frameworkLeakViolations.length > 0) {
|
if (options.architecture && frameworkLeakViolations.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
`\n🏗️ Found ${String(frameworkLeakViolations.length)} framework leak(s):\n`,
|
`\n🏗️ Found ${String(frameworkLeakViolations.length)} framework leak(s)`,
|
||||||
)
|
)
|
||||||
|
|
||||||
frameworkLeakViolations.forEach((fl, index) => {
|
displayGroupedViolations(frameworkLeakViolations, (fl, index) => {
|
||||||
console.log(`${String(index + 1)}. ${fl.file}`)
|
console.log(`${String(index + 1)}. ${fl.file}`)
|
||||||
|
console.log(` Severity: ${SEVERITY_LABELS[fl.severity]}`)
|
||||||
console.log(` Package: ${fl.packageName}`)
|
console.log(` Package: ${fl.packageName}`)
|
||||||
console.log(` Category: ${fl.categoryDescription}`)
|
console.log(` Category: ${fl.categoryDescription}`)
|
||||||
console.log(` Layer: ${fl.layer}`)
|
console.log(` Layer: ${fl.layer}`)
|
||||||
@@ -130,12 +238,13 @@ program
|
|||||||
// Entity exposure violations
|
// Entity exposure violations
|
||||||
if (options.architecture && entityExposureViolations.length > 0) {
|
if (options.architecture && entityExposureViolations.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
`\n🎭 Found ${String(entityExposureViolations.length)} entity exposure(s):\n`,
|
`\n🎭 Found ${String(entityExposureViolations.length)} entity exposure(s)`,
|
||||||
)
|
)
|
||||||
|
|
||||||
entityExposureViolations.forEach((ee, index) => {
|
displayGroupedViolations(entityExposureViolations, (ee, index) => {
|
||||||
const location = ee.line ? `${ee.file}:${String(ee.line)}` : ee.file
|
const location = ee.line ? `${ee.file}:${String(ee.line)}` : ee.file
|
||||||
console.log(`${String(index + 1)}. ${location}`)
|
console.log(`${String(index + 1)}. ${location}`)
|
||||||
|
console.log(` Severity: ${SEVERITY_LABELS[ee.severity]}`)
|
||||||
console.log(` Entity: ${ee.entityName}`)
|
console.log(` Entity: ${ee.entityName}`)
|
||||||
console.log(` Return Type: ${ee.returnType}`)
|
console.log(` Return Type: ${ee.returnType}`)
|
||||||
if (ee.methodName) {
|
if (ee.methodName) {
|
||||||
@@ -154,16 +263,53 @@ program
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dependency direction violations
|
||||||
|
if (options.architecture && dependencyDirectionViolations.length > 0) {
|
||||||
|
console.log(
|
||||||
|
`\n⚠️ Found ${String(dependencyDirectionViolations.length)} dependency direction violation(s)`,
|
||||||
|
)
|
||||||
|
|
||||||
|
displayGroupedViolations(dependencyDirectionViolations, (dd, index) => {
|
||||||
|
console.log(`${String(index + 1)}. ${dd.file}`)
|
||||||
|
console.log(` Severity: ${SEVERITY_LABELS[dd.severity]}`)
|
||||||
|
console.log(` From Layer: ${dd.fromLayer}`)
|
||||||
|
console.log(` To Layer: ${dd.toLayer}`)
|
||||||
|
console.log(` Import: ${dd.importPath}`)
|
||||||
|
console.log(` ${dd.message}`)
|
||||||
|
console.log(` 💡 Suggestion: ${dd.suggestion}`)
|
||||||
|
console.log("")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repository pattern violations
|
||||||
|
if (options.architecture && repositoryPatternViolations.length > 0) {
|
||||||
|
console.log(
|
||||||
|
`\n📦 Found ${String(repositoryPatternViolations.length)} repository pattern violation(s)`,
|
||||||
|
)
|
||||||
|
|
||||||
|
displayGroupedViolations(repositoryPatternViolations, (rp, index) => {
|
||||||
|
console.log(`${String(index + 1)}. ${rp.file}`)
|
||||||
|
console.log(` Severity: ${SEVERITY_LABELS[rp.severity]}`)
|
||||||
|
console.log(` Layer: ${rp.layer}`)
|
||||||
|
console.log(` Type: ${rp.violationType}`)
|
||||||
|
console.log(` Details: ${rp.details}`)
|
||||||
|
console.log(` ${rp.message}`)
|
||||||
|
console.log(` 💡 Suggestion: ${rp.suggestion}`)
|
||||||
|
console.log("")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Hardcode violations
|
// Hardcode violations
|
||||||
if (options.hardcode && hardcodeViolations.length > 0) {
|
if (options.hardcode && hardcodeViolations.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
`${CLI_MESSAGES.HARDCODE_VIOLATIONS_HEADER} ${String(hardcodeViolations.length)} ${CLI_LABELS.HARDCODE_VIOLATIONS}\n`,
|
`\n${CLI_MESSAGES.HARDCODE_VIOLATIONS_HEADER} ${String(hardcodeViolations.length)} ${CLI_LABELS.HARDCODE_VIOLATIONS}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
hardcodeViolations.forEach((hc, index) => {
|
displayGroupedViolations(hardcodeViolations, (hc, index) => {
|
||||||
console.log(
|
console.log(
|
||||||
`${String(index + 1)}. ${hc.file}:${String(hc.line)}:${String(hc.column)}`,
|
`${String(index + 1)}. ${hc.file}:${String(hc.line)}:${String(hc.column)}`,
|
||||||
)
|
)
|
||||||
|
console.log(` Severity: ${SEVERITY_LABELS[hc.severity]}`)
|
||||||
console.log(` Type: ${hc.type}`)
|
console.log(` Type: ${hc.type}`)
|
||||||
console.log(` Value: ${JSON.stringify(hc.value)}`)
|
console.log(` Value: ${JSON.stringify(hc.value)}`)
|
||||||
console.log(` Context: ${hc.context.trim()}`)
|
console.log(` Context: ${hc.context.trim()}`)
|
||||||
@@ -180,7 +326,9 @@ program
|
|||||||
circularDependencyViolations.length +
|
circularDependencyViolations.length +
|
||||||
namingViolations.length +
|
namingViolations.length +
|
||||||
frameworkLeakViolations.length +
|
frameworkLeakViolations.length +
|
||||||
entityExposureViolations.length
|
entityExposureViolations.length +
|
||||||
|
dependencyDirectionViolations.length +
|
||||||
|
repositoryPatternViolations.length
|
||||||
|
|
||||||
if (totalIssues === 0) {
|
if (totalIssues === 0) {
|
||||||
console.log(CLI_MESSAGES.NO_ISSUES)
|
console.log(CLI_MESSAGES.NO_ISSUES)
|
||||||
|
|||||||
@@ -64,9 +64,36 @@ export const PLACEHOLDERS = {
|
|||||||
* Violation severity levels
|
* Violation severity levels
|
||||||
*/
|
*/
|
||||||
export const SEVERITY_LEVELS = {
|
export const SEVERITY_LEVELS = {
|
||||||
ERROR: "error",
|
CRITICAL: "critical",
|
||||||
WARNING: "warning",
|
HIGH: "high",
|
||||||
INFO: "info",
|
MEDIUM: "medium",
|
||||||
|
LOW: "low",
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SeverityLevel = (typeof SEVERITY_LEVELS)[keyof typeof SEVERITY_LEVELS]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Severity order for sorting (lower number = more critical)
|
||||||
|
*/
|
||||||
|
export const SEVERITY_ORDER: Record<SeverityLevel, number> = {
|
||||||
|
[SEVERITY_LEVELS.CRITICAL]: 0,
|
||||||
|
[SEVERITY_LEVELS.HIGH]: 1,
|
||||||
|
[SEVERITY_LEVELS.MEDIUM]: 2,
|
||||||
|
[SEVERITY_LEVELS.LOW]: 3,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Violation type to severity mapping
|
||||||
|
*/
|
||||||
|
export const VIOLATION_SEVERITY_MAP = {
|
||||||
|
CIRCULAR_DEPENDENCY: SEVERITY_LEVELS.CRITICAL,
|
||||||
|
REPOSITORY_PATTERN: SEVERITY_LEVELS.CRITICAL,
|
||||||
|
DEPENDENCY_DIRECTION: SEVERITY_LEVELS.HIGH,
|
||||||
|
FRAMEWORK_LEAK: SEVERITY_LEVELS.HIGH,
|
||||||
|
ENTITY_EXPOSURE: SEVERITY_LEVELS.HIGH,
|
||||||
|
NAMING_CONVENTION: SEVERITY_LEVELS.MEDIUM,
|
||||||
|
ARCHITECTURE: SEVERITY_LEVELS.MEDIUM,
|
||||||
|
HARDCODE: SEVERITY_LEVELS.LOW,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export * from "./rules"
|
export * from "./rules"
|
||||||
|
|||||||
Reference in New Issue
Block a user