Library Layers
The 192 libraries have 1,529 runtime dependencies and a chain 36 deep. This page defines seven layers, lists the 146 dependencies that break them, and shows the three ways to fix one.
Problem
The graph has no import cycles. It has no layering either. Domain libraries import each other sideways, for example media → requests → chats → user → phone → comms.
| Measure | Today |
|---|---|
| Runtime dependencies between libraries | 1,529 |
| Dependencies a tree would have | 191 |
| Longest dependency chain | 36 libraries |
| Libraries affected by a typical change | 13 |
| Libraries affected by the worst change above the core layer | 100 |
This is a cost in build time, test scope and review risk. It is not a memory cost once servers are merged. See Server Footprint Plan.
Layers
A library may import from any lower layer. Within its own layer it may import only libraries of its own domain.
| Layer | Libraries | Holds |
|---|---|---|
| feature | 26 | Orchestration across domains: Command Center, front desk, website backend |
| domain | 64 | One business area each: scheduling, gbp, website, content, clients |
| platform | 17 | Shared product infrastructure: AI runtime, messaging |
| core | 14 | Identity: entity, auth, user, location |
| integration | 37 | One third-party client each: twilio, stripe, hubspot |
| contracts | 6 | Types, constants, pure functions, port and queue definitions |
| foundation | 28 | config, monitoring, drizzle-orm, bull, llm |
An import that the compiler erases is always allowed. import type and type-only uses do not create a runtime dependency.
Every domain and its libraries
| Layer | Domain | Libraries |
|---|---|---|
| core | identity | app-blocker, app-visitor, app-webview, auth, auth-entity, business, businessprofile, cohorts, employees, entity, ftux, location, user, working-hours |
| platform | ai | agent-memory, agent-provisioning, agents, kb, prompts, suggestions |
| platform | comms | chats, comms, comms-inbox, comms-journey, comms-provider, email, gateway, phone, requests, sales-communication, updates |
| domain | scheduling | booking-playground, coupons, gift-cards, inventory, loyalty, offers, packages, scheduling, services, staff, waitlist, shared-ledger, wallets |
| domain | gbp | gbp-attributes, gbp-completion, gbp-location, gbp-media, gbp-notifications, gbp-reserve-with-google, gbp-reviews, gbp-scraper, gbp-services, review-feedback, review-invites, reviews, website-reviews |
| domain | local-seo | lead-magnet, bucketing, census-data-import, keyword-gen, keywords-competitor, keywords-demand, local-seo, map-grid, rca-automation |
| domain | website | blogs, domain-transfer, homepage, link-scraper, links, website, website-creation, website-services |
| domain | content | campaigns, content, image-generation, image-tagging, media, media-callback, social-media |
| domain | brain | agent, clustering, core, ingestion, orchestrator, question-bank |
| domain | clients | booking-enquiries, clients, contacts |
| domain | scraping | platform-scraper, remote-browser, scrape |
| domain | billing | billing, contracts |
| feature | command-center | host, mcp, orchestrator, runtime, tools, website-editor-agent, discovery-agent-tools, frontdesk-agent-tools, scheduling-agent-tools, support-agent-tools |
| feature | frontdesk | ai-sdr, booking-agent, chatbot, cx, frontdesk-agent, win |
| feature | backoffice | help-center, internal-tools, learning-center, release-publications, reports |
| feature | platforms | integrated-platforms, platform-migration |
| feature | other | agent-eval, business-profile, website-backend |
The assignment lives in tools/lib-layers/layers.json. It is a proposal. Each row needs its owner's agreement.
Violations
146 of 1,529 runtime dependencies break the rules. That is 10%.
| Rule | Edges | Meaning |
|---|---|---|
| cross-domain | 68 | A core, platform or domain library imports a sibling in another domain |
| upward | 58 | A library imports from a higher layer |
| sideways | 20 | An integration or feature library imports a sibling outside its group |
The largest groups: website → gbp (8), website → content (5), website → scheduling (5), scheduling → website (5), scheduling → clients (5), command-center → frontdesk (5), identity → comms (4).
Fixes
Each violating edge was classified by what crosses it. An edge can need more than one fix.
| Fix | Edges | When it applies |
|---|---|---|
| port | 116 | An injected service is called and its result is used |
| contracts | 50 | Enums, constants, pure functions, DTOs or error classes cross the edge |
| queue | 34 | An injected service is called only for its side effect |
| manual | 12 | The analysis could not resolve how the import is used |
A contracts library alone clears 3 edges. Almost every edge that imports a constant also injects a service. Ports are the main work.
Contracts
Move the symbols to <domain>-contracts. The owning library re-exports them, so its other importers do not change.
Done: scheduling → booking-agent. The recommendation engine is three pure files. They moved to libs/booking-agent-contracts. 17 new unit tests cover them.
Port
A port is an interface and an injection token in a contracts library. The owning library binds the token to its service in a global <Domain>PortsModule. The application root imports that module. The consumer injects the token and stops importing the owner.
Done: gateway → scheduling. The chat gateway read one boolean from SchedulingService.
libs/scheduling-contracts/src/lib/ports/scheduling-status.port.tstsexport const SCHEDULING_STATUS_PORT = Symbol('SchedulingStatusPort');
export interface SchedulingStatusPort {
isSchedulingActive(entityId: string): Promise<boolean>;
}
libs/scheduling/src/lib/scheduling-ports.module.tsts@Global()
@Module({
imports: [SchedulingModule],
providers: [{ provide: SCHEDULING_STATUS_PORT, useExisting: SchedulingService }],
exports: [SCHEDULING_STATUS_PORT],
})
export class SchedulingPortsModule {}
libs/gateway/src/lib/chat.gateway.tsts@Inject(SCHEDULING_STATUS_PORT) private readonly schedulingStatus: SchedulingStatusPort,
A server that mounts a consumer without the ports module fails at boot with a dependency error. That is intended.
Queue
The producer depends on @mononest/bull and a job contract: a queue name and a payload type in the owner's contracts library. The processor lives in the owning library. Not done yet. The six edges that need only a queue are employees → chats, fireflies → cx, fireflies → pipedrive, frontdesk-agent → integrated-platforms, website → gbp-media and website → reviews.
Verification of the two fixes
api-server was rebuilt and booted. All 372 parameterless GET routes return the same status and response shape as before the change. The new specs pass. gateway and the changed scheduling files type-check clean.
Expected effect
| Step | Dependencies | Longest chain | Typical change affects | Worst change affects |
|---|---|---|---|---|
| Today | 1,529 | 36 | 13 | 100 |
| Contracts-only edges fixed | 1,526 | 36 | 13 | 99 |
| Plus queue-only edges | 1,505 | 34 | 13 | 99 |
| All 146 fixed | 1,383 | 15 | 3 | 42 |
The gain arrives late. Most of it needs the ports.
Libraries carried by the servers that import few directly:
| Server | Today | All fixed |
|---|---|---|
| public-server | 114 | 41 |
| leads-server | 84 | 59 |
| internal-server | 101 | 83 |
| auth-server | 44 | 28 |
Work list
The order of work, owners and exits are in Codebase Organization Plan. This section is the procedure for each kind of fix and every edge by name. Waves 1 to 3 are Phase 3 of the plan. Waves 4 and 5 are Phase 4.
Wave 1 — Contracts only
Three edges import only constants, enums or pure functions. For each, create <owner>-contracts, move the symbols, and re-export them from the owning library so its other importers do not change. The pattern is in Contracts.
A symbol qualifies only if the file that declares it imports nothing above the contracts layer. Check that before moving it. Pure functions get unit tests in the new library.
Exit: baseline at 143.
Wave 2 — Queue only
Six edges call another library only for a side effect. For each:
Define the job contract
A queue name and a payload type. Use ids in the payload, never whole records.
Write the processor in the owning library
The processor calls the same service method the producer used to call. It must be idempotent, because a retried job runs twice.
Enqueue from the producer
Replace the injected service with bullService.addJob. Remove the owner's module from the producer's imports.
Register the processor in one application
The owning library's module must be loaded by exactly one process that consumes the queue.
Each conversion changes delivery from inline to eventual. Confirm with the domain owner that the caller does not rely on the effect having happened. website → gbp-media and website → reviews write to Google and should be checked first.
Exit: baseline at 137.
Wave 3 — One-method ports
42 edges inject one service and read one method. Each is the change shown in Port:
Add the port to the owner's contracts library
Name the port for what the consumer asks, not for the service that answers. SchedulingStatusPort, not SchedulingServicePort.
Bind it in the owner's ports module
Create the ports module if the owner has none. Reuse it if it does.
Inject the token in the consumer
Types in the port signature come from the contracts library. If the method returns a domain type, move that type too or return a narrower one.
Import the ports module in every application that mounts the consumer
A missing import fails at boot, not at request time.
Several of these methods write, for example LocationService.createOrUpdateEntityLink and FollowupLifecycleService.createManualFollowup. A port can carry a write. Where the caller does not need the result, prefer a queue.
Several owners appear more than once. Group the work by owner: services and links serve four of these edges each, and win and gbp-location three each, with one ports module per owner.
Exit: baseline at 95.
Wave 4 — Multi-method ports
83 edges across 40 owning libraries read several methods, or mix reads with side effects and constants. Work one owning library at a time:
List what outsiders call
The union of methods read across all violating importers is the library's real public interface.
Group the methods into ports by purpose
A port with twenty methods is the service under another name. Group by what the consumer is trying to do.
Move shared types and constants to contracts
The port signatures need them.
Convert side-effect calls to jobs
Repoint the importers, one pull request each
Order by number of violating importers: media and gbp-location (10 each), then win, entity and auth-entity (7 each), then website, services, links and booking-enquiries (6 each).
Exit: baseline at 12.
Wave 5 — Manual
Twelve edges the analysis could not classify. Three patterns:
- A service is injected but never called through a typed property. Read the file.
- A symbol crosses that is neither portable nor injected, such as a decorator (
Public,Auth) or a helper object (DestinationObj).mcp → authandmcp → agentsare foundation importing upward. Either the symbols move down ormcpmoves up a layer. - An integration library holds domain logic.
frontdesk-agent-tools → winreads 17 methods. That is a split, not a port.
Exit: baseline at 0.
Full work list
Wave 1 edges · contracts only · 3
| Edge | Rule | Symbols to move |
|---|---|---|
waitlist → clients/contacts | cross-domain | ContactTypeEnum |
local-seo/keywords-demand → content | cross-domain | PaginatedDto |
website/website-creation → content | cross-domain | PREPROCESS_ALL_JOB_NAME, PREPROCESSING_COMPLETED_AT_PREF, preprocessAfterCatalogueJobId |
Wave 2 edges · queue only · 6
| Edge | Rule | Call to enqueue |
|---|---|---|
employees → chats | upward | ChatsService.addEmployeeAsChatMember |
fireflies → cx | upward | CxService.generateMeetingMinutes |
website → gbp/gbp-media | cross-domain | GbpMediaEntityService.uploadProfileMedia |
frontdesk-agent → integrated-platforms | sideways | PlatformReachabilityService.probe |
fireflies → pipedrive | sideways | PipedriveService.processPaidOnboardingMeetingAttended |
website → reviews | cross-domain | ReviewsEntityService.updateReviewAttributes |
Wave 3 edges · one-method ports · 42
| Edge | Rule | Port method |
|---|---|---|
gmail → auth-entity | upward | AuthEntityService.getGoogleClient |
dictation → auth | upward | EntityAccessService.isAccessibleFor |
local-seo/local-seo → booking-enquiries | cross-domain | EnquiriesEntityService.getLeadsTrackerStats |
updates → booking-enquiries | upward | EnquiriesEntityService.getL2BMarker |
blogs → brain/ingestion | cross-domain | BrainContextSearchService.search |
content → brain/ingestion | cross-domain | BrainContextSearchService.search |
scheduling → chatbot | upward | ChatbotFollowupService.deactivateAllFollowUpsForClient |
mindbody → clients/clients | upward | ClientsEntityService.getClientById |
scheduling → clients/contacts | cross-domain | ContactsEntityService.createOrGetPhoneNumber |
local-seo/local-seo → content | cross-domain | ContentMultiLocationService.getPublishedCounts |
chargebee → cx | upward | CxService.suggestAndAssignAccountManager |
user → email | upward | EmailService.getByEntityId |
chargebee → employees | upward | EmployeesService.getEmployeeById |
fireflies → employees | upward | EmployeesService.getEmployeeById |
fireflies → entity | upward | EntityService.getLocationData |
mindbody → entity | upward | EntityService.isFeatureEnabled |
location → gbp/gbp-attributes | upward | AttributeEntityService.createBulkAttributes |
fireflies → gbp/gbp-location | upward | GbpLocationCoreService.getLocationFromLocationEntityId |
local-seo/keyword-gen → gbp/gbp-location | cross-domain | GbpLocationCoreService.getLocationFromLocationEntityId |
local-seo/rca-automation → gbp/gbp-location | cross-domain | GbpLocationInsightsService.getLocationFromLocationEntityId |
mindbody → google-maps | sideways | GoogleMapsService.resolveTimezone |
review-feedback → links | cross-domain | LinksService.createLink |
review-invites → links | cross-domain | LinksService.createLink |
scheduling → links | cross-domain | LinksService.createLink |
twilio → links | upward | LinksService.createLink |
meta → location | upward | LocationService.createOrUpdateEntityLink |
twilio → location | upward | LocationService.getLocationByEntityId |
gift-cards → media | cross-domain | MediaService.findOneById |
services → platform-scraper | cross-domain | PlatformScraperService.getBusinessDetails |
chargebee → requests | upward | RequestsService.findOpenCancellationRequest |
billing → reviews | cross-domain | ReviewsEntityService.getStats |
billing → scheduling | cross-domain | BookingService.getBookingCountsByStatus |
booking-enquiries → services | cross-domain | ServicesService.getMedianServicePriceAndCurrencyForEntity |
gbp/gbp-completion → services | cross-domain | ServicesService.getServicesByEntity |
local-seo/keyword-gen → services | cross-domain | ServicesService.getServicesByEntity |
website → services | cross-domain | ServicesService.getCategoriesByEntity |
billing → website | cross-domain | WebsiteService.getWebsiteSubdomainFromEntityId |
booking-enquiries → website | cross-domain | WebsiteService.getWebsiteId |
billing → win | upward | FrontdeskCallHistoryService.getEntityCallHistory |
local-seo/local-seo → win | upward | FrontdeskMultiLocationService.getOverview |
loyalty → win | upward | FollowupLifecycleService.createManualFollowup |
chargebee → zoho | sideways | ZohoService.getDocumentDetails |
Wave 4 edges · multi-method ports, by owning library · 83
| Owning library | Importers | Reads | Side effects | Symbols for contracts |
|---|---|---|---|---|
gbp/gbp-location | booking-enquiries, content, homepage, local-seo/local-seo, offers, website, website/website-creation | 21: GbpLocationCoreService.getCountryCode, GbpLocationCoreService.getLocationFromLocationEntityId, GbpLocationCoreService.getUserEntityId, GbpPostsService.createPost and 17 more | GbpPostsService.deletePostByLocation, GbpLocationCoreService.updateProfileDataFromLocationEntityId, GbpLocationCoreService.updateLocation | CallToActionType, LocalPostTopicType, MediaFormat, SUPPORTED_GBP_POST_CTA_TYPES and 4 more |
auth-entity | acuity, glossgenius, square, tiktok, vagaro | 2: AuthEntityService.fetchAuthEntityByPlatform, AuthEntityService.findOneBy | AuthEntityService.upsert, AuthEntityService.update | AuthStatus, AuthPlatforms |
entity | chargebee, google-search, hubspot, linear, moengage | 19: EntityService.getEntityPreferences, EntityService.getEntityProducts, EntityService.resurrectLocation, EntityService.getLocationData and 15 more | none | FRONTDESK_PRODUCT_ID, SCHEDULING_PRODUCT_ID, TRIAL_GRANTED_PREFERENCE, LinksPlatformType |
media | blogs, gbp/gbp-location, gbp/gbp-media, services, staff | 8: MediaService.create, MediaService.buildImageKitUrlMapByMediaId, MediaService.findAll, MediaService.convertToImageKitUrl and 4 more | none | selectContentMedia, MEDIA_SOURCE_TYPE, MEDIA_ACTOR, GBP_MEDIA_SOURCE_TYPES and 4 more |
booking-enquiries | gateway, homepage, moengage, scheduling | 9: EnquiriesEntityService.countUnmarkedEnquiries, EnquiriesEntityService.getL2BMarker, EnquiriesEntityService.getHomepageBookingPerformanceData, EnquiriesEntityService.getTotalEnquiriesCountByEntityId and 5 more | none | EnquiryStatus |
prompts | fireflies, instantly, sales-communication, sybill | 2: PromptsService.getPrompt, PromptsService.generatePromptString | none | PromptsUseCaseEnum |
website | local-seo/local-seo, offers, packages, scheduling | 6: WebsiteService.getWebsiteId, WebsitePagesRepository.listForWebsite, WebsiteService.createCTA, WebsiteService.websiteUpdatedEvent and 2 more | EditorPublishService.detachOfferFromOffersSection, EditorPublishService.setPopupOffer, EditorPublishService.clearPopupOffer and 2 more | CreateCTADto |
brain/core | agents, blogs, content | 5: EntityScopeService.getUserLocations, EntityScopeService.resolveBrainEntity, EntityScopeService.unionForLocation, MemoriesService.getByIdScoped and 1 more | BrainCallbackClient.proposeMemory, MemoriesService.softDelete | none |
business-profile | discovery-agent-tools, gbp/gbp-completion, support-agent-tools | 4: BusinessProfileService.generateAIDescriptionText, BusinessProfileService.getProfileDetails, BusinessProfileService.getPrimaryCategorySuggestion, BusinessProfileService.updateProfileData | BusinessProfileService.updateProfileData | BUSINESS_PROFILE_UPDATE_TYPES |
clients/clients | review-invites, scheduling, waitlist | 9: ClientsEntityService.getClientsByIds, ClientsEntityService.getClientsByEntityId, ClientsEntityService.getClientsByEmail, ClientsEntityService.getClientsByPhoneNumber and 5 more | ClientsEntityService.addClientConversations | Client, SourceEnum, ChannelEnum, PlatformEnum and 1 more |
reviews | blogs, local-seo/local-seo, website/website-creation | 3: ReviewsEntityService.getReviewsForEntityId, ReviewsEntityService.getStats, ReviewsMetricsService.getReviewMetrics | none | Rating, ReviewPlatform |
win | discovery-agent-tools, scheduling-agent-tools, support-agent-tools | 9: ConversionFollowupService.createManualFollowup, FrontdeskCallHistoryRepository.getCallById, FrontdeskAnalyticsService.getDashboardAnalytics, FrontdeskCallHistoryService.getEntityCallHistory and 5 more | none | CommunicationChannel, ConversationState, FollowUpType |
cx | homepage, linear | 3: CxRepository.getLocationAccountManagerName, CxRepository.getAccountExecutive, CxRepository.getAccountManager | none | none |
links | booking-enquiries, loyalty | 2: LinksService.createLink, LinksService.createOrGetLink | none | BOT_USER_AGENT_PATTERNS |
moengage | hubspot, pipedrive | 2: MoengageService.createEvent, MoengageService.getUser | MoengageService.createUser, MoengageService.createEvent | isMoengageDemoEventEligible, MoengageDemoEventStatus |
phone | hubspot, user | 3: PhoneService.upsert, PhoneService.getByEntityId, PhoneService.create | none | Phone |
services | blogs, website/website-creation | 4: ServicesService.getServiceById, ServicesService.getServicesByEntity, ServicesService.getCategoriesByEntity, ServicesService.batchCreateCategoriesWithServices | ServicesService.deleteAllCategoriesForEntity, StaffServicesService.bulkAssignServices, StaffServicesService.removeAllForLocationMember | CurrencyCodeEnum |
staff | website, website/website-creation | 9: StaffService.updateStaffMember, StaffService.replaceStaffMemberProfileMedia, StaffService.bulkCreateStaffMembers, StaffService.attachStaffMemberMedia and 5 more | StaffService.reorderStaffMembers, StaffAdminRepository.attachProfileMedia, StaffAdminRepository.enableWebsiteVisibility and 2 more | none |
brain/ingestion | agents | 3: BrainContextSearchService.search, BrainTopicResolverService.resolveTopic, IngestionPipelineService.persistAgentMemory | IngestionPipelineService.triggerRecluster | none |
chargebee | linear | 1: ChargebeeService.getSubscriptionStatusesForLocation | ChargebeeService.handleRetentionRiskTicket | none |
chatbot | scheduling-agent-tools | 0 | none | none |
clients/contacts | review-invites | 1: ContactsEntityService.getContacstByClientIds | none | ContactTypeEnum |
content | blogs | 3: ContentQueueService.createItem, ContentQueueService.markItemFailedIfPending, PlannedBlogTargetService.checkLiveness | ContentQueueService.markItemFailedIfPending, ContentQueueService.forcePublishItem, ContentQueueService.linkContentAsPublished and 2 more | pickDefaultBlogDays, resolveBlogPublishDays |
contracts | chargebee | 13: ContractsRepository.upsertBillingState, ContractsRepository.findContractById, ContractsRepository.findContractParty, ContractsRepository.findBillingState and 9 more | ContractsRepository.upsertBillingState | none |
email | employees | 0 | EmailService.create | Email |
frontdesk-agent | local-seo/local-seo | 0 | none | none |
gbp/gbp-services | content | 2: GbpServicesService.getServicesWithFallback, GbpServicesService.getServicesFromSchema | none | none |
google-client | google-user | 0 | none | none |
hubspot | instantly | 9: HubspotService.upsertContact, HubspotService.getHubSpotAssociations, HubspotService.upsertCompany, HubspotService.getContactById and 5 more | HubspotService.createHubSpotAssociation, HubspotService.createHubSpotTask, HubspotService.updateHubSpotTask and 1 more | LEAD_BUCKET_TO_INTEREST_VALUE, buildSdrTaskBodyHtml |
image-generation | lead-magnet | 0 | none | none |
integrated-platforms | win | 37: PlatformDetectionService.getConnectedPlatform, IntegratedPlatformsService.getStaffMemberIdsForService, IntegratedPlatformsService.canBookParty, IntegratedPlatformsService.getStaffEligibleForAllServices and 33 more | IntegratedPlatformsService.setPlatformClientId, PlatformReachabilityService.probe | mergeVariationAttributes, readPlatformServiceId, SlotLookupError, APPOINTMENT_SYNC_PREFERENCE_KEY |
offers | moengage | 2: OffersRepository.getOffersWithChannelsCount, OffersRepository.get | none | none |
pipedrive | hubspot | 1: PipedriveService.searchPipedrivePersons | none | EventType |
platform-scraper | website/website-creation | 4: PlatformScraperService.getBusinessDetails, PlatformScraperService.scrapeServicesOnly, PlatformScraperService.captureWebsiteScreenshot, PlatformScraperService.getWebsiteTheme | none | NOISE_BRAND_COLORS |
requests | linear | 0 | RequestsService.markRequestCompleted, RequestsService.update | RequestStatus |
review-invites | updates | 2: ReviewInviteEntityService.getQrCode, ReviewInviteEntityService.createWrappedReviewLink | none | none |
sales-communication | fireflies | 0 | none | none |
scrape | phorest | 1: PlatformCredentialsRepository.findByEntityAndPlatform | PlatformCredentialsRepository.upsert, PlatformCredentialsRepository.updatePlatformIds, PlatformCredentialsRepository.markVerified and 1 more | none |
twilio | moengage | 2: TwilioService.sendSmsViaMessagingService, TwilioService.validateWebhookSignature | none | none |
website-reviews | website | 0 | none | none |
Wave 5 edges · need a manual look · 12
| Edge | Rule | Why |
|---|---|---|
mcp → agents | upward | unclassified symbols: tool |
mindbody → auth-entity | upward | service is injected but no call was found |
mcp → auth | upward | service is injected but no call was found |
comms → clients/contacts | upward | service is injected but no call was found |
gbp/gbp-completion → content | cross-domain | unclassified symbols: GbpPostEnrichmentUtil |
instantly → employees | upward | service is injected but no call was found |
gmail → google-client | sideways | service is injected but no call was found |
meta → media | upward | unclassified symbols: DestinationObj |
offers → media | cross-domain | unclassified symbols: MediaItemDto |
website → media | cross-domain | unclassified symbols: DestinationObj |
website/website-creation → media | cross-domain | unclassified symbols: DestinationObj |
frontdesk-agent-tools → win | sideways | unclassified symbols: getVoiceAgents |
Port surface
The services other layers call are each library's real public interface. These libraries need the most port methods.
| Library | Violating importers | Methods read | Side-effect methods |
|---|---|---|---|
| media | 10 | 9 | 1 |
| gbp-location | 10 | 21 | 3 |
| win | 7 | 28 | 1 |
| entity | 7 | 20 | 0 |
| auth-entity | 7 | 3 | 2 |
| website | 6 | 7 | 5 |
| services | 6 | 5 | 3 |
| booking-enquiries | 6 | 10 | 0 |
| links | 6 | 2 | 0 |
| content | 5 | 7 | 5 |
| reviews | 5 | 3 | 1 |
win and gbp-location are read through more than 20 methods each. A library read that widely is doing several jobs. They are the first candidates to split.
Ratchet
tools/lib-layers/baseline.json records the 146 violations with the symbols each one imports. The check fails on a new violating edge, on a new symbol added to a recorded edge, and on a fixed edge left in the baseline. The file can only get shorter.
$ pnpm check:layers $ pnpm layers:report $ pnpm layers:plan $ node tools/lib-layers/check.js --update
.github/workflows/lib-layers.yml runs the check on pull requests that touch libs/. It reports the status libs/layers. It blocks merges only after it is added to branch protection.
Method and limits
symbols.js parses every source file with the TypeScript compiler API. It records each cross-library import, the kind of symbol, and whether the import survives compilation.