app-architecture

Create apps following contract-port architecture with composition roots. Use when creating new apps in apps/, scaffolding CLI tools, setting up dependency injection, or when the user asks about app structure, entrypoints, or platform-agnostic design.

16 stars

Best use case

app-architecture is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Create apps following contract-port architecture with composition roots. Use when creating new apps in apps/, scaffolding CLI tools, setting up dependency injection, or when the user asks about app structure, entrypoints, or platform-agnostic design.

Teams using app-architecture should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/app-architecture/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/development/app-architecture/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/app-architecture/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How app-architecture Compares

Feature / Agentapp-architectureStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Create apps following contract-port architecture with composition roots. Use when creating new apps in apps/, scaffolding CLI tools, setting up dependency injection, or when the user asks about app structure, entrypoints, or platform-agnostic design.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# App Architecture

Apps mirror the package architecture - they should be platform-agnostic outside of the entrypoint where ports are assembled with platform-specific globals.

## Core Principle

**Assemble all ports at the top level (composition root), then feed them down to individual modules.**

```
┌─────────────────────────────────────────────────────────────┐
│                     Entry Point (cli.ts)                    │
│                  Parse args, call composition root          │
└─────────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                    Composition Root                         │
│                    (composition.ts)                         │
│         Inject platform globals → Create ports → Return     │
└─────────────────────────────────────────────────────────────┘
                           │
         ┌─────────────────┼─────────────────┐
         ▼                 ▼                 ▼
  ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
  │   Module A  │   │   Module B  │   │   Module C  │
  │ (no globals)│   │ (no globals)│   │ (no globals)│
  └─────────────┘   └─────────────┘   └─────────────┘
```

## File Structure

```
apps/{app-name}/
├── src/
│   ├── cli.ts              # Entry point (thin) - only parses args, calls composition
│   ├── composition.ts      # Composition root - assembles all ports
│   ├── types.ts            # App-specific types
│   ├── errors.ts           # Error classes (extend ConveauxError)
│   └── {domain}.ts         # Domain modules - platform-agnostic
├── CLAUDE.md               # Inline instructions for this app
├── README.md               # User documentation
├── package.json
└── tsconfig.json
```

## Composition Root Pattern

The composition root is the ONLY place where platform globals appear:

```typescript
// composition.ts
import type { Env } from '@scope/contract-env';
import type { Logger } from '@scope/contract-logger';
import type { WallClock } from '@scope/contract-wall-clock';
import type { EphemeralScheduler } from '@scope/contract-ephemeral-scheduler';

import { createEnv, createShellEnvSource, createStaticEnvSource } from '@scope/port-env';
import { createEphemeralScheduler } from '@scope/port-ephemeral-scheduler';
import { createLogger, createJsonFormatter, createPrettyFormatter } from '@scope/port-logger';
import { createOutChannel } from '@scope/port-outchannel';
import { createWallClock } from '@scope/port-wall-clock';

// Define what deps the app needs
export interface RuntimeDeps {
  readonly logger: Logger;
  readonly clock: WallClock;
  readonly env: Env;
  readonly scheduler: EphemeralScheduler;
}

export interface RuntimeOptions {
  readonly json?: boolean;
  readonly verbose?: boolean;
}

// Factory that creates all deps - platform globals only appear HERE
export function createRuntimeDeps(options: RuntimeOptions = {}): RuntimeDeps {
  // Inject platform globals into ports
  const clock = createWallClock({ Date });

  const scheduler = createEphemeralScheduler({
    setTimeout: globalThis.setTimeout,
    clearTimeout: globalThis.clearTimeout,
    setInterval: globalThis.setInterval,
    clearInterval: globalThis.clearInterval,
  });

  const logChannel = createOutChannel(process.stderr);

  const formatter = options.json
    ? createJsonFormatter()
    : createPrettyFormatter({ colors: process.stderr.isTTY ?? false });

  const logger = createLogger({
    Date,
    channel: logChannel,
    clock,
    options: { formatter, minLevel: options.verbose ? 'debug' : 'info' },
  });

  const env = createEnv({
    sources: [
      createShellEnvSource(
        { getEnv: (name) => process.env[name] },
        { name: 'shell', priority: 100 }
      ),
      createStaticEnvSource(
        { DEFAULT_TIMEOUT: '30' },
        { name: 'defaults', priority: 0 }
      ),
    ],
  });

  return { logger, clock, env, scheduler };
}
```

## Entry Point Pattern

The entry point should be thin - just parse args and call composition:

```typescript
// cli.ts
#!/usr/bin/env node

import type { Logger } from '@scope/contract-logger';
import { Command } from 'commander';
import { createRuntimeDeps } from './composition.js';
import { runApp } from './app.js';
import { createClient } from './client.js';

const program = new Command();

program
  .name('my-app')
  .action(async (options: CliOptions) => {
    // 1. Create all deps at the top level
    const deps = createRuntimeDeps({
      json: options.json,
      verbose: options.verbose,
    });
    const { logger, clock, env, scheduler } = deps;

    // 2. Create app-specific services, passing deps down
    const client = createClient({ logger, clock });

    // 3. Run the app, passing deps down
    const result = await runApp({ logger, clock, client, scheduler }, config);

    console.log(JSON.stringify(result, null, 2));
    process.exit(EXIT_CODES[result.status]);
  });

program.parse();
```

## Domain Module Pattern

Domain modules receive deps via injection - they never use globals:

