| name | refactor-node-cli-architecture |
| description | Refactor existing Node.js CLIs toward a layered or vertical-slice, command-driven architecture with thin entrypoints, focused command handlers, application services, pure domain logic, isolated infrastructure adapters, and characterization tests. Use when someone asks to refactor a CLI, separate command parsing from business logic, move logic out of the entrypoint, split work into src/commands src/services src/domain src/infrastructure, use a domain-first CLI or vertical slice layout, or perform a behavior-preserving CLI refactor. Do NOT use for greenfield scaffolding, non-CLI applications, or general infrastructure provisioning. |
| license | CC-BY-4.0 |
| metadata | {"author":"Luiz Massa","version":"1.1.0"} |
Refactor Node CLI Architecture
Refactor large Node.js CLIs toward a layered, command-driven design without changing user-visible behavior unless the current behavior is clearly a bug.
When to Apply
Use this skill when the task is about an existing Node.js CLI and the user wants to:
- separate command parsing from workflow logic
- move business rules out of the entrypoint
- introduce or reinforce
src/commands, src/services, src/domain, or src/infrastructure
- move multi-command CLIs toward domain-first or vertical-slice modules
- add characterization coverage before a behavior-preserving CLI refactor
- reduce architecture violations in a CLI codebase
- preserve current commands, flags, output, config behavior, and exit behavior during a refactor
Do not use this skill for:
- greenfield scaffolding with no existing CLI to untangle
- non-CLI web or service applications
- generic codebase cleanups with no CLI architecture goal
Example Triggers
- "Refactor this Node.js CLI so the entrypoint only wires commands."
- "Split this commander-based tool into commands, services, domain, and infrastructure."
- "Move business logic out of
src/cli.ts without breaking flags or output."
- "Add characterization tests, then refactor this CLI command without changing behavior."
- "Move this multi-command CLI to a vertical-slice architecture."
Working Style
- Inspect first. Read the current entrypoint, command registration, tests, package scripts, and touched workflows before editing.
- Build a lightweight discovery map: commands, side-effect hotspots, dependency violations, and current test surface.
- Add or identify characterization coverage for the first command before moving code. If automated tests are impractical, capture exact before/after manual commands, stdout, stderr, exit code, and env/config assumptions.
- Name the current violations explicitly before changing code.
- Start with one vertical slice. Prove the pattern on one representative command before broadening the refactor.
- Preserve behavior. Keep command names, flags, aliases, output, config handling, filesystem behavior, and exit behavior unless the old behavior is clearly broken.
- Refactor incrementally. Avoid large rewrites unless the current design makes smaller moves impossible.
- Keep edits scoped. Do not mix the architecture refactor with unrelated cleanup.
Target Architecture
Create only the boundaries the code actually needs. Do not add empty folders or architecture theater.
Shape A: Technical layers
Use this when the CLI is small, single-domain, or already mostly organized by layers.
src/
cli/ # bootstrapping and command registration only
commands/ # CLI-facing handlers and formatting
services/ # application workflows and orchestration
domain/ # pure business rules and domain errors
infrastructure/ # filesystem, env, network, storage, shell, APIs
shared/ # only truly generic cross-cutting helpers
Shape B: Domain-first vertical slices
Prefer this for multi-command or multi-domain CLIs, where keeping each command's handler, use case, rules, and adapters nearby reduces context switching.
src/
cli/ # bootstrapping, command registration, exit wiring
<domain-or-feature>/
command.ts # CLI-facing handler and formatting
service.ts # use-case orchestration
domain.ts # pure rules and domain errors
infrastructure.ts # side-effect adapters for this slice
shared/ # only truly generic cross-cutting helpers
Folder and file names should match the repo's style. A vertical slice may still split command/, services/, domain/, or infrastructure/ inside the slice if one file would mix responsibilities.
Layer Responsibilities
| Layer or role | Owns | Must avoid |
|---|
cli/ | process bootstrap, global error handling, command registration, exit wiring | business logic, workflow orchestration |
commands/ or command.ts | flag parsing, CLI validation, output formatting, user-facing error mapping | heavy business logic, direct infrastructure work beyond trivial bootstrapping |
services/ or service.ts | use cases, orchestration, coordination between domain and infrastructure | terminal libraries, console.*, process.argv, process.exit, preformatted CLI output as the main result |
domain/ or domain.ts | pure rules, parsing, validation, entities, value objects, domain errors | CLI, filesystem, network, env, infrastructure knowledge |
infrastructure/ or infrastructure.ts | side-effect adapters for filesystem, config, storage, APIs, env, shell, logging | command routing, flag parsing, CLI formatting |
Dependency Direction
cli -> commands/command -> services/service -> domain
service -> infrastructure
infrastructure -> domain
Forbidden directions:
services/ importing commands/ or cli/
domain/ importing services/, commands/, cli/, or infrastructure/
infrastructure/ importing commands/ or cli/
- lower layers knowing how terminal output is rendered
- cross-slice imports that bypass the target slice's public service or domain API
Refactoring Workflow
1. Inspect the current CLI
Before editing, inspect:
- the executable entrypoint and command registration
- the representative command or workflow to extract first
- current tests and package scripts
- current output and error behavior
- side-effect hotspots such as filesystem, env, network, storage, shell, prompts, or process exits
- current dependency direction violations
Avoid full architecture-health reports unless the user asked for an audit. Gather enough facts to choose the first slice and preserve behavior.
2. Establish characterization coverage
Before moving code for the chosen command, identify existing coverage or add focused characterization tests around current behavior:
- command name, flags, aliases, stdin/stdout/stderr, exit code, config/env behavior, and filesystem/network effects
- expected failures and user-facing error text for the touched workflow
- snapshots or exact assertions for stable output; looser assertions only where output is intentionally variable
If automated tests are not practical, run representative commands before the refactor and record the exact command, stdout, stderr, exit code, and env/config assumptions. Repeat after the refactor and compare.
3. Identify current violations
Look for:
- entrypoints that contain business logic
- command handlers that directly do filesystem, network, database, or env work
- services that print, colorize, prompt, or parse CLI flags
- domain logic embedded inside commands or infrastructure
- large files mixing routing, formatting, workflow orchestration, and effects
- abstractions that hide simple flow without reducing real duplication or volatility
4. Pick one vertical slice
Choose one representative command or workflow and extract it end to end:
- move pure rules into
domain/
- move use-case orchestration into
services/
- move side effects into
infrastructure/
- keep parsing, formatting, and exit behavior in
commands/
- keep the entrypoint focused on registration and delegation
Use that slice as the pattern for the rest of the migration.
5. Apply layer-specific rules
Commands
- Keep commands thin.
- Parse flags and validate CLI-specific input here.
- Call services with explicit inputs.
- Format service results for humans here.
- Translate service or domain errors into CLI-friendly messages here.
Services
- Model services as use cases, not generic helpers or managers.
- Return structured data or typed results.
- Do not import
chalk, commander, yargs, inquirer, ora, or readline.
- Do not use
console.log, console.error, process.argv, or process.exit.
- Do not read CLI flags directly.
Domain
- Keep domain code deterministic and reusable.
- Use domain errors for expected business failures.
- Keep validation independent from CLI wording.
- Avoid Node-specific modules unless they are truly unavoidable.
Infrastructure
- Wrap effects behind small, explicit adapters.
- Centralize filesystem, env, config, storage, network, and shell access.
- Return data or low-level errors, not CLI-formatted strings.
Design Constraints
- Prefer the repo's current patterns where they are compatible with the target layering.
- Choose Shape A for simple or already layer-based CLIs. Choose Shape B for multi-domain CLIs where local slices reduce file-hopping.
- Add abstractions only when they remove real complexity, match an existing local pattern, or create a needed test seam.
- Do not add an interface, adapter, or port unless it wraps an external boundary, a volatile dependency, multiple real implementations, or a seam needed for characterization tests.
- Do not introduce a DI container, event bus, saga, plugin system, or generic framework unless it already exists or the current behavior genuinely requires it.
- Split files by responsibility, not by arbitrary size.
- Keep module names specific to the use case, not vague utility buckets.
- When behavior is unclear, preserve the current behavior and document the ambiguity.
Verification
Do not claim success without checking both architecture and behavior.
Architecture checks
Search for forbidden dependencies and usages:
- in
src/services/: terminal libraries, console.*, process.argv, process.exit
- in
src/domain/: imports from cli/, commands/, services/, infrastructure/, plus direct filesystem, network, or env access
- in
src/infrastructure/: imports from cli/ or commands/, CLI formatting, or flag parsing
- globally: dependency direction violations between the layers above
Behavior checks
- run the existing test suite
- run lint, typecheck, and build scripts when available
- add focused tests for touched service or domain logic when coverage is missing
- update command-level tests when command behavior is touched
- manually run representative CLI commands when practical, especially when automated characterization coverage is missing
If a verification step cannot be run, say so explicitly.
Response Contract
When reporting the result, include:
- what changed and why
- chosen architecture shape and why
- which files moved or split
- the architecture violations found and how they were fixed
- behavior coverage used, including characterization tests or manual command transcripts
- verification commands run and their outcomes
- behavior intentionally preserved
- remaining risks or recommended follow-ups