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:
imfozilbek
2025-11-24 02:54:39 +05:00
parent 9f97509b06
commit 03705b5264
96 changed files with 9520 additions and 0 deletions

View File

@@ -0,0 +1,263 @@
import { BaseEntity } from "../../../../src/domain/entities/BaseEntity"
import { OrderId } from "../value-objects/OrderId"
import { UserId } from "../value-objects/UserId"
import { OrderStatus } from "../value-objects/OrderStatus"
import { Money } from "../value-objects/Money"
import { OrderItem } from "../entities/OrderItem"
/**
* Order Aggregate Root
*
* DDD Patterns:
* - Aggregate Root: controls access to OrderItems
* - Consistency Boundary: all changes through Order
* - Rich Domain Model: contains business logic
*
* SOLID Principles:
* - SRP: manages order lifecycle
* - OCP: extensible through status transitions
* - ISP: focused interface for order operations
*
* Business Rules (Invariants):
* - Order must have at least one item
* - Cannot modify confirmed/paid/shipped orders
* - Status transitions must be valid
* - Total = sum of all items
* - Cannot cancel delivered orders
*
* Clean Code:
* - No magic numbers: MIN_ITEMS constant
* - Meaningful names: addItem, removeItem, confirm
* - Small methods: each does one thing
* - No hardcoded strings: OrderStatus enum
*/
export class Order extends BaseEntity {
private static readonly MIN_ITEMS = 1
private readonly _orderId: OrderId
private readonly _userId: UserId
private readonly _items: Map<string, OrderItem>
private _status: OrderStatus
private readonly _createdAt: Date
private _confirmedAt?: Date
private _deliveredAt?: Date
private constructor(
orderId: OrderId,
userId: UserId,
items: OrderItem[],
status: OrderStatus,
createdAt: Date,
confirmedAt?: Date,
deliveredAt?: Date,
) {
super(orderId.value)
this._orderId = orderId
this._userId = userId
this._items = new Map(items.map((item) => [item.id, item]))
this._status = status
this._createdAt = createdAt
this._confirmedAt = confirmedAt
this._deliveredAt = deliveredAt
this.validateInvariants()
}
/**
* Factory: Create new order
*/
public static create(userId: UserId): Order {
const orderId = OrderId.create()
const now = new Date()
return new Order(orderId, userId, [], OrderStatus.PENDING, now)
}
/**
* Factory: Reconstitute from persistence
*/
public static reconstitute(
orderId: OrderId,
userId: UserId,
items: OrderItem[],
status: OrderStatus,
createdAt: Date,
confirmedAt?: Date,
deliveredAt?: Date,
): Order {
return new Order(orderId, userId, items, status, createdAt, confirmedAt, deliveredAt)
}
/**
* Business Operation: Add item to order
*
* DDD: Only Aggregate Root can modify its entities
*/
public addItem(productId: string, productName: string, price: Money, quantity: number): void {
this.ensureCanModify()
const existingItem = Array.from(this._items.values()).find(
(item) => item.productId === productId,
)
if (existingItem) {
existingItem.updateQuantity(existingItem.quantity + quantity)
} else {
const newItem = OrderItem.create(productId, productName, price, quantity)
this._items.set(newItem.id, newItem)
}
this.touch()
}
/**
* Business Operation: Remove item from order
*/
public removeItem(itemId: string): void {
this.ensureCanModify()
if (!this._items.has(itemId)) {
throw new Error(`Item not found: ${itemId}`)
}
this._items.delete(itemId)
this.touch()
}
/**
* Business Operation: Update item quantity
*/
public updateItemQuantity(itemId: string, newQuantity: number): void {
this.ensureCanModify()
const item = this._items.get(itemId)
if (!item) {
throw new Error(`Item not found: ${itemId}`)
}
item.updateQuantity(newQuantity)
this.touch()
}
/**
* Business Operation: Confirm order
*/
public confirm(): void {
this.transitionTo(OrderStatus.CONFIRMED)
this._confirmedAt = new Date()
}
/**
* Business Operation: Mark as paid
*/
public markAsPaid(): void {
this.transitionTo(OrderStatus.PAID)
}
/**
* Business Operation: Ship order
*/
public ship(): void {
this.transitionTo(OrderStatus.SHIPPED)
}
/**
* Business Operation: Deliver order
*/
public deliver(): void {
this.transitionTo(OrderStatus.DELIVERED)
this._deliveredAt = new Date()
}
/**
* Business Operation: Cancel order
*/
public cancel(): void {
if (this._status.isDelivered()) {
throw new Error("Cannot cancel delivered order")
}
this.transitionTo(OrderStatus.CANCELLED)
}
/**
* Business Query: Calculate total
*/
public calculateTotal(): Money {
const items = Array.from(this._items.values())
if (items.length === 0) {
return Money.zero("USD")
}
return items.reduce((total, item) => total.add(item.calculateTotal()), Money.zero("USD"))
}
/**
* Business Query: Check if order can be modified
*/
public canModify(): boolean {
return this._status.isPending()
}
/**
* Getters
*/
public get orderId(): OrderId {
return this._orderId
}
public get userId(): UserId {
return this._userId
}
public get items(): readonly OrderItem[] {
return Array.from(this._items.values())
}
public get status(): OrderStatus {
return this._status
}
public get createdAt(): Date {
return this._createdAt
}
public get confirmedAt(): Date | undefined {
return this._confirmedAt
}
public get deliveredAt(): Date | undefined {
return this._deliveredAt
}
/**
* Private helpers
*/
private ensureCanModify(): void {
if (!this.canModify()) {
throw new Error(`Cannot modify order in ${this._status.value} status`)
}
}
private transitionTo(newStatus: OrderStatus): void {
if (!this._status.canTransitionTo(newStatus)) {
throw new Error(
`Invalid status transition: ${this._status.value} -> ${newStatus.value}`,
)
}
this._status = newStatus
this.touch()
}
/**
* Invariant validation
*/
private validateInvariants(): void {
if (!this._status.isPending() && this._items.size < Order.MIN_ITEMS) {
throw new Error(`Order must have at least ${Order.MIN_ITEMS} item(s)`)
}
}
}

