{ "forbidden": [ { "name": "domain-is-framework-neutral", "severity": "error", "from": { "path": "^src/domain" }, "to": { "path": "^(src/(application|presentation|adapters|bootstrap)|react|react-dom|@tanstack)" } }, { "name": "application-does-not-know-concrete-runtime", "severity": "error", "from": { "path": "^src/application" }, "to": { "path": "^(src/(presentation|adapters|bootstrap)|react|react-dom|@tanstack)" } }, { "name": "presentation-does-not-know-adapters", "severity": "error", "from": { "path": "^src/presentation/(?!adapters/query)" }, "to": { "path": "^(src/(adapters|bootstrap)|@tanstack)" } }, { "name": "page-templates-own-layout-only", "severity": "error", "from": { "path": "^src/presentation/templates" }, "to": { "path": "^(src/(application|adapters|bootstrap)|src/presentation/adapters|@tanstack)" } }, { "name": "icon-vendor-is-facade-only", "severity": "error", "from": { "path": "^src", "pathNot": "^src/presentation/design-system/icons/vendors/lucide\\.tsx$" }, "to": { "path": "^lucide-react$" } }, { "name": "adapters-do-not-know-presentation", "severity": "error", "from": { "path": "^src/adapters" }, "to": { "path": "^src/(presentation|bootstrap)" } }, { "name": "feature-domain-is-framework-neutral", "severity": "error", "from": { "path": "^src/features/[^/]+/domain" }, "to": { "path": "^(src/(application|presentation|adapters|bootstrap)|src/features/[^/]+/(application|adapters|presentation)|react|react-dom|@tanstack)" } }, { "name": "feature-application-does-not-know-runtime", "severity": "error", "from": { "path": "^src/features/[^/]+/application" }, "to": { "path": "^(src/(presentation|adapters|bootstrap)|src/features/[^/]+/(adapters|presentation)|react|react-dom|@tanstack)" } }, { "name": "feature-presentation-does-not-know-outbound-adapters", "severity": "error", "from": { "path": "^src/features/[^/]+/presentation" }, "to": { "path": "^(src/(adapters|bootstrap)|src/features/[^/]+/adapters|@tanstack)" } }, { "name": "feature-adapters-do-not-know-presentation", "severity": "error", "from": { "path": "^src/features/[^/]+/adapters" }, "to": { "path": "^(src/(presentation|bootstrap)|src/features/[^/]+/presentation)" } }, { "name": "concrete-adapters-compose-only-in-bootstrap", "severity": "error", "from": { "path": "^src/(domain|application|presentation|contracts)" }, "to": { "path": "^src/adapters" } }, { "name": "external-contract-package-single-import-path", "comment": "§4.1: a generated service package may only be imported from src/features//contracts/*-contract-contribution.ts", "severity": "error", "from": { "path": "^src", "pathNot": "^src/features/[^/]+/contracts/[^/]+-contract-contribution\\.ts$" }, "to": { "path": "^@org-contracts/" } }, { "name": "presentation-does-not-fetch-directly", "comment": "§9.2 / appendix B: a page or hook never opens a socket, worker or HTTP adapter itself", "severity": "error", "from": { "path": "^src/(presentation|features/[^/]+/presentation)" }, "to": { "path": "^src/adapters/(http|realtime|service-worker|web-worker|storage)" } }, { "name": "generic-worker-has-no-network-or-credentials", "comment": "§16.13 / §21.10: a CPU worker never imports HTTP, realtime or auth", "severity": "error", "from": { "path": "^src/adapters/web-worker" }, "to": { "path": "^src/adapters/(http|realtime|auth|web-push)" } }, { "name": "service-worker-entry-is-not-page-code", "comment": "§17.2.1: the worker realm never imports React, presentation or bootstrap page code", "severity": "error", "from": { "path": "^src/adapters/service-worker" }, "to": { "path": "^(src/(presentation|bootstrap)|react|react-dom|@tanstack)" } }, { "name": "contracts-do-not-know-application", "comment": "§4. `src/contracts` is the lower of the two packages: application reads contracts, never the other way round. Before this rule the shared Result carrier and the compatibility predicate lived in application and were imported back down by contracts, so neither package owned the shared vocabulary and the coupling was invisible to every gate.", "severity": "error", "from": { "path": "^src/contracts" }, "to": { "path": "^src/(application|features)" } }, { "name": "generic-presentation-does-not-compose-the-product", "comment": "§4 / §9. Which features are installed is a product decision that belongs to bootstrap. Generic presentation reads the installed registries directly today; the paths below are the exact set that does so, frozen so the coupling cannot spread while the assembly is lifted into bootstrap.", "severity": "error", "from": { "path": "^src/presentation/", "pathNot": "^src/presentation/(layouts/app-shell\\.tsx|pages/(not-found-page|home-page)\\.tsx|routes/(route-contract|route-codecs|app-router|navigation-policy)\\.(ts|tsx)|i18n/catalog\\.ts|examples/platform-overview-page\\.tsx)$" }, "to": { "path": "^src/features/installed-" } }, { "name": "adapters-do-not-know-other-concrete-adapters", "comment": "docs/architecture/layers.md §4: a concrete adapter never depends on another concrete adapter. Only the adapter kernel is shared — `src/adapters/platform` (clock, abort primitive, capacity guard) and the browser-data result helpers. `query-cache` still reads two collaborator types from `cross-context-invalidation`; that edge is named here rather than left silent, and closes when those types are lifted to a port.", "severity": "error", "from": { "path": "^src/adapters/([^/]+)/" }, "to": { "path": "^src/adapters/([^/]+)/", "pathNot": "^src/adapters/($1/|platform/|browser-file-storage/result\\.ts$|cross-context-invalidation/index\\.ts$)" } }, { "name": "no-circular-dependencies", "severity": "error", "from": {}, "to": { "circular": true } } ], "options": { "doNotFollow": { "path": "node_modules" }, "exclude": { "path": "^(dist|artifacts|tests/fixtures)" }, "enhancedResolveOptions": { "exportsFields": [ "exports" ], "conditionNames": [ "import", "require", "node", "default" ] }, "tsConfig": { "fileName": "tsconfig.app.json" } } }