mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-28 15:26:53 +05:00
feat(guardian): add guardian package - code quality analyzer
Add @puaros/guardian package v0.1.0 - code quality guardian for vibe coders and enterprise teams. Features: - Hardcode detection (magic numbers, magic strings) - Circular dependency detection - Naming convention enforcement (Clean Architecture) - Architecture violation detection - CLI tool with comprehensive reporting - 159 tests with 80%+ coverage - Smart suggestions for fixes - Built for AI-assisted development Built with Clean Architecture and DDD principles. Works with Claude, GPT, Copilot, Cursor, and any AI coding assistant.
This commit is contained in:
109
packages/guardian/src/domain/entities/DependencyGraph.ts
Normal file
109
packages/guardian/src/domain/entities/DependencyGraph.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { BaseEntity } from "./BaseEntity"
|
||||
import { SourceFile } from "./SourceFile"
|
||||
|
||||
interface GraphNode {
|
||||
file: SourceFile
|
||||
dependencies: string[]
|
||||
dependents: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents dependency graph of the analyzed project
|
||||
*/
|
||||
export class DependencyGraph extends BaseEntity {
|
||||
private readonly nodes: Map<string, GraphNode>
|
||||
|
||||
constructor(id?: string) {
|
||||
super(id)
|
||||
this.nodes = new Map()
|
||||
}
|
||||
|
||||
public addFile(file: SourceFile): void {
|
||||
const fileId = file.path.relative
|
||||
|
||||
if (!this.nodes.has(fileId)) {
|
||||
this.nodes.set(fileId, {
|
||||
file,
|
||||
dependencies: [],
|
||||
dependents: [],
|
||||
})
|
||||
}
|
||||
|
||||
this.touch()
|
||||
}
|
||||
|
||||
public addDependency(from: string, to: string): void {
|
||||
const fromNode = this.nodes.get(from)
|
||||
const toNode = this.nodes.get(to)
|
||||
|
||||
if (fromNode && toNode) {
|
||||
if (!fromNode.dependencies.includes(to)) {
|
||||
fromNode.dependencies.push(to)
|
||||
}
|
||||
if (!toNode.dependents.includes(from)) {
|
||||
toNode.dependents.push(from)
|
||||
}
|
||||
this.touch()
|
||||
}
|
||||
}
|
||||
|
||||
public getNode(filePath: string): GraphNode | undefined {
|
||||
return this.nodes.get(filePath)
|
||||
}
|
||||
|
||||
public getAllNodes(): GraphNode[] {
|
||||
return Array.from(this.nodes.values())
|
||||
}
|
||||
|
||||
public findCycles(): string[][] {
|
||||
const cycles: string[][] = []
|
||||
const visited = new Set<string>()
|
||||
const recursionStack = new Set<string>()
|
||||
|
||||
const dfs = (nodeId: string, path: string[]): void => {
|
||||
visited.add(nodeId)
|
||||
recursionStack.add(nodeId)
|
||||
path.push(nodeId)
|
||||
|
||||
const node = this.nodes.get(nodeId)
|
||||
if (node) {
|
||||
for (const dep of node.dependencies) {
|
||||
if (!visited.has(dep)) {
|
||||
dfs(dep, [...path])
|
||||
} else if (recursionStack.has(dep)) {
|
||||
const cycleStart = path.indexOf(dep)
|
||||
cycles.push(path.slice(cycleStart))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recursionStack.delete(nodeId)
|
||||
}
|
||||
|
||||
for (const nodeId of this.nodes.keys()) {
|
||||
if (!visited.has(nodeId)) {
|
||||
dfs(nodeId, [])
|
||||
}
|
||||
}
|
||||
|
||||
return cycles
|
||||
}
|
||||
|
||||
public getMetrics(): {
|
||||
totalFiles: number
|
||||
totalDependencies: number
|
||||
avgDependencies: number
|
||||
maxDependencies: number
|
||||
} {
|
||||
const nodes = Array.from(this.nodes.values())
|
||||
const totalFiles = nodes.length
|
||||
const totalDependencies = nodes.reduce((sum, node) => sum + node.dependencies.length, 0)
|
||||
|
||||
return {
|
||||
totalFiles,
|
||||
totalDependencies,
|
||||
avgDependencies: totalFiles > 0 ? totalDependencies / totalFiles : 0,
|
||||
maxDependencies: Math.max(...nodes.map((node) => node.dependencies.length), 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user