/nw-code-design-oo
OO code-design SSOT — the WHAT-to-design anti-smell catalog (Object Calisthenics, RPP smell taxonomy, effect isolation) shared by the solution architect (design-time) and the crafter (execution-time).
Claude Code
$ npx -y skills add nWave-ai/nWave --skill nw-code-design-oo --agent claude-code
Codex
$ npx skills add nWave-ai/nWave --skill nw-code-design-oo -a codex -y
Cursor
$ npx skills add nWave-ai/nWave --skill nw-code-design-oo -a cursor -y
Gemini CLI
$ npx skills add nWave-ai/nWave --skill nw-code-design-oo -a gemini-cli -y
GitHub Copilot
$ npx skills add nWave-ai/nWave --skill nw-code-design-oo -a github-copilot -y
OpenCode
$ npx skills add nWave-ai/nWave --skill nw-code-design-oo -a opencode -y
Windsurf
$ npx skills add nWave-ai/nWave --skill nw-code-design-oo -a windsurf -y
SKILL.md
nw-code-design-oo.SKILL.mdname: nw-code-design-oo
description: "Load before designing or changing domain/application code: owners, reuse, boundaries, simplest design, domain language, calisthenics, aggregates, architectural and contract tests."
Code design (OO, ports and adapters)
1. Decide before writing
1. Follow the paradigm the repository and touched code already use; a unit in another paradigm gets its reason in the closing statement. Procedural code keeps its imperative structure; do not rewrite it into classes or functions to fit a label. Section 5 (calisthenics) applies to the OO track only; FP track rules are in section 5b. 2. Search for an existing owner by responsibility, invariant and consumers, not by name. A second algorithm deciding a fact an owner already decides is a defect even when it looks different. Reuse or extend the owner at the place that owns the rule; create new only for a stated incompatibility. 3. Merge two units into one abstraction only when they answer the same question and change for the same reason; look-alikes that change for different reasons stay apart. 4. Give each decision, effect, error type and per-process state exactly one owning module. No second module decides or stores it. 5. Changing a shared contract or rule: change its owner, its producer and every consumer in the same diff, or name in the closing statement why a consumer is unaffected. 6. A pipeline stage, container or infra file extends the existing workflow/definition; if it cannot, name the existing alternative and why it failed. 7. Add no field, parameter, flag, branch, layer, option or extension point that no accepted outcome reads. Add no pass-through layer that hides no knowledge. Add no type or channel that repeats a fact an existing carrier already holds. 8. Abstract (framework, plugin, base class) only when a second real caller needs it. 9. An edge case: reuse or relax the general rule; add no named exception, token or special-case branch. 10. When removing or merging a type, layer or branch, keep every accepted outcome observed by a passing test. Keep an element if an accepted outcome breaks without it, or if removing it makes the whole path costlier (more steps, rework or waiting for other roles). 11. Judge a design in this order: behaviour shown by tests; no semantic duplication; clarity; fewest elements. 12. State in the closing statement the chosen design, the alternative you rejected and what the choice costs (coupling, latency, effort). 13. For a change touching an external dependency, retry, recovery or concurrency, name each assumption it relies on, what still works when it fails, and what must never happen then; add one test per such failure.
2. Boundaries
- Dependencies point inward. Domain and application code depend on driven ports named for a capability (`OrderStore`, not `PostgresClient`), never on adapters or technology.
- Every new user-facing behaviour is reachable from its public driving port; check the producer-to-consumer path reaches that port.
- An adapter failure (timeout, decode error, dependency down) is translated at the port into the declared application outcome. No vendor or transport exception type reaches domain or application code.
- A read-only driving port exposes no write method. A preview or validate operation returns a plan value and writes nothing: `dry_run(cfg) -> Plan` is pure, `execute(plan)` is the only impure call.
- Core and business units receive collaborators and environment (clock, env vars, home dir, filesystem) as constructor or function parameters, resolved where the caller can see them. Only composition roots and effect adapters construct or read them.
- Wiring needs a restricted capability (`SafeFileSystem(root)`), not a god object (`os`, `Path.home()`).
- Per component state its contract shape: pure (return only), bounded change (declared mutation set), or plan-only.
3. Domain model and language
- A decision about an object's data lives on its owner: value object, entity, aggregate root, choice type. A policy spanning aggregates lives in a named domain service. An application service coordinates ports and owns no domain rule; a method that reads state, branches, and writes it back moves onto the owner.
- Value object: immutable, refuses an invalid value at construction, equal when its exposed observations agree.
- One transaction changes one aggregate. Reference other aggregates by identity (`ProductId`, not `Product`). A cross-aggregate effect is a domain event.
- A repository serves one aggregate root, returns domain objects, takes domain-language queries (`find_active_orders`), and exposes no SQL, JSON or row shape to domain code.
- Domain events are past-tense facts, one fact each (`OrderConfirmed`).
- Names and types come from the domain vocabulary, never from mechanics (`Manager`, `Helper`, `Data`). Do not abbreviate a name.
- One word names one concept across code, tests and docs of a bounded context. Use the context glossary's terms in changed docs. A word meaning different things in two contexts is kept distinct at the boundary (`SalesOrder`, `FulfilmentOrder`).
- Renaming a concept renames every use in code, tests and docs in the same diff.
- Two confusable concepts get distinct types, not the same `str`/`int`; a native type that already says the concept stays native.
- A generic payload boundary (dict, JSON) is documented at the boundary; deliberate boundaries use precise records, unions or result types.
4. Outcomes
Count the operation's outcomes. Each domain outcome (not found, refused, empty, already done) is in the return type as a named value the caller branches on, illegal combinations unconstructible; never return `None`/null for a domain outcome. Raise only for programming errors. A caller matching on `except` of a domain outcome is a defect.
5. Calisthenics (OO track: all ten, strict, in domain and application code)
1. One indentation level per method. 2. No `else`: guard clauses, polymorphism or Nu
Read more
name: nw-code-design-oo description: "Load before designing or changing domain/application code: owners, reuse, boundaries, simplest design, domain language, calisthenics, aggregates, architectural and contract tests."
Code design (OO, ports and adapters)
1. Decide before writing
1. Follow the paradigm the repository and touched code already use; a unit in another paradigm gets its reason in the closing statement. Procedural code keeps its imperative structure; do not rewrite it into classes or functions to fit a label. Section 5 (calisthenics) applies to the OO track only; FP track rules are in section 5b. 2. Search for an existing owner by responsibility, invariant and consumers, not by name. A second algorithm deciding a fact an owner already decides is a defect even when it looks different. Reuse or extend the owner at the place that owns the rule; create new only for a stated incompatibility. 3. Merge two units into one abstraction only when they answer the same question and change for the same reason; look-alikes that change for different reasons stay apart. 4. Give each decision, effect, error type and per-process state exactly one owning module. No second module decides or stores it. 5. Changing a shared contract or rule: change its owner, its producer and every consumer in the same diff, or name in the closing statement why a consumer is unaffected. 6. A pipeline stage, container or infra file extends the existing workflow/definition; if it cannot, name the existing alternative and why it failed. 7. Add no field, parameter, flag, branch, layer, option or extension point that no accepted outcome reads. Add no pass-through layer that hides no knowledge. Add no type or channel that repeats a fact an existing carrier already holds. 8. Abstract (framework, plugin, base class) only when a second real caller needs it. 9. An edge case: reuse or relax the general rule; add no named exception, token or special-case branch. 10. When removing or merging a type, layer or branch, keep every accepted outcome observed by a passing test. Keep an element if an accepted outcome breaks without it, or if removing it makes the whole path costlier (more steps, rework or waiting for other roles). 11. Judge a design in this order: behaviour shown by tests; no semantic duplication; clarity; fewest elements. 12. State in the closing statement the chosen design, the alternative you rejected and what the choice costs (coupling, latency, effort). 13. For a change touching an external dependency, retry, recovery or concurrency, name each assumption it relies on, what still works when it fails, and what must never happen then; add one test per such failure.
2. Boundaries
- Dependencies point inward. Domain and application code depend on driven ports named for a capability (`OrderStore`, not `PostgresClient`), never on adapters or technology.
- Every new user-facing behaviour is reachable from its public driving port; check the producer-to-consumer path reaches that port.
- An adapter failure (timeout, decode error, dependency down) is translated at the port into the declared application outcome. No vendor or transport exception type reaches domain or application code.
- A read-only driving port exposes no write method. A preview or validate operation returns a plan value and writes nothing: `dry_run(cfg) -> Plan` is pure, `execute(plan)` is the only impure call.
- Core and business units receive collaborators and environment (clock, env vars, home dir, filesystem) as constructor or function parameters, resolved where the caller can see them. Only composition roots and effect adapters construct or read them.
- Wiring needs a restricted capability (`SafeFileSystem(root)`), not a god object (`os`, `Path.home()`).
- Per component state its contract shape: pure (return only), bounded change (declared mutation set), or plan-only.
3. Domain model and language
- A decision about an object's data lives on its owner: value object, entity, aggregate root, choice type. A policy spanning aggregates lives in a named domain service. An application service coordinates ports and owns no domain rule; a method that reads state, branches, and writes it back moves onto the owner.
- Value object: immutable, refuses an invalid value at construction, equal when its exposed observations agree.
- One transaction changes one aggregate. Reference other aggregates by identity (`ProductId`, not `Product`). A cross-aggregate effect is a domain event.
- A repository serves one aggregate root, returns domain objects, takes domain-language queries (`find_active_orders`), and exposes no SQL, JSON or row shape to domain code.
- Domain events are past-tense facts, one fact each (`OrderConfirmed`).
- Names and types come from the domain vocabulary, never from mechanics (`Manager`, `Helper`, `Data`). Do not abbreviate a name.
- One word names one concept across code, tests and docs of a bounded context. Use the context glossary's terms in changed docs. A word meaning different things in two contexts is kept distinct at the boundary (`SalesOrder`, `FulfilmentOrder`).
- Renaming a concept renames every use in code, tests and docs in the same diff.
- Two confusable concepts get distinct types, not the same `str`/`int`; a native type that already says the concept stays native.
- A generic payload boundary (dict, JSON) is documented at the boundary; deliberate boundaries use precise records, unions or result types.
4. Outcomes
Count the operation's outcomes. Each domain outcome (not found, refused, empty, already done) is in the return type as a named value the caller branches on, illegal combinations unconstructible; never return `None`/null for a domain outcome. Raise only for programming errors. A caller matching on `except` of a domain outcome is a defect.
5. Calisthenics (OO track: all ten, strict, in domain and application code)
1. One indentation level per method. 2. No `else`: guard clauses, polymorphism or Nu
Other skills on nwave.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about nw-code-design-oo.
- Which coding agents can use nw-code-design-oo?
- nw-code-design-oo installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

