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.
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
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/app-architecture/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How app-architecture Compares
| Feature / Agent | app-architecture | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/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
Feature-Sliced Design(FSD) 아키텍처를 적용한 프론트엔드 프로젝트 개발 지원. FSD 레이어, 슬라이스, 세그먼트 구조 설계, 의존성 규칙 적용, 마이그레이션 시 사용.
api-architecture
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
Use when implementing MVVM, clean architecture, dependency injection with Hilt, or structuring Android app layers.
analyzer-architecture-review
analyzerアプリケーションのアーキテクチャレビュー。Port&Adapterアーキテクチャ(ヘキサゴナルアーキテクチャ)のルールに従っているかをチェックします。新しいPort/Adapter/Usecase/Model追加時、PRレビュー時、またはアーキテクチャ違反の検出が必要な時に使用します。Port層の関数型定義、依存関係の方向、New*関数パターン、レイヤー分離などを検証します。
analyze-project-architecture
LLM-based architectural analysis that transforms raw project data into meaningful structure
analyze-architecture
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
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
Enforces high-level architectural thinking, separation of concerns, and scalability checks before coding.
agentic-jumpstart-architecture
Architecture guidelines for Jarvy CLI - codebase structure, tool implementation patterns, registry system, platform-specific code organization, and module conventions.
Advanced Clean Hexagonal Architecture
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)
Create and maintain Architecture Decision Records following a consistent template
acc-architecture-doc-template
Generates ARCHITECTURE.md files for PHP projects. Creates layer documentation, component descriptions, and architectural diagrams.