Repository Structure Anti-Patterns
Purpose: Use this catalog when auditing repository structure problems, assigning severity, and proposing concrete remediation steps.
Contents
- Anti-pattern catalog
- Severity guide
- Audit report format
Detection rules, severity levels, and remediation strategies.
Anti-Pattern Catalog
AP-001: God Directory
Severity: High Detection: A single directory contains 50+ files at one level.
# BAD: Everything dumped in src/
src/
├── auth.ts
├── authController.ts
├── authService.ts
├── user.ts
├── userController.ts
├── userService.ts
├── payment.ts
├── ... (50+ more files)Impact:
- Difficult to navigate and find files
- No logical grouping of related code
- Merge conflicts increase with team size
Fix:
# GOOD: Feature-based modules
src/
├── features/
│ ├── auth/
│ │ ├── auth.controller.ts
│ │ ├── auth.service.ts
│ │ └── index.ts
│ ├── user/
│ └── payment/
└── shared/Audit command:
# Count files per directory (flag directories with 50+ files)
find src -maxdepth 1 -type f | wc -lAP-002: Scattered Tests
Severity: High
Detection: Test files located outside tests/ directory without co-location convention.
# BAD: Tests scattered across project
src/auth/auth.test.ts
src/user/__tests__/user.test.ts
tests/payment.test.ts
test/integration.test.ts
spec/e2e.test.tsImpact:
- Inconsistent test discovery
- CI configuration complexity
- Hard to measure coverage by module
Fix (Option A: Centralized):
tests/
├── unit/
│ ├── auth/
│ └── user/
├── integration/
└── e2e/Fix (Option B: Co-located — Go/Rust convention):
src/auth/
├── auth.service.ts
├── auth.service.test.ts # Co-locatedRule: Choose ONE pattern and apply consistently. Do not mix.
AP-003: Config Soup
Severity: Medium Detection: 10+ configuration files at project root.
# BAD: Root cluttered with configs
/
├── .eslintrc.js
├── .eslintrc.json # Duplicate!
├── .prettierrc
├── .prettierrc.js # Duplicate!
├── tsconfig.json
├── tsconfig.build.json
├── tsconfig.test.json
├── jest.config.ts
├── vitest.config.ts # Conflict with jest!
├── .babelrc
├── webpack.config.js
├── postcss.config.js
├── tailwind.config.js
...Impact:
- Visual noise in project root
- Duplicate/conflicting configurations
- Hard to understand which configs are active
Fix:
# GOOD: Consolidate where possible
/
├── config/ # Non-standard configs
│ ├── webpack.config.js
│ └── postcss.config.js
├── eslint.config.js # Flat config (one file)
├── tsconfig.json # Must be at root
├── vitest.config.ts # Choose ONE test framework
└── tailwind.config.js # Framework requires rootStrategies:
- Use flat configs (ESLint flat config, single tsconfig with
extends) - Move non-root-required configs to
config/ - Eliminate duplicates (pick one format per tool)
- Remove unused configs from abandoned tools
AP-004: Script Chaos
Severity: Medium Detection: Shell/helper scripts scattered at project root or in random locations.
# BAD: Scripts everywhere
/
├── setup.sh
├── deploy.sh
├── seed-db.sh
├── run-tests.sh
├── fix-permissions.sh
├── migrate.sh
├── utils/
│ └── backup.sh
├── helpers/
│ └── generate-cert.shImpact:
- No discoverability for available scripts
- Unclear which scripts are safe to run
- No categorization by purpose
Fix:
scripts/
├── setup.sh # Environment setup
├── seed.sh # Database seeding
├── deploy.sh # Deployment
├── migrate.sh # Database migration
└── README.md # Script documentationAP-005: Doc Desert
Severity: High
Detection: No docs/ directory, or docs/ exists but is empty/contains only README.
# BAD: No documentation structure
/
├── src/
├── README.md # Only documentation
└── (no docs/)Impact:
- No requirements traceability
- No design decisions recorded
- Onboarding difficulty
- Knowledge locked in individuals
Fix:
docs/
├── prd/ # Requirements
├── specs/ # Specifications
├── design/ # Design documents
├── checklists/ # Implementation/review checklists
├── test-specs/ # Test specifications
├── adr/ # Architecture decisions
├── guides/ # Developer guides
└── api/ # API documentationAP-006: Orphaned Docs
Severity: Medium
Detection: docs/ contains unstructured, flat files without subdirectories.
# BAD: Flat docs dump
docs/
├── api-design.md
├── auth-requirements.md
├── database-schema.md
├── deployment-notes.md
├── meeting-notes-2024-01.md # Not technical documentation
├── old-design.md # Stale document
├── README.md
├── setup-guide.md
├── todo.md # Not documentation
└── v2-migration-plan.mdImpact:
- No categorization, hard to find documents
- Stale documents mixed with current
- Non-documentation files pollute the directory
Fix:
- Categorize into subdirectories (prd/, design/, guides/, etc.)
- Remove non-documentation files (meeting notes, todos)
- Archive stale documents or delete
- Apply naming conventions (PRD-, HLD-, etc.)
AP-007: Missing Specs
Severity: High
Detection: docs/prd/ or docs/design/ is empty or absent in a project with active development.
# BAD: Code without specifications
docs/
├── guides/
│ └── getting-started.md
└── api/
└── openapi.yaml
# No prd/, no design/, no checklists/Impact:
- No formal requirements → scope creep
- No design documents → inconsistent architecture
- No checklists → quality gaps
Fix:
- Create PRD for each feature before implementation
- Create HLD for system-level changes
- Create LLD for complex features
- Use implementation checklists for tracking
AP-008: Flat Hell
Severity: Medium
Detection: All source files at single level, no subdirectories in src/.
# BAD: Everything flat
src/
├── app.ts
├── authController.ts
├── authMiddleware.ts
├── authService.ts
├── database.ts
├── logger.ts
├── paymentController.ts
├── paymentService.ts
├── userController.ts
├── userModel.ts
├── userService.ts
└── utils.tsImpact:
- No module boundaries
- Circular dependency risk
- Cannot scale beyond ~20 files
Fix: Group by feature or layer (feature-based grouping preferred for most projects).
AP-009: Nested Abyss
Severity: Medium Detection: Directory nesting deeper than 6 levels from project root.
# BAD: Too deep
src/modules/core/services/auth/providers/oauth/google/handlers/callback/
└── handler.ts # 10 levels deepImpact:
- Long import paths
- Cognitive overhead navigating
- Often indicates over-engineering
Fix:
- Maximum 4-5 levels from project root
- Flatten unnecessarily deep structures
- Use path aliases to simplify imports
AP-010: Duplicate Structures
Severity: Low Detection: Multiple directories serving the same purpose.
# BAD: Duplicated purpose
/
├── lib/ # Shared code?
├── shared/ # Shared code?
├── utils/ # Shared code?
├── helpers/ # Shared code?
├── common/ # Shared code?
├── tests/ # Tests?
├── test/ # Tests?
├── __tests__/ # Tests?
└── spec/ # Tests?Fix: Pick ONE name per purpose and consolidate.
Monorepo-Specific Anti-Patterns
AP-011: Circular Package Dependencies
Severity: Critical Detection: Package A depends on Package B, and Package B depends on Package A (directly or transitively).
# BAD: Circular dependency between packages
packages/
├── auth/
│ └── package.json # depends on "user"
└── user/
└── package.json # depends on "auth"Impact:
- Build order becomes indeterminate (build errors, non-deterministic behavior)
- Package boundaries collapse, defeating the purpose of a monorepo
- Test independence is compromised
Fix:
# GOOD: Break cycle with shared package
packages/
├── auth/ # depends on "shared"
├── user/ # depends on "shared"
└── shared/ # common types/interfaces
└── src/
└── types.ts # AuthUser, UserAuth interfacesAudit command:
# Turborepo
npx turbo run build --dry-run --graph # Output dependency graph in DOT format
# Nx
npx nx graph # Visualize dependencies in browser
# Go
go mod graph | grep -E 'cycle'
# Gradle
./gradlew dependencies --configuration compileClasspathAP-012: Package Boundary Violation
Severity: High Detection: A package directly references another package's internal files (bypasses public API).
# BAD: Direct import of internal file
// apps/web/src/page.tsx
import { validateEmail } from '../../packages/auth/src/internal/validators';
// ^^^^^^^^ internal path
// BAD: Bypassing internal/ in Go
// services/api/handler.go
import "example.com/services/worker/internal/queue" // Compiler will reject thisImpact:
- Internal refactoring of a package affects external consumers
- Implicit coupling increases, making independent deployment difficult
- Versioning becomes meaningless
Fix:
# GOOD: Access through public API
// packages/auth/index.ts (barrel export)
export { validateEmail } from './src/validators'; // Explicitly public API
// apps/web/src/page.tsx
import { validateEmail } from '@monorepo/auth'; // Reference by package nameEnforcement:
// Nx: module boundary rules
// .eslintrc.json
{
"rules": {
"@nx/enforce-module-boundaries": ["error", {
"depConstraints": [
{ "sourceTag": "scope:app", "onlyDependOnLibsWithTags": ["scope:shared"] }
]
}]
}
}AP-013: Shared Config Drift
Severity: Medium Detection: Config files are inconsistent across packages within the same monorepo.
# BAD: Inconsistent config per package
packages/
├── auth/
│ ├── tsconfig.json # target: ES2020, strict: true
│ └── .eslintrc.js # extends: recommended
├── user/
│ ├── tsconfig.json # target: ES2022, strict: false ← Mismatch!
│ └── .eslintrc.json # extends: standard ← Mismatch!
└── shared/
├── tsconfig.json # target: ESNext ← Yet another!
└── (no eslint config) ← Missing!Impact:
- Different compile targets across packages lead to compatibility issues
- Inconsistent lint rules result in uneven code quality
- Unclear which config is "correct" when creating new packages
Fix:
# GOOD: Shared base config + package-specific overrides
packages/
├── config/ # Shared config package
│ ├── tsconfig.base.json
│ ├── eslint.base.js
│ └── package.json
├── auth/
│ └── tsconfig.json # { "extends": "@monorepo/config/tsconfig.base.json" }
└── user/
└── tsconfig.json # { "extends": "@monorepo/config/tsconfig.base.json" }Audit command:
# Compare tsconfig targets across packages
find . -name "tsconfig.json" -not -path "*/node_modules/*" \
-exec grep -l "target" {} \; | xargs grep "target"AP-014: Root Pollution
Severity: Medium Detection: Excessive logic, source code, or scripts placed at the monorepo root.
# BAD: Business logic at root
/
├── apps/
├── packages/
├── src/ # ← src/ at root is unnecessary
│ └── shared-utils.ts
├── scripts/
│ ├── deploy-auth.sh
│ ├── deploy-user.sh
│ ├── seed-all.sh
│ ├── migrate-auth.sh
│ ├── migrate-user.sh # ← Package-specific scripts at root
│ └── ... (20+ scripts)
├── utils.ts # ← Utility at root
└── helpers.js # ← Helper at rootImpact:
- Root becomes a dumping ground between packages
- Package independence is compromised
- Build scope optimization becomes difficult
Fix:
# GOOD: Root handles orchestration only
/
├── apps/
├── packages/
├── scripts/ # Monorepo-wide scripts only
│ ├── setup.sh
│ └── ci-check-all.sh
├── turbo.json
└── package.json # Workspace definition onlyAP-015: Orphan Package
Severity: Low Detection: A package that is not depended on by any other package and is not a deployment target.
# BAD: Packages nobody uses
packages/
├── auth/ # used by: apps/web, apps/api
├── user/ # used by: apps/web, apps/api
├── legacy-utils/ # used by: (nobody) ← Orphan!
└── experiment-v2/ # used by: (nobody) ← Orphan!Impact:
- Increased CI/CD build time
- Dependency update cost (unmaintained vulnerabilities)
- Confusion for new team members
Fix:
- Check dependency graph and identify unused packages
- Delete or archive if truly unnecessary
- If experimental, make status explicit (
[EXPERIMENTAL]tag inREADME.md)
Audit command:
# Turborepo: Check for isolated nodes in dependency graph
npx turbo run build --dry-run --graph 2>&1 | grep -v " -> "
# Nx
npx nx graph --affected # Check packages outside affected scopeAP-016: Implicit Dependency
Severity: High Detection: Dependencies used but not declared in package manifest (package.json, go.mod, etc.).
# BAD: Used without declaration in package.json
// packages/auth/src/service.ts
import { hash } from 'bcrypt'; // bcrypt not declared in auth/package.json
// Accidentally resolved from root node_modules
// BAD: Excessive use of replace directives in Go
// go.mod
replace example.com/pkg => ../pkg // Should be managed via workspaceImpact:
- Build/test fails when package is built in isolation
- Fragile dependency resolution relying on hoisting
- Independent publishing of package is impossible
Fix:
# Explicitly declare dependencies in each package
cd packages/auth && pnpm add bcrypt
# Detect via strict mode
# .npmrc
strict-peer-dependencies=true
# pnpm: .npmrc
shamefully-hoist=false # Disable hoisting to detect issuesEnforcement:
# syncpack: Check dependency version consistency across packages
npx syncpack list-mismatches
# depcheck: Detect undeclared dependencies
npx depcheck packages/authSeverity Guide
| Severity | Description | Action |
|---|---|---|
| High | Actively impedes development | Fix immediately |
| Medium | Causes friction, scales poorly | Fix in next refactor |
| Low | Cosmetic / minor inconsistency | Fix when convenient |
Audit Report Format
## Repository Structure Audit
**Repository:** {name}
**Date:** YYYY-MM-DD
**Overall Health:** {score}/100
### Anti-Patterns Detected
| ID | Pattern | Severity | Location | Status |
|----|---------|----------|----------|--------|
| AP-001 | God Directory | High | src/ (67 files) | 🔴 |
| AP-005 | Doc Desert | High | docs/ missing | 🔴 |
| AP-003 | Config Soup | Medium | root (14 configs) | 🟡 |
### Structure Score Breakdown
...