Architecture
Forge uses Domain-Driven Design (DDD) with strict three-layer separation.
Layer structure
Section titled “Layer structure”src/ main.ts — 14 lines, imports + bootstraps domain/ — Pure types, logic, schemas. No I/O. application/ — Command handlers (orchestration) infrastructure/ — All I/O: filesystem, git, CLI, configDependencies flow inward: infrastructure → application → domain. Never reverse.
Domain layer
Section titled “Domain layer”Pure types, logic, and schemas. No I/O, no framework dependencies. Imports nothing outside itself.
Key modules:
- assistants/ — 5 assistant definitions (dir-per-entry)
- config/ — config types, Zod schemas
- sync/ — resolver, drift detection, conflict resolution, merge, state, report
- transforms/ — rename, frontmatter-rename (self-registering)
Application layer
Section titled “Application layer”Command handlers that orchestrate domain + infrastructure.
- Each command lives in
commands/<name>/withindex.ts(definition) andhandler.ts(logic) commands/shared/— reusable prompt functions (assistant, source, sync, scheduler)- Handlers resolve their own
projectRootviaio.resolveProjectRoot()
Infrastructure layer
Section titled “Infrastructure layer”All I/O: filesystem, git, CLI prompts, config parsing.
- cli/ — parser, renderer, prompts
- config/ — loader, writer, state (sync-state.json)
- fs/ — mutations, reader, rollback, scanner
- source/ — git, local loaders (self-registering)
- scheduler/ — launchd, crontab, schtasks (self-registering)
- logger/ — pino-based file logger
- telemetry/ — local JSONL backend
Key patterns
Section titled “Key patterns”| Pattern | Implementation |
|---|---|
| Self-registering modules | Commands, assistants, transforms, schedulers — mutable registry + side-effect import |
| Dispatch maps | Record<string, Handler> for renderer, transforms, source loaders, conflict resolution |
| Functions, not classes | Plain functions and object literals. Classes only for custom errors |
| Const objects for unions | Define const object, derive union type via typeof OBJ[keyof typeof OBJ] |
| Infrastructure boundary mocking | Tests mock at @infrastructure/*, never at file level |
Import rules
Section titled “Import rules”- Cross-layer: path aliases (
@domain/*,@application/*,@infrastructure/*) - Within a layer: relative imports
- Domain must never import from application or infrastructure
- Infrastructure must never import from application
Adding things
Section titled “Adding things”| What | Where | How |
|---|---|---|
| New assistant | domain/assistants/<name>/ |
One file + registry entry |
| New source type | infrastructure/source/<type>/ |
Three files + barrel entry |
| New command | application/commands/<name>/ |
Two files + barrel entry |
| New transform | domain/transforms/<name>/ |
One file + registry entry |
Zero changes to existing commands or handlers required.