diff --git a/packages/guardian/CHANGELOG.md b/packages/guardian/CHANGELOG.md index 4788b71..5600d79 100644 --- a/packages/guardian/CHANGELOG.md +++ b/packages/guardian/CHANGELOG.md @@ -5,6 +5,16 @@ 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/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.7.3] - 2025-11-25 + +### Fixed + +- 🐛 **False positive: repository importing its own aggregate:** + - Added `isInternalBoundedContextImport()` method to detect internal imports + - Imports like `../aggregates/Entity` from `repositories/Repo` are now allowed + - This correctly allows `ICodeProjectRepository` to import `CodeProject` from the same bounded context + - Cross-aggregate imports (with multiple `../..`) are still detected as violations + ## [0.7.2] - 2025-11-25 ### Fixed diff --git a/packages/guardian/package.json b/packages/guardian/package.json index 5dd8715..63c3ca3 100644 --- a/packages/guardian/package.json +++ b/packages/guardian/package.json @@ -1,6 +1,6 @@ { "name": "@samiyev/guardian", - "version": "0.7.2", + "version": "0.7.3", "description": "Research-backed code quality guardian for AI-assisted development. Detects hardcodes, circular deps, framework leaks, entity exposure, and 8 architecture violations. Enforces Clean Architecture/DDD principles. Works with GitHub Copilot, Cursor, Windsurf, Claude, ChatGPT, Cline, and any AI coding tool.", "keywords": [ "puaros", diff --git a/packages/guardian/src/infrastructure/analyzers/AggregateBoundaryDetector.ts b/packages/guardian/src/infrastructure/analyzers/AggregateBoundaryDetector.ts index 411be9c..48f4a94 100644 --- a/packages/guardian/src/infrastructure/analyzers/AggregateBoundaryDetector.ts +++ b/packages/guardian/src/infrastructure/analyzers/AggregateBoundaryDetector.ts @@ -195,6 +195,11 @@ export class AggregateBoundaryDetector implements IAggregateBoundaryDetector { return false } + // Check if import stays within the same bounded context + if (this.isInternalBoundedContextImport(normalizedPath)) { + return false + } + const targetAggregate = this.extractAggregateFromImport(normalizedPath) if (!targetAggregate || targetAggregate === currentAggregate) { return false @@ -207,6 +212,36 @@ export class AggregateBoundaryDetector implements IAggregateBoundaryDetector { return this.seemsLikeEntityImport(normalizedPath) } + /** + * Checks if the import is internal to the same bounded context + * + * An import like "../aggregates/Entity" from "repositories/Repo" stays within + * the same bounded context (one level up goes to the bounded context root). + * + * An import like "../../other-context/Entity" crosses bounded context boundaries. + */ + private isInternalBoundedContextImport(normalizedPath: string): boolean { + const parts = normalizedPath.split("/") + const dotDotCount = parts.filter((p) => p === "..").length + + /* + * If only one ".." and path goes into aggregates/entities folder, + * it's likely an internal import within the same bounded context + */ + if (dotDotCount === 1) { + const nonDotParts = parts.filter((p) => p !== ".." && p !== ".") + if (nonDotParts.length >= 1) { + const firstFolder = nonDotParts[0] + // Importing from aggregates/entities within same bounded context is allowed + if (this.entityFolderNames.has(firstFolder)) { + return true + } + } + } + + return false + } + /** * Checks if the import path is from an allowed folder (value-objects, events, etc.) */