View File

@@ -0,0 +1,251 @@
import { BaseEntity } from "../../../../src/domain/entities/BaseEntity"
import { Email } from "../value-objects/Email"
import { UserId } from "../value-objects/UserId"
import { UserCreatedEvent } from "../events/UserCreatedEvent"
/**
* User Aggregate Root
*
* DDD Patterns:
* - Aggregate Root: consistency boundary
* - Rich Domain Model: contains business logic
* - Domain Events: publishes UserCreatedEvent
*
* SOLID Principles:
* - SRP: manages user identity and state
* - OCP: extensible through events
* - DIP: depends on abstractions (Email, UserId)
*
* Business Rules (Invariants):
* - Email must be unique (enforced by repository)
* - User must have valid email
* - Blocked users cannot be activated directly
* - Only active users can be blocked
*/
export class User extends BaseEntity {
private readonly _userId: UserId
private readonly _email: Email
private readonly _firstName: string
private readonly _lastName: string
private _isActive: boolean
private _isBlocked: boolean
private readonly _registeredAt: Date
private _lastLoginAt?: Date
private constructor(
userId: UserId,
email: Email,
firstName: string,
lastName: string,
isActive: boolean,
isBlocked: boolean,
registeredAt: Date,
lastLoginAt?: Date,
) {
super(userId.value)
this._userId = userId
this._email = email
this._firstName = firstName
this._lastName = lastName
this._isActive = isActive
this._isBlocked = isBlocked
this._registeredAt = registeredAt
this._lastLoginAt = lastLoginAt
this.validateInvariants()
}
/**
* Factory method: Create new user (business operation)
*
* DDD: Named constructor that represents business intent
* Clean Code: Intention-revealing method name
*/
public static create(email: Email, firstName: string, lastName: string): User {
const userId = UserId.create()
const now = new Date()
const user = new User(userId, email, firstName, lastName, true, false, now)
user.addDomainEvent(
new UserCreatedEvent({
userId: userId.value,
email: email.value,
registeredAt: now,
}),
)
return user
}
/**
* Factory method: Reconstitute from persistence
*
* DDD: Separate creation from reconstitution
* No events raised - already happened
*/
public static reconstitute(
userId: UserId,
email: Email,
firstName: string,
lastName: string,
isActive: boolean,
isBlocked: boolean,
registeredAt: Date,
lastLoginAt?: Date,
): User {
return new User(
userId,
email,
firstName,
lastName,
isActive,
isBlocked,
registeredAt,
lastLoginAt,
)
}
/**
* Business Operation: Activate user
*
* DDD: Business logic in domain
* SOLID SRP: User manages its own state
*/
public activate(): void {
if (this._isBlocked) {
throw new Error("Cannot activate blocked user. Unblock first.")
}
if (this._isActive) {
return
}
this._isActive = true
this.touch()
}
/**
* Business Operation: Deactivate user
*/
public deactivate(): void {
if (!this._isActive) {
return
}
this._isActive = false
this.touch()
}
/**
* Business Operation: Block user
*
* Business Rule: Only active users can be blocked
*/
public block(reason: string): void {
if (!this._isActive) {
throw new Error("Cannot block inactive user")
}
if (this._isBlocked) {
return
}
this._isBlocked = true
this._isActive = false
this.touch()
}
/**
* Business Operation: Unblock user
*/
public unblock(): void {
if (!this._isBlocked) {
return
}
this._isBlocked = false
this.touch()
}
/**
* Business Operation: Record login
*/
public recordLogin(): void {
if (!this._isActive) {
throw new Error("Inactive user cannot login")
}
if (this._isBlocked) {
throw new Error("Blocked user cannot login")
}
this._lastLoginAt = new Date()
this.touch()
}
/**
* Business Query: Check if user can login
*/
public canLogin(): boolean {
return this._isActive && !this._isBlocked
}
/**
* Getters: Read-only access to state
*/
public get userId(): UserId {
return this._userId
}
public get email(): Email {
return this._email
}
public get firstName(): string {
return this._firstName
}
public get lastName(): string {
return this._lastName
}
public get fullName(): string {
return `${this._firstName} ${this._lastName}`
}
public get isActive(): boolean {
return this._isActive
}
public get isBlocked(): boolean {
return this._isBlocked
}
public get registeredAt(): Date {
return this._registeredAt
}
public get lastLoginAt(): Date | undefined {
return this._lastLoginAt
}
/**
* Invariant validation
*
* DDD: Enforce business rules
*/
private validateInvariants(): void {
if (!this._firstName?.trim()) {
throw new Error("First name is required")
}
if (!this._lastName?.trim()) {
throw new Error("Last name is required")
}
if (this._isBlocked && this._isActive) {
throw new Error("Blocked user cannot be active")
}
}
}