home / skills / gpolanco / skills-as-context / structuring-projects

structuring-projects skill

/skills/structuring-projects

This skill enforces universal architectural patterns for organizing code, ensuring domain-focused structure and clear public APIs across projects.

npx playbooks add skill gpolanco/skills-as-context --skill structuring-projects

Review the files below or copy the command above to add this skill to your agents.

Files (4)
SKILL.md
12.1 KB
---
name: structuring-projects
description: >
  Enforces universal architectural patterns for organizing code.
  Trigger: When organizing project files, creating features, setting up imports, 
  implementing DDD/Hexagonal/Clean Architecture, or refactoring codebase structure.
  Language-agnostic (applies to Node.js, Next.js, Python, PHP, Go, etc.)
license: Apache-2.0
metadata:
  author: gpolanco
  version: "2.0.0"
  scope: [root]
  auto_invoke: "Organizing project structure"
allowed-tools: Read
---

# āš ļø AUTO-INVOKE DIRECTIVE

**This skill MUST be read BEFORE:**
- Creating any project structure
- Moving files between directories
- Setting up path aliases
- Deciding where to place code
- Implementing DDD or Clean Architecture

**If you are an AI agent performing any of the above, STOP and read this skill first.**

---

## 🚨 CRITICAL: Reference Files are MANDATORY

**This SKILL.md provides PRINCIPLES only. For EXACT structure:**

| Project Type | MANDATORY Reading |
|--------------|-------------------|
| **Backend API/CLI** (Node.js, Python, Go, PHP) | āš ļø [reference/node-cli-patterns.md](reference/node-cli-patterns.md) |
| **Frontend** (Next.js, React) | āš ļø [reference/nextjs-patterns.md](reference/nextjs-patterns.md) |
| **DDD/Hexagonal** | āš ļø [reference/ddd-rules.md](reference/ddd-rules.md) |

**āš ļø DO NOT invent structure from memory. READ the exact reference file for your project type.**

---

## When to Use

- Setting up a new project structure (any language/framework)
- Creating new features or modules
- Refactoring existing codebase organization
- Defining import boundaries and module contracts
- Implementing DDD, Hexagonal, or Clean Architecture

---

## Decision Tree

```
What type of project are you working on?
│
ā”œā”€ 🌐 Frontend ONLY (React, Next.js, Vue)
│  └─ āš ļø STOP → Read reference/nextjs-patterns.md FIRST
│
ā”œā”€ šŸ”§ Backend/CLI ONLY (Node.js, Python, Go, PHP)
│  ā”œā”€ Simple CRUD?
│  │  └─ āš ļø STOP → Read reference/node-cli-patterns.md → "Simple Feature-Based" section
│  │
│  └─ Complex Business Logic (DDD)?
│     └─ āš ļø STOP → Read reference/ddd-rules.md FIRST
│        (DO NOT use the simplified example below - it's incomplete)
│
└─ šŸ“¦ Fullstack Monorepo (Multiple Apps)
   ā”œā”€ For EACH app in apps/, apply the pattern for its type:
   │  ā”œā”€ apps/web/ (Next.js) → Use reference/nextjs-patterns.md
   │  ā”œā”€ apps/api/ (Backend) → Use reference/node-cli-patterns.md
   │  └─ apps/worker/ (CLI)  → Use reference/node-cli-patterns.md
   │
   └─ DO NOT mix patterns within a single app
```

**🚫 NEVER create structure from memory or assumptions. ALWAYS read the specific reference file for each app type.**

