@samiyev/guardian
Version:
Research-backed code quality guardian for AI-assisted development. Detects hardcodes, secrets, circular deps, framework leaks, entity exposure, and 9 architecture violations. Enforces Clean Architecture/DDD principles. Works with GitHub Copilot, Cursor, W
312 lines • 14.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExecuteDetection = void 0;
const HardcodedValue_1 = require("../../../domain/value-objects/HardcodedValue");
const constants_1 = require("../../../shared/constants");
/**
* Pipeline step responsible for running all detectors
*/
class ExecuteDetection {
hardcodeDetector;
namingConventionDetector;
frameworkLeakDetector;
entityExposureDetector;
dependencyDirectionDetector;
repositoryPatternDetector;
aggregateBoundaryDetector;
secretDetector;
anemicModelDetector;
duplicateValueTracker;
// eslint-disable-next-line max-params
constructor(hardcodeDetector, namingConventionDetector, frameworkLeakDetector, entityExposureDetector, dependencyDirectionDetector, repositoryPatternDetector, aggregateBoundaryDetector, secretDetector, anemicModelDetector, duplicateValueTracker) {
this.hardcodeDetector = hardcodeDetector;
this.namingConventionDetector = namingConventionDetector;
this.frameworkLeakDetector = frameworkLeakDetector;
this.entityExposureDetector = entityExposureDetector;
this.dependencyDirectionDetector = dependencyDirectionDetector;
this.repositoryPatternDetector = repositoryPatternDetector;
this.aggregateBoundaryDetector = aggregateBoundaryDetector;
this.secretDetector = secretDetector;
this.anemicModelDetector = anemicModelDetector;
this.duplicateValueTracker = duplicateValueTracker;
}
async execute(request) {
const secretViolations = await this.detectSecrets(request.sourceFiles);
return {
violations: this.sortBySeverity(this.detectViolations(request.sourceFiles)),
hardcodeViolations: this.sortBySeverity(this.detectHardcode(request.sourceFiles)),
circularDependencyViolations: this.sortBySeverity(this.detectCircularDependencies(request.dependencyGraph)),
namingViolations: this.sortBySeverity(this.detectNamingConventions(request.sourceFiles)),
frameworkLeakViolations: this.sortBySeverity(this.detectFrameworkLeaks(request.sourceFiles)),
entityExposureViolations: this.sortBySeverity(this.detectEntityExposures(request.sourceFiles)),
dependencyDirectionViolations: this.sortBySeverity(this.detectDependencyDirections(request.sourceFiles)),
repositoryPatternViolations: this.sortBySeverity(this.detectRepositoryPatternViolations(request.sourceFiles)),
aggregateBoundaryViolations: this.sortBySeverity(this.detectAggregateBoundaryViolations(request.sourceFiles)),
secretViolations: this.sortBySeverity(secretViolations),
anemicModelViolations: this.sortBySeverity(this.detectAnemicModels(request.sourceFiles)),
};
}
detectViolations(sourceFiles) {
const violations = [];
const layerRules = {
[constants_1.LAYERS.DOMAIN]: [constants_1.LAYERS.SHARED],
[constants_1.LAYERS.APPLICATION]: [constants_1.LAYERS.DOMAIN, constants_1.LAYERS.SHARED],
[constants_1.LAYERS.INFRASTRUCTURE]: [constants_1.LAYERS.DOMAIN, constants_1.LAYERS.APPLICATION, constants_1.LAYERS.SHARED],
[constants_1.LAYERS.SHARED]: [],
};
for (const file of sourceFiles) {
if (!file.layer) {
continue;
}
const allowedLayers = layerRules[file.layer];
for (const imp of file.imports) {
const importedLayer = this.detectLayerFromImport(imp);
if (importedLayer &&
importedLayer !== file.layer &&
!allowedLayers.includes(importedLayer)) {
violations.push({
rule: constants_1.RULES.CLEAN_ARCHITECTURE,
message: `Layer "${file.layer}" cannot import from "${importedLayer}"`,
file: file.path.relative,
severity: constants_1.VIOLATION_SEVERITY_MAP.ARCHITECTURE,
});
}
}
}
return violations;
}
detectLayerFromImport(importPath) {
const layers = Object.values(constants_1.LAYERS);
for (const layer of layers) {
if (importPath.toLowerCase().includes(layer)) {
return layer;
}
}
return undefined;
}
detectHardcode(sourceFiles) {
const allHardcodedValues = [];
for (const file of sourceFiles) {
const hardcodedValues = this.hardcodeDetector.detectAll(file.content, file.path.relative);
for (const hardcoded of hardcodedValues) {
allHardcodedValues.push({ value: hardcoded, file });
}
}
this.duplicateValueTracker.clear();
for (const { value, file } of allHardcodedValues) {
this.duplicateValueTracker.track(value, file.path.relative);
}
const violations = [];
for (const { value, file } of allHardcodedValues) {
const duplicateLocations = this.duplicateValueTracker.getDuplicateLocations(value.value, value.type);
const enrichedValue = duplicateLocations
? HardcodedValue_1.HardcodedValue.create(value.value, value.type, value.line, value.column, value.context, value.valueType, duplicateLocations.filter((loc) => loc.file !== file.path.relative))
: value;
if (enrichedValue.shouldSkip(file.layer)) {
continue;
}
violations.push({
rule: constants_1.RULES.HARDCODED_VALUE,
type: enrichedValue.type,
value: enrichedValue.value,
file: file.path.relative,
line: enrichedValue.line,
column: enrichedValue.column,
context: enrichedValue.context,
suggestion: {
constantName: enrichedValue.suggestConstantName(),
location: enrichedValue.suggestLocation(file.layer),
},
severity: constants_1.VIOLATION_SEVERITY_MAP.HARDCODE,
});
}
return violations;
}
detectCircularDependencies(dependencyGraph) {
const violations = [];
const cycles = dependencyGraph.findCycles();
for (const cycle of cycles) {
const cycleChain = [...cycle, cycle[0]].join(" → ");
violations.push({
rule: constants_1.RULES.CIRCULAR_DEPENDENCY,
message: `Circular dependency detected: ${cycleChain}`,
cycle,
severity: constants_1.VIOLATION_SEVERITY_MAP.CIRCULAR_DEPENDENCY,
});
}
return violations;
}
detectNamingConventions(sourceFiles) {
const violations = [];
for (const file of sourceFiles) {
const namingViolations = this.namingConventionDetector.detectViolations(file.content, file.path.filename, file.layer, file.path.relative);
for (const violation of namingViolations) {
violations.push({
rule: constants_1.RULES.NAMING_CONVENTION,
type: violation.violationType,
fileName: violation.fileName,
layer: violation.layer,
file: violation.filePath,
expected: violation.expected,
actual: violation.actual,
message: violation.getMessage(),
suggestion: violation.suggestion,
severity: constants_1.VIOLATION_SEVERITY_MAP.NAMING_CONVENTION,
});
}
}
return violations;
}
detectFrameworkLeaks(sourceFiles) {
const violations = [];
for (const file of sourceFiles) {
const leaks = this.frameworkLeakDetector.detectLeaks(file.imports, file.path.relative, file.layer);
for (const leak of leaks) {
violations.push({
rule: constants_1.RULES.FRAMEWORK_LEAK,
packageName: leak.packageName,
category: leak.category,
categoryDescription: leak.getCategoryDescription(),
file: file.path.relative,
layer: leak.layer,
line: leak.line,
message: leak.getMessage(),
suggestion: leak.getSuggestion(),
severity: constants_1.VIOLATION_SEVERITY_MAP.FRAMEWORK_LEAK,
});
}
}
return violations;
}
detectEntityExposures(sourceFiles) {
const violations = [];
for (const file of sourceFiles) {
const exposures = this.entityExposureDetector.detectExposures(file.content, file.path.relative, file.layer);
for (const exposure of exposures) {
violations.push({
rule: constants_1.RULES.ENTITY_EXPOSURE,
entityName: exposure.entityName,
returnType: exposure.returnType,
file: file.path.relative,
layer: exposure.layer,
line: exposure.line,
methodName: exposure.methodName,
message: exposure.getMessage(),
suggestion: exposure.getSuggestion(),
severity: constants_1.VIOLATION_SEVERITY_MAP.ENTITY_EXPOSURE,
});
}
}
return violations;
}
detectDependencyDirections(sourceFiles) {
const violations = [];
for (const file of sourceFiles) {
const directionViolations = this.dependencyDirectionDetector.detectViolations(file.content, file.path.relative, file.layer);
for (const violation of directionViolations) {
violations.push({
rule: constants_1.RULES.DEPENDENCY_DIRECTION,
fromLayer: violation.fromLayer,
toLayer: violation.toLayer,
importPath: violation.importPath,
file: file.path.relative,
line: violation.line,
message: violation.getMessage(),
suggestion: violation.getSuggestion(),
severity: constants_1.VIOLATION_SEVERITY_MAP.DEPENDENCY_DIRECTION,
});
}
}
return violations;
}
detectRepositoryPatternViolations(sourceFiles) {
const violations = [];
for (const file of sourceFiles) {
const patternViolations = this.repositoryPatternDetector.detectViolations(file.content, file.path.relative, file.layer);
for (const violation of patternViolations) {
violations.push({
rule: constants_1.RULES.REPOSITORY_PATTERN,
violationType: violation.violationType,
file: file.path.relative,
layer: violation.layer,
line: violation.line,
details: violation.details,
message: violation.getMessage(),
suggestion: violation.getSuggestion(),
severity: constants_1.VIOLATION_SEVERITY_MAP.REPOSITORY_PATTERN,
});
}
}
return violations;
}
detectAggregateBoundaryViolations(sourceFiles) {
const violations = [];
for (const file of sourceFiles) {
const boundaryViolations = this.aggregateBoundaryDetector.detectViolations(file.content, file.path.relative, file.layer);
for (const violation of boundaryViolations) {
violations.push({
rule: constants_1.RULES.AGGREGATE_BOUNDARY,
fromAggregate: violation.fromAggregate,
toAggregate: violation.toAggregate,
entityName: violation.entityName,
importPath: violation.importPath,
file: file.path.relative,
line: violation.line,
message: violation.getMessage(),
suggestion: violation.getSuggestion(),
severity: constants_1.VIOLATION_SEVERITY_MAP.AGGREGATE_BOUNDARY,
});
}
}
return violations;
}
async detectSecrets(sourceFiles) {
const violations = [];
for (const file of sourceFiles) {
const secretViolations = await this.secretDetector.detectAll(file.content, file.path.relative);
for (const secret of secretViolations) {
violations.push({
rule: constants_1.RULES.SECRET_EXPOSURE,
secretType: secret.secretType,
file: file.path.relative,
line: secret.line,
column: secret.column,
message: secret.getMessage(),
suggestion: secret.getSuggestion(),
severity: "critical",
});
}
}
return violations;
}
detectAnemicModels(sourceFiles) {
const violations = [];
for (const file of sourceFiles) {
const anemicModels = this.anemicModelDetector.detectAnemicModels(file.content, file.path.relative, file.layer);
for (const anemicModel of anemicModels) {
violations.push({
rule: constants_1.RULES.ANEMIC_MODEL,
className: anemicModel.className,
file: file.path.relative,
layer: anemicModel.layer,
line: anemicModel.line,
methodCount: anemicModel.methodCount,
propertyCount: anemicModel.propertyCount,
hasOnlyGettersSetters: anemicModel.hasOnlyGettersSetters,
hasPublicSetters: anemicModel.hasPublicSetters,
message: anemicModel.getMessage(),
suggestion: anemicModel.getSuggestion(),
severity: constants_1.VIOLATION_SEVERITY_MAP.ANEMIC_MODEL,
});
}
}
return violations;
}
sortBySeverity(violations) {
return violations.sort((a, b) => {
return constants_1.SEVERITY_ORDER[a.severity] - constants_1.SEVERITY_ORDER[b.severity];
});
}
}
exports.ExecuteDetection = ExecuteDetection;
//# sourceMappingURL=ExecuteDetection.js.map