```typescript
// client.ts
import type { Logger } from '@scope/contract-logger';
import type { WallClock } from '@scope/contract-wall-clock';

// Define what deps THIS module needs (subset of RuntimeDeps)
export interface ClientDeps {
  readonly logger: Logger;
  readonly clock: WallClock;
}

// Interface for what this module provides
export interface Client {
  fetch(url: string): Promise<Response>;
}

// Factory receives deps, returns implementation
export function createClient(deps: ClientDeps): Client {
  const { logger, clock } = deps;

  return {
    async fetch(url: string) {
      const startTime = clock.nowMs();  // Use injected clock, not Date.now()
      logger.debug('Fetching', { url });

      // ... implementation

      const durationMs = clock.nowMs() - startTime;
      logger.debug('Fetched', { url, durationMs });
      return response;
    }
  };
}
```

## Checklist for Creating Apps

### Composition Root
- [ ] Single `composition.ts` file that creates all runtime deps
- [ ] Platform globals (Date, setTimeout, process.env) only appear here
- [ ] Exports `RuntimeDeps` interface and `createRuntimeDeps` factory
- [ ] Options (json, verbose) are separate from deps

### Entry Point
- [ ] Thin `cli.ts` - only arg parsing and wiring
- [ ] Calls `createRuntimeDeps()` once at the top
- [ ] Passes deps down to all modules
- [ ] Handles errors with proper exit codes

### Domain Modules
- [ ] Each module defines its own `*Deps` interface
- [ ] Deps interface only includes contracts the module needs
- [ ] No direct use of globals (Date.now, console, process.env)
- [ ] Factory pattern: `createX(deps): X`

### Types
- [ ] `types.ts` for shared app-specific types
- [ ] Config types separate from deps types
- [ ] Exit codes as const object

### Errors
- [ ] `errors.ts` with classes extending `UserError` or `RetryableError`
- [ ] Actionable error messages with guidance
- [ ] Use `getExitCode()` for proper exit codes

## Common Deps by Use Case

| Need | Contract | Port |
|------|----------|------|
| Current time | `contract-wall-clock` | `port-wall-clock` |
| Logging | `contract-logger` | `port-logger` |
| Environment vars | `contract-env` | `port-env` |
| Delays/timers | `contract-ephemeral-scheduler` | `port-ephemeral-scheduler` |
| Writing output | `contract-outchannel` | `port-outchannel` |
| Error handling | `contract-control-flow` | `port-control-flow` |

## Anti-Patterns

| Anti-Pattern | Problem | Fix |
|--------------|---------|-----|
| `Date.now()` in module | Hidden platform dependency | Inject `WallClock` |
| `setTimeout()` in module | Untestable timing | Inject `EphemeralScheduler` |
| `process.env.X` in module | Hidden env dependency | Inject `Env` |
| `console.log()` in module | Hidden output dependency | Inject `Logger` |
| Deps created inside module | Can't test with mocks | Pass deps from composition root |
| Module imports port directly | Bypasses DI | Import contract types only |

Related Skills

applying-fsd-architecture

16
from diegosouzapw/awesome-omni-skill

Feature-Sliced Design(FSD) 아키텍처를 적용한 프론트엔드 프로젝트 개발 지원. FSD 레이어, 슬라이스, 세그먼트 구조 설계, 의존성 규칙 적용, 마이그레이션 시 사용.

api-architecture

16
from diegosouzapw/awesome-omni-skill

API versioning, security, authentication, rate limiting, monitoring, error handling, and documentation strategies for production APIs. Use when planning API infrastructure, implementing security concerns, or designing monitoring strategies.

android-architecture

16
from diegosouzapw/awesome-omni-skill

Use when implementing MVVM, clean architecture, dependency injection with Hilt, or structuring Android app layers.

analyzer-architecture-review

16
from diegosouzapw/awesome-omni-skill

analyzerアプリケーションのアーキテクチャレビュー。Port&Adapterアーキテクチャ(ヘキサゴナルアーキテクチャ)のルールに従っているかをチェックします。新しいPort/Adapter/Usecase/Model追加時、PRレビュー時、またはアーキテクチャ違反の検出が必要な時に使用します。Port層の関数型定義、依存関係の方向、New*関数パターン、レイヤー分離などを検証します。

analyze-project-architecture

16
from diegosouzapw/awesome-omni-skill

LLM-based architectural analysis that transforms raw project data into meaningful structure

analyze-architecture

16
from diegosouzapw/awesome-omni-skill

Comprehensive brownfield architecture analysis for existing codebases. Discovers structure, identifies patterns, assesses quality, calculates production readiness, and provides actionable recommendations. Use when analyzing existing codebases to understand architecture, assess quality, or prepare for modernization.

aidb-architecture

16
from diegosouzapw/awesome-omni-skill

Comprehensive architectural reference for AIDB core and MCP integration. Covers 6-layer architecture (MCP, Service, Session, Adapter, DAP Client, Protocol), component organization, data flow patterns, and design decisions. Use when explaining overall system design or understanding how layers interact.

agentic_architecture

16
from diegosouzapw/awesome-omni-skill

Enforces high-level architectural thinking, separation of concerns, and scalability checks before coding.

agentic-jumpstart-architecture

16
from diegosouzapw/awesome-omni-skill

Architecture guidelines for Jarvy CLI - codebase structure, tool implementation patterns, registry system, platform-specific code organization, and module conventions.

Advanced Clean Hexagonal Architecture

16
from diegosouzapw/awesome-omni-skill

Apply Clean Architecture and Hexagonal (Ports & Adapters) patterns for domain isolation and testability. Use when designing system boundaries, creating ports/adapters, or structuring domain-driven applications.

ADR (Architecture Decision Record)

16
from diegosouzapw/awesome-omni-skill

Create and maintain Architecture Decision Records following a consistent template

acc-architecture-doc-template

16
from diegosouzapw/awesome-omni-skill

Generates ARCHITECTURE.md files for PHP projects. Creates layer documentation, component descriptions, and architectural diagrams.