**Note on Monorepos:**
- A monorepo contains MULTIPLE independent apps in `apps/` or `packages/`
- Each app maintains its OWN structure according to its type (don't mix patterns)
- Example: `apps/web/` uses Next.js patterns, `apps/api/` uses Node.js backend patterns
- Shared code goes in `packages/@shared/` with its own structure

---

## Universal Principles (APPLIES TO ALL LANGUAGES)

### ALWAYS

- **Single Source Root**: All code in `src/` (or language equivalent: `lib/`, `app/`)
- **Organize by Domain/Feature**: Group by business capability, NOT by technical layer
- **Explicit Module Boundaries**: Each feature/module exports a public API
- **Dependency Direction**: Dependencies point INWARD (domain ← infrastructure ← app)
- **Path Aliases**: Use absolute imports (`@/`, `~/`) over deep relative imports
- **Tests Isolated**: Tests in `/tests` (or `__tests__/`, `test/`) not mixed with src

### NEVER

- **Never organize by file type at root**: `controllers/`, `services/`, `models/` as top-level
- **Never use deep relative imports**: `../../../../utils/helper`
- **Never create circular dependencies**: Module A imports B, B imports A
- **Never create global "utils" dumping ground**: Use feature-specific utils
- **Never bypass module boundaries**: Import internal implementation details directly

### DEFAULTS

- Features communicate via public API exports only
- Shared code lives in `shared/` (or `common/`, `core/`)
- Naming: `kebab-case` for files/folders (except Python: `snake_case`)
- Use single path alias: `@/*` → `src/*`

---

## 🚫 Critical Anti-Patterns

- **DO NOT** use deep relative imports (`../../../../`) → use path aliases (`@/*`).
- **DO NOT** organize by file type at root (e.g., `controllers/`, `models/`) → organize by domain/feature.
- **DO NOT** bypass module boundaries → DO NOT import internals from other features directly; always use the public API (`index.ts`).
- **DO NOT** create a circular dependency between features.
- **DO NOT** create a global "utils" folder as a dumping ground → keep utilities specific to the feature they serve.

---

## Architecture Patterns

### 1. Feature-Based (Default - Simple Projects)

**When to use:**
- Simple CRUD applications
- Small to medium projects
- Clear feature boundaries without complex domain logic

**Universal Structure:**

```
src/
  features/
    <feature-name>/
      api/            # Public interface (exported functions/classes)
      services/       # Business logic
      models/         # Data models
      types/          # Type definitions
      utils/          # Feature-specific helpers
      index.<ext>     # Public API exports (CRITICAL)
  
  shared/             # Cross-feature infrastructure
    logging/
    config/
    errors/
  
  app/                # Entry points (meaning varies by framework)
```

**Technology-Specific Details:**
- **Next.js**: See [reference/nextjs-patterns.md](reference/nextjs-patterns.md)
- **Node.js CLI/APIs**: See [reference/node-cli-patterns.md](reference/node-cli-patterns.md)
- **Python**: (Coming soon)
- **PHP**: (Coming soon)

---

### 2. Domain-Driven Design (Complex Projects)

**When to use:**
- Complex business rules and invariants
- Multiple bounded contexts
- Need for domain events
- Large teams requiring clear boundaries

**āš ļø STOP: DO NOT implement DDD from the simplified example below.**

**šŸ‘‰ MANDATORY: Read [reference/ddd-rules.md](reference/ddd-rules.md) FIRST for:**
- Complete folder structure with `app/` entry points
- Layer dependency rules and import patterns
- Concrete examples for your language/runtime

**Simplified Overview (REFERENCE ONLY - NOT for implementation):**

```
src/
  features/            # OR packages/ in monorepo
    core/              # OR <bounded-context>/
      domain/          # Business logic (PURE - no framework/infra)
        entities/
        value-objects/
        repositories/  # Interfaces ONLY
        services/      # Domain services
      
      application/     # Use cases (orchestration layer)
        services/      # Application services
      
      infrastructure/  # Technical implementations
        db/
          repositories/  # Repository implementations
        http/
        messaging/
  
  app/                 # Entry points (see reference/ddd-rules.md)
```

**Key Principle:** Domain layer has ZERO dependencies on infrastructure or frameworks.

**🚨 This example is incomplete. Missing: entry points, shared/, path aliases, and more. → [reference/ddd-rules.md](reference/ddd-rules.md)**

---

## Module Boundaries & Public APIs

### Explicit Exports (CRITICAL PATTERN)

Every feature MUST have a public API file that exports ONLY what other features need:

**TypeScript/JavaScript:**
```typescript
// features/auth/index.ts
export { AuthService } from "./services/auth-service";
export { useAuth } from "./hooks/use-auth";
export type { User, AuthConfig } from "./types";

// āŒ DO NOT export internal helpers
// export { hashPassword } from "./utils/crypto"; // KEEP PRIVATE
```

**Python:**
```python
# features/auth/__init__.py
from .services.auth_service import AuthService
from .types import User, AuthConfig

__all__ = ["AuthService", "User", "AuthConfig"]
```

**PHP:**
```php
<?php
// features/Auth/index.php
namespace App\Features\Auth;

// Export only public classes
```

### Import Rules (Universal)

```typescript
// āœ… ALWAYS: Import from public API
import { AuthService } from "@/features/auth";

// āŒ NEVER: Import internals directly
import { hashPassword } from "@/features/auth/utils/crypto";
import { validateToken } from "@/features/auth/services/internal";
```

---

## Shared Infrastructure Pattern

`shared/` (or `common/`) contains reusable cross-cutting concerns, NOT business logic:

**What belongs in shared/:**
- āœ… Logging utilities
- āœ… Configuration loaders
- āœ… Generic error classes
- āœ… Date/time utilities
- āœ… Retry logic, circuit breakers
- āœ… UI components (for frontend): Button, Input, Modal

**What does NOT belong in shared/:**
- āŒ Business logic (`calculateTax` → belongs in `features/billing/`)
- āŒ Domain-specific utilities (`validateEmail` → belongs in `features/auth/`)
- āŒ Feature-specific types (`User` → belongs in `features/auth/types`)

```
shared/
ā”œā”€ā”€ logging/          # āœ… Generic logger
ā”œā”€ā”€ config/           # āœ… Config loader
ā”œā”€ā”€ errors/           # āœ… Base error classes
└── time/             # āœ… Clock, date utils

// āŒ WRONG:
shared/utils/calculate-tax.ts      # Move to features/billing/
shared/hooks/use-user-profile.ts   # Move to features/auth/
```

---

## Path Aliases (Language-Specific Config)

### TypeScript (tsconfig.json)

```json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
```

### Python (pyproject.toml)

```toml
[tool.pytest.ini_options]
pythonpath = ["src"]
```

### PHP (composer.json)

```json
{
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  }
}
```

---

## Naming Conventions

| Element | Convention | Example | Notes |
|---------|-----------|---------|-------|
| Files/Folders | `kebab-case` | `user-service.ts`, `auth-module/` | Python: `snake_case` |
| Components/Classes | `PascalCase` | `UserService`, `AuthConfig` | All languages |
| Functions/Variables | `camelCase` | `getUserData()`, `userId` | Python: `snake_case` |
| Constants | `UPPER_SNAKE_CASE` | `MAX_RETRY_COUNT` | All languages |

---

## Anti-Patterns (Universal)

### āŒ God Folders

```
src/utils/          # Everything dumped here
  auth.ts
  billing.ts
  user.ts
```

### āœ… Feature-Specific

```
src/features/
  auth/utils/       # Clear ownership
  billing/utils/    # Isolated
```

---

### āŒ Type Soup

```
src/types.ts        # 500+ lines of unrelated types
```

### āœ… Distributed Types

```
src/features/
  auth/types.ts
  billing/types.ts
```

---

### āŒ Cross-Feature Coupling

```typescript
// Importing internals
import { getUserEmail } from "@/features/auth/utils/user";
```

### āœ… Public API

```typescript
// Using public interface
import { getUserEmail } from "@/features/auth";
```

---

## Migration Strategy (Universal)

### Step 1: Audit

```bash
# Find scattered utilities
find src -name "utils" -type d

# Find deep imports
grep -r "import.*\.\./\.\./\.\." src/
```

### Step 2: Create Structure

```bash
# Generic structure (adjust extensions for your language)
mkdir -p src/features/<feature-name>/{api,services,models,types,utils}
touch src/features/<feature-name>/index.<ext>
```

### Step 3: Move Files

Move files from scattered locations to their respective features.

### Step 4: Update Imports

Replace relative imports with alias imports.

### Step 5: Create Public APIs

Export only public interfaces in `index.<ext>` files.

---

## Commands

```bash
# Create feature structure (TypeScript)
mkdir -p src/features/<feature-name>/{api,services,models,types,utils}
touch src/features/<feature-name>/index.ts

# Create shared infrastructure
mkdir -p src/shared/{logging,config,errors}

# Audit existing structure
find src -name "utils" -type d
grep -r "import.*\.\./\.\./\.\." src/

# Find cross-feature imports (audit)
grep -r "import.*@/features/.*/.*/" src/features/
```

---

## Resources

- **DDD Architecture**: [reference/ddd-rules.md](reference/ddd-rules.md) - Universal DDD patterns
- **Next.js Specifics**: [reference/nextjs-patterns.md](reference/nextjs-patterns.md) - App Router, RSC, components
- **Node.js Specifics**: [reference/node-cli-patterns.md](reference/node-cli-patterns.md) - CLI, APIs, services

Overview

This skill enforces universal architectural patterns for organizing project code across languages and frameworks. It defines mandatory reference reads for each project type and prescribes feature-based, DDD/hexagonal, and shared-infrastructure patterns to ensure clear module boundaries and inward dependency flow.

How this skill works

Before creating or moving files, the skill requires reading the exact reference for your project type to avoid inventing structure from memory. It inspects the project type, enforces a single source root (src/ or equivalent), and validates feature-level public APIs, path aliases, and dependency direction. It provides migration steps, audit commands, and concrete anti-patterns to correct dangerous layouts.

When to use it

  • Setting up a new project structure or monorepo apps
  • Creating new features, modules, or public APIs
  • Refactoring codebase organization or moving files
  • Defining import boundaries, path aliases, or module contracts
  • Implementing DDD, Hexagonal, or Clean Architecture patterns

Best practices

  • Always group code by domain/feature under src/, not by technical layer
  • Expose only a feature's public API via an index file; keep internals private
  • Use a single path alias (e.g. @/* → src/*) to avoid deep relative imports
  • Keep shared/ for cross-cutting infra (logging, config, errors), not business logic
  • Prevent circular dependencies and avoid global utils; keep helpers feature-scoped

Example use cases

  • Scaffold a simple CRUD service using the feature-based layout for quick delivery
  • Audit and migrate a legacy codebase: find deep relative imports and redistribute utils
  • Set up a large DDD-backed backend: read the DDD reference, then create domain/application/infrastructure layers
  • Organize a monorepo: apply the appropriate pattern per app under apps/ and keep shared packages isolated
  • Refactor a frontend Next.js app to follow the frontend reference with feature boundaries

FAQ

Do I always need to read a reference file?

Yes. For each project type you must read the exact reference before creating or reorganizing structure; never invent the layout from memory.

Where should shared utilities live?

Place reusable cross-cutting concerns in shared/ (logging, config, errors). Domain-specific logic and types remain inside their feature.