Codex compatibility note:
- Invoke repository skills with
$skill-namein Codex; this mirrored copy rewrites legacy Claude/skill-namereferences.- Task tracker mandate: BEFORE executing any workflow or skill step, create/update task tracking for all steps and keep it synchronized as progress changes.
- User-question prompts mean to ask the user directly in Codex.
- Ignore Claude-specific mode-switch instructions when they appear.
- Strict execution contract: when a user explicitly invokes a skill, execute that skill protocol as written.
- Subagent authorization: when a skill is user-invoked or AI-detected and its protocol requires subagents, that skill activation authorizes use of the required
spawn_agentsubagent(s) for that task.- Do not skip, reorder, or merge protocol steps unless the user explicitly approves the deviation first.
- For workflow skills, execute each listed child-skill step explicitly and report step-by-step evidence.
- If a required step/tool cannot run in this environment, stop and ask the user before adapting.
Codex Project-Reference Loading (No Hooks)
Codex uses static project-reference loading instead of runtime-injected project docs. When coding, planning, debugging, testing, or reviewing, open project docs explicitly using this routing.
Always read:
docs/project-config.json(project-specific paths, commands, modules, and workflow/test settings)docs/project-reference/docs-index-reference.md(routes to the fulldocs/project-reference/*catalog)docs/project-reference/lessons.md(always-on guardrails and anti-patterns)
Missing/stale context route: If docs/project-config.json, the docs index, lessons.md, CLAUDE.md, AGENTS.md, or any task-required reference doc is missing or stale, auto-run $project-init or the narrow setup route ($project-config, $docs-init, $scan-all, $scan --target=<key>, $claude-md-init) before ordinary project-specific work. If Codex mirrors or AGENTS.md are missing/stale, ask the user to run $sync-codex; do not auto-run it.
Situation-based docs:
- Project structure/architecture/tech-stack/deployment/setup (any layer — backend, frontend, or infra):
project-structure-reference.md - Backend/CQRS/API/domain/entity changes:
backend-patterns-reference.md,domain-entities-reference.md - Frontend/UI/styling/design-system:
frontend-patterns-reference.md,scss-styling-guide.md,design-system/README.md - Spec authoring,
docs/specs/pathing, or TC format:feature-spec-reference.md,spec-system-reference.md,spec-principles.md - Behavior/public-contract changes or spec-test-code sync:
workflow-spec-test-code-cycle-reference.mdplus the spec docs above - Derived spec indexes/ERDs/reimplementation guides:
spec-system-reference.mdand source Feature Specs underdocs/specs/ - Integration test implementation/review:
integration-test-reference.md - E2E test implementation/review:
e2e-test-reference.md - Code review/audit work:
code-review-rules.mdplus domain docs above based on changed files
Do not read all docs blindly. Start from docs-index-reference.md, then open only relevant files for the task.
[BLOCKING] Execute skill steps in declared order. NEVER skip, reorder, or merge steps without explicit user approval. [BLOCKING] Before each step or sub-skill call, update task tracking: set
in_progresswhen step starts, setcompletedwhen step ends. [BLOCKING] Every completed/skipped step MUST include brief evidence or explicit skip reason. [BLOCKING] If Task tools are unavailable, create and maintain an equivalent step-by-step plan tracker with the same status transitions.
Quick Summary
Goal: Analyze business domain (bounded contexts, aggregates, entities, VOs, domain events, cross-context relationships) and generate a domain model report + ERD — producing a user-validated DDD domain model with correct bounded contexts, aggregate boundaries, and event flows so downstream implementation builds on the right invariants and avoids costly boundary rework after consumers depend on them.
Summary:
- Purpose: turn business artifacts into a user-validated DDD domain model (bounded contexts, aggregates, entities, VOs, domain events, ERD) so downstream code builds on correct invariants — never re-cut boundaries after consumers depend on them.
- Ten ordered steps (do all, none skippable): 0 locate active plan +
domain-entities-reference.md→ 1 load business context (nouns→entities, verbs→events) → 2 identify bounded contexts → 3 model entities & aggregates → 4 map relationships → 5 domain events → 6 generate Mermaid ERD → 7 user-validation interview → 8 entity-change assessment vs reference doc → 9 updateplan.md## Domain Model. - Drive the model from business artifacts, not guesses: load plan/PBI/business-eval inputs and
domain-entities-reference.md, then extract nouns→entities, verbs→events, roles, and processes before classifying anything. - Every concept passes the Entity-vs-VO matrix and aggregate boundary rules (≤5 entities, one transaction, reference-by-ID only, root is the sole mutation entry) — flag primitive obsession and anemic models as you go.
- User validation is non-skippable: present bounded contexts and the Mermaid ERD, then run the 5-8 question ask the user directly interview to confirm boundaries, aggregate roots, and event flows before marking the model confirmed.
- Close the loop on persistence: reconcile findings against
domain-entities-reference.md(new/modified/deprecated), update the## Domain Modelsection ofplan.md, and keep cross-context communication event-driven with{AggregateNoun}{PastTenseVerb}naming and no cross-service FKs.
Workflow:
- Locate Active Plan & Domain Reference — Glob
plans/*/plan.md, read plan + prior research +domain-entities-reference.md; set{plan-dir} - Load Business Context — Read idea, business evaluation, refined PBI artifacts; extract nouns→entities, verbs→events, roles, processes
- Identify Bounded Contexts — Group related concepts, define context boundaries (validate grouping with user)
- Model Entities & Aggregates — Define aggregates, entities, value objects per context
- Map Relationships — Entity relationships, cross-context integration points (context map)
- Domain Events — Identify events crossing context boundaries,
{AggregateNoun}{PastTenseVerb} - Generate ERD — Mermaid ER diagram with all entities and relationships
- User Validation — Present model, ask 5-8 questions, confirm decisions →
status: confirmed - Domain Entity Change Assessment — Compare against
domain-entities-reference.md, update/create if needed - Update Main Plan — Append/update
## Domain Modelsection of{plan-dir}/plan.md
Key Rules:
- MANDATORY IMPORTANT MUST ATTENTION validate every bounded context boundary with user
- MANDATORY IMPORTANT MUST ATTENTION include Mermaid ERD diagram in report
- MANDATORY IMPORTANT MUST ATTENTION run user validation interview at end (NEVER skip)
- Every entity belongs to exactly one bounded context
- Cross-context communication via domain events only — NEVER direct references
Be skeptical. Every claim needs traced proof, confidence percentages >80% to act.
DDD Reference: Strategic Design
Bounded Context Rules
| Signal | Action |
|---|---|
| Same term, different meaning across teams | Separate bounded contexts |
| Different data lifecycles for same concept | Separate contexts |
| Different invariants on same entity | Separate contexts |
| Team ownership conflict (Conway's Law) | Separate contexts |
| Shared DB table touched by two services | Extract shared kernel or introduce ACL |
Ubiquitous Language Rules:
- Every noun in codebase matches domain expert vocabulary exactly (not "User" when the domain says "Customer")
- Class/method/variable names reflect ubiquitous language — zero translation layers inside bounded context
- Developers say "we call it X but domain means Y" → model is wrong, fix it
Context Map Pattern Decision Table
| Situation | Pattern |
|---|---|
| Two teams, joint success/failure, equal power | Partnership |
| Small shared code nucleus, joint governance acceptable | Shared Kernel |
| Downstream can influence upstream roadmap | Customer-Supplier |
| Downstream has no influence on upstream | Conformist |
| External/legacy system with hostile or polluting model | Anti-Corruption Layer (ACL) |
| One upstream, many downstream consumers | Open Host Service + Published Language |
| Integration cost exceeds integration value | Separate Ways |
ACL — when to use: Upstream is external/legacy/third-party (Salesforce, SAP, Workday). Upstream types NEVER cross ACL into domain model.
Shared Kernel — when NOT to use: Teams cannot coordinate on every change → use Customer-Supplier + Published Language instead.
DDD Reference: Entity vs Value Object
Decision Matrix
| Question | Entity | Value Object |
|---|---|---|
| Has identity beyond its attributes? | YES | no |
| Can two instances with same data be distinct? | YES | no |
| Has a lifecycle (created, modified, deleted)? | YES | no |
| Identified by an ID in any downstream system? | YES | no |
| Measured or described (quantity, address, money)? | no | YES |
| Replaced rather than modified on change? | no | YES |
| Must be found independently of parent? | YES | no |
Fast heuristics:
- Replace with equal-valued copy → breaks nothing? → Value Object
- Must be tracked across time or fetched by ID? → Entity
- Always retrieved as part of another object? → likely Value Object
- Two instances with same data are interchangeable? → Value Object
Canonical Value Objects
| VO | Attributes | Key Invariants |
|---|---|---|
Money |
amount: Decimal, currency: Currency | amount ≥ 0, valid ISO currency; Add/Subtract require same currency |
Email |
value: string | RFC 5322 format, normalized to lowercase |
Address |
street, city, country, postalCode | All fields non-empty; composed of Country + PostalCode VOs |
DateRange |
start: DateOnly, end: DateOnly | start ≤ end; operations: Contains, Overlaps, Duration |
PhoneNumber |
countryCode, number | E.164 format |
Percentage |
value: int | 0 ≤ value ≤ 100 |
Primitive Obsession → Value Object Mapping
| Primitive Usage | Replace With |
|---|---|
string orderId |
OrderId typed wrapper |
decimal amount, string currency |
Money { amount, currency } |
string street, string city, string zip |
Address { ... } |
DateTime start, DateTime end |
DateRange { start, end } |
string email |
Email { value } |
int percentage |
Percentage { value } |
string phoneNumber |
PhoneNumber { countryCode, number } |
Rule: Primitive with validation rules, formatting, or always passed grouped with other primitives → missing Value Object.
Value Object Construction Pattern
A VO is self-validating: invariants enforced at construction via a factory (no public constructor that can produce an invalid instance), immutable, and equality-by-value. The base class and factory names below are one illustrative instantiation — translate to your language's equivalents.
Example (illustrative — adapt to your language):
// Self-validating VO — never an invalid instance in memory
public sealed class Email : ValueObject<Email>
{
private Email(string value) { Value = value; }
public string Value { get; }
public static Email Of(string raw)
{
var normalized = raw?.Trim().ToLowerInvariant();
if (string.IsNullOrWhiteSpace(normalized) || !IsValidFormat(normalized))
throw new DomainException($"Invalid email: {raw}");
return new Email(normalized);
}
}
Rules:
- No public constructor without validation — factory method (
Of()/Create()) enforces invariants - VOs can reference other VOs; VOs NEVER reference entities (lifecycle coupling)
- Mutation means replacement:
email = Email.Of(newValue), NEVERemail.Value = newValue
VO Persistence Strategies
| Strategy | When to Use | Trade-offs |
|---|---|---|
| Owned types (EF Core) | VO maps to same table as owning entity | Simple, no FK, nullable columns possible |
| Embedded document store | VO stored as subdocument | Natural fit, no joins |
| JSON column | Complex VO, low query frequency on VO fields | Flexible, not queryable by parts |
| Serialized string | Simple VOs (Email, PostalCode) | Compact, unqueryable by parts |
Rule: VOs NEVER have own table with primary key — that makes them entities by infrastructure.
DDD Reference: Entity Design
Identity Strategies
| Strategy | When to Use | Trade-offs |
|---|---|---|
| ULID (default) | New entities in distributed system | Sortable, URL-safe, monotonic, 128-bit |
| UUID v4 | True randomness / security-sensitive IDs | Not sortable, fragmented indexes |
| UUID v7 | Sortable UUID needed | Time-ordered, good index locality |
| Natural key | Domain guarantees permanent uniqueness (SSN, EAN) | Unstable — domain can change |
| Surrogate int | Legacy/single-DB sequences | No distributed generation |
| Composite key | Relationship/join table | Harder to reference from other aggregates |
Rules:
- Prefer ULID for new entities — sortable, no coordination overhead
- NEVER use email/username as PK — users change them
- Cross-service references use same ID type as the owning service
Rich vs Anemic Domain Model
| Anemic (Anti-Pattern) | Rich (Correct) |
|---|---|
| Entity is data bag, logic in services | Entity contains behavior + invariants |
public set on all properties |
Private setters, mutation via named methods |
OrderService.Confirm(order) |
order.Confirm() |
| Service checks rules then mutates entity | Entity refuses invalid state transitions |
| Logic duplicated across services | Single authoritative location in entity |
Tell Don't Ask Principle:
- BAD:
if (order.Status == Confirmed) { order.Status = OnHold; }(external ask + mutate) - GOOD:
order.Hold(reason)(entity enforces its own invariants)
Entity Invariant Enforcement
A rich entity guards its own state: a private constructor reserved for ORM/persistence hydration, named factory methods for valid creation, and intent-named mutation methods that reject invalid transitions and emit domain events. The base class, guard helper, and ID generator below are one illustrative instantiation — substitute your language's equivalents.
Example (illustrative — adapt to your language):
public class Order : AuditedAggregateRoot<Order, string>
{
private Order() { } // ORM hydration only
public static Order Create(string name, Email email, WarehouseId warehouseId)
{
Guard.NotNullOrWhitespace(name, nameof(name));
Guard.NotNull(email, nameof(email));
return new Order
{
Id = Ulid.NewUlid().ToString(),
Name = name,
Email = email,
WarehouseId = warehouseId,
Status = OrderStatus.Confirmed
};
}
public void Cancel(string reason, DateOnly cancellationDate)
{
if (Status == OrderStatus.Cancelled)
throw new DomainException("Order already cancelled");
if (cancellationDate < DateOnly.FromDateTime(DateTime.UtcNow))
throw new DomainException("Cancellation date cannot be in the past");
Status = OrderStatus.Cancelled;
CancellationReason = reason;
CancellationDate = cancellationDate;
AddDomainEvent(new OrderCancelledDomainEvent(Id, cancellationDate));
}
}
Entity Lifecycle State Machines
Document ALL transitions explicitly. Unmodeled transitions throw DomainException.
Draft → Submitted (Submit())
Submitted → Approved (Approve(approverId))
Submitted → Rejected (Reject(reason))
Approved → Active (Activate())
Active → Suspended (Suspend(reason))
Suspended → Active (Reinstate())
Active → Archived (Archive())
| Pattern | When to Use |
|---|---|
| Status enum + transition methods | Simple linear/branching lifecycles (most cases) |
| State pattern (class per state) | Complex per-state behavior, many states |
| Event sourcing | Full audit trail + point-in-time reconstruction required |
Domain Validation Layers
| Layer | What It Validates | Failure Signal (per stack) |
|---|---|---|
| Value Object | Single-value format/range invariants | Construction failure (raised error or result type) |
| Entity method | Aggregate consistency rules, state transitions | Domain rule violation (e.g. DomainException) |
| Application service | Cross-aggregate rules, authorization, existence | Structured validation result (e.g. ValidationResult / problem-details payload) |
| Infrastructure | DB constraints (last resort, NEVER first line) | Persistence-layer error (last-resort constraint) |
Decision rule:
- Rule requires loading another aggregate? → Application service
- Rule needs only data within aggregate? → Entity method
- Rule concerns single value's format? → Value Object constructor
Factory Methods on Entities
Use when: construction requires domain logic, multiple paths, raises domain events, or object graph initialization.
Naming:
Order.Create(...)— primary creationOrder.Place(...)— semantically loaded creation (domain language)- Private constructor — ORM hydration only, NEVER called directly
Temporal (Bi-Temporal) Entities
Two time axes: valid time (fact true in real world) + transaction time (recorded in system).
ProductPrice {
validFrom: DateOnly // valid time: price effective from
validTo: DateOnly // valid time: price effective until
recordedAt: DateTime // transaction time: when entered into system
}
Use when: regulatory compliance, retroactive corrections, "as-of" queries.
DDD Reference: Aggregate Design
Aggregate Boundary Rules
- Invariant scope — boundary = objects needed to enforce invariants atomically
- Transaction boundary — exactly one database transaction per aggregate operation
- Consistency scope — must be consistent atomically? same aggregate. Eventual consistency acceptable? separate aggregates
- Small aggregates preferred — fewer members = fewer transaction conflicts = better scalability
Aggregate Size Heuristics
| Heuristic | Guideline |
|---|---|
| Default | Start with single-entity aggregate unless invariant demands more |
| Add member | Only when invariant requires atomic consistency across root + member |
| Max size | > 5 entities → redesign; likely missing sub-aggregates |
| Concurrent writes conflict | Reduce aggregate size |
Aggregate Root Responsibilities
- Maintain all invariants across all members
- All mutation paths go through root (child entities NEVER directly accessible from outside)
- Emit domain events for significant state changes
- Control creation of child entities (factory methods on root)
- Assign IDs to child entities
Rule: Outside code NEVER holds direct reference to non-root entity within aggregate.
Cross-Aggregate References
| Rule | Detail |
|---|---|
| Reference by ID only | NEVER order.Customer.Name — load separately |
| No FK object navigation | CustomerId field, NEVER Customer Customer navigation property |
| Cross-aggregate transactions are eventual | Need them in same transaction → boundaries are wrong |
| Deletion cascade | Domain event → handler → compensating action in other aggregate |
Aggregate Invariant Enforcement
All mutation flows through the aggregate root, which checks every invariant before applying a change and recomputes derived state so no member can be left inconsistent. The throw-on-violation idiom below is one illustrative instantiation — your language may surface invariant breaches differently (exceptions, result types).
Example (illustrative — adapt to your language):
public void AddLineItem(ProductId productId, int quantity, Money unitPrice)
{
if (Status != OrderStatus.Draft)
throw new DomainException("Cannot modify confirmed order");
if (LineItems.Count >= 50)
throw new DomainException("Order cannot exceed 50 line items");
var item = OrderLineItem.Create(productId, quantity, unitPrice);
_lineItems.Add(item);
RecalculateTotal(); // invariant: Total == sum(lineItems)
}
Aggregate Design Patterns
| Pattern | When to Use |
|---|---|
| Single-entity aggregate | Default — most entities are their own aggregate |
| Nested aggregate | Invariant requires atomic consistency across root + children |
| Aggregate with VOs | Root + embedded value objects (no IDs, no own table) |
When to Break Aggregate Rules (Pragmatic DDD)
| Situation | Acceptable Pragmatism |
|---|---|
| ORM limitation (EF owned entities) | Allow private owned collections even if not strictly necessary |
| Performance — 1-query load | Embed child data as VO/owned type rather than separate aggregate |
| Legacy schema migration | Accept cross-aggregate FK temporarily, document as debt |
Rule: Breaking aggregate rules acceptable ONLY when explicitly documented as technical debt with mitigation plan.
DDD Reference: ERD Design
Cardinality Types
| Cardinality | Mermaid | When to Use |
|---|---|---|
| 1:1 | |o--o| |
Same-table extension, optional sub-type, shared lifecycle |
| 1:N | |o--{ |
Parent-child, one entity owns many dependent records |
| M:N | }o--o{ |
Peer relationship; ALWAYS use explicit join/association entity |
M:N rule: Relationship has attributes (date joined, role) → make join table explicit named entity.
1:1 decision: Same concept with optional attributes → same table. Different concepts with independent lifecycles → separate tables with FK.
Normalization Targets
| Normal Form | Rule | Use For |
|---|---|---|
| 1NF | Atomic values, no repeating groups | Always — baseline |
| 2NF | No partial dependency on composite key | Composite PKs only |
| 3NF | No transitive dependencies | OLTP — standard target |
| BCNF | Every determinant is a candidate key | When 3NF still has anomalies |
| Denormalized | Intentional redundancy | Read models, projections, OLAP |
Rule: 3NF for OLTP write models; denormalize only in read models/projections with documented justification.
Identifying vs Non-Identifying Relationships
| Type | FK in Child PK? | Child Existence |
|---|---|---|
| Identifying | YES (FK is part of PK) | Child cannot exist without parent (line item without order) |
| Non-identifying | NO (FK is separate column) | Child can exist independently (order without warehouse) |
Mapping to DDD: Identifying relationship → child entity inside parent aggregate. Non-identifying FK → likely separate aggregates.
ERD for Microservices
- Each bounded context has its OWN ERD — NEVER cross-service entity arrows
- Cross-service references shown as
ExternalEntityId (string/ULID)— ID only, no relationship line - Shared data shown as replicated/synced data note, NEVER shared table
- Event-sourced aggregates: ERD shows current state projection, not event stream
ERD Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
| God table (50+ columns) | Every feature adds more columns | Extract sub-entities, decompose by bounded context |
| Cross-service FK | Direct FK across microservice schemas | Replicate needed data, use events to sync |
| Polymorphic association | entityType + entityId columns |
Separate tables per concrete type or JSON column |
| EAV (Entity-Attribute-Value) | Key-value rows replacing typed columns | JSON column or explicit schema with migration |
| Implicit M:N (two FK cols, no PK) | Hard to add relationship attributes | Explicit join table with surrogate PK |
| Nullable FK everywhere | Unclear cardinality | Separate optional relationship into explicit table |
ERD to Aggregate Mapping
| ERD Pattern | DDD Mapping |
|---|---|
| Parent-child with identifying relationship | Child entity inside parent aggregate |
| Parent-child with non-identifying FK | Likely separate aggregates (independent lifecycle) |
| M:N join table with no extra attributes | Both sides separate aggregates, IDs in domain events |
| M:N join table with attributes | Association entity as separate aggregate |
| Strong entity + many weak dependents | Root entity + owned collection aggregate |
DDD Reference: Domain Events
Event Naming Conventions
Format: {AggregateNoun}{PastTenseVerb} — what happened, not what to do.
| Good | Bad |
|---|---|
OrderCancelled |
CancelOrder (command naming) |
OrderConfirmed |
OrderStatusChanged (too generic) |
PaymentProcessed |
PaymentComplete (not past tense) |
SalaryBandUpdated |
SalaryChanged (vague) |
Event Payload Design Rules
| Decision | Rule |
|---|---|
| Minimal vs fat | Minimal (default): AggregateId + what changed. Consumer queries for rest if needed |
| Fat event | Accept when: round-trip cost high AND consumers known AND staleness acceptable |
| Required fields always | AggregateId, OccurredOn (UTC timestamp), Version, CorrelationId |
| No mutable references | Payload contains value copies, not object references |
Domain Events vs Integration Events
| Dimension | Domain Event | Integration Event |
|---|---|---|
| Scope | Within one bounded context | Across bounded contexts |
| Delivery | In-process, post-commit | Via configured message bus or event stream |
| Schema ownership | Domain owns, internal | Published Language contract |
| Versioning | Internal refactor freely | Versioned, backward-compatible |
| Failure handling | Transaction rollback | At-least-once delivery, idempotent consumer |
Rule: Domain event raised → in-process handlers fire → if cross-service needed, handler publishes integration event to message bus.
Event Versioning Strategies
| Strategy | Mechanism | Trade-offs |
|---|---|---|
| Additive only | NEVER remove/rename fields, only add | Simple, payload bloats over time |
| Multiple versions | OrderConfirmedV1, OrderConfirmedV2 |
Clear versioning, consumers handle both |
| Upcasting | Transform old events to new on deserialization | Transparent to consumers, complex infra |
Backward compatibility rules:
- Adding optional field → backward compatible
- Removing or renaming field → breaking
- Changing field type → breaking
DDD Reference: Repository Pattern
Interface Design Principles
- One repository per aggregate root — NEVER one for child entities
- Domain language in methods —
GetConfirmedOrdersInWarehouse()notFindAll(e => e.Status == Active) - Return domain objects — repositories return entities, not DTOs
- No infrastructure concerns — no leaked query/ORM types or connection strings in the interface.
- Async-first — methods return the configured runtime's async primitive.
Repository vs DAO
| Repository | DAO |
|---|---|
| Domain-oriented interface | Data-oriented interface |
| Returns entities/VOs | Returns DTOs or raw data |
| Used in application/domain layer | Used in infrastructure layer |
| Hides persistence mechanism | Often tied to persistence mechanism |
Query Objects — When to Use Specification
Use when: rule used in multiple places, warrants naming + testing, needs composition.
A specification names a query predicate as a reusable, composable, testable unit owned by the domain. The expression-tree form below is one illustrative instantiation — your language may model it as a predicate function, query builder, or specification object.
Example (illustrative — adapt to your language):
// Static expression on entity — composable, testable
public static Expression<Func<Order, bool>> ByWarehouseExpression(string warehouseId)
=> o => o.WarehouseId == warehouseId && o.Status == OrderStatus.Confirmed;
When NOT to use Specification: Simple single-use predicate → inline lambda. Rule only used once → repository method directly.
DDD Reference: Anti-Patterns Quick Reference
| Anti-Pattern | Detection Signal | Fix |
|---|---|---|
| Anemic domain model | Services have entity-specific logic; entity has all public setters | Move logic to entity |
| God aggregate | Aggregate > 5 entities or 100+ ms load time | Extract sub-aggregates |
| Leaky aggregate | External code mutates child entities directly | Private setters + root mutation methods |
| Primitive obsession | 3+ primitives always travel together as group | Introduce Value Object |
| Implicit concept | Domain expert names it, code doesn't model it | Explicit class |
| Feature envy | Method uses more of B's data than A's | Move method to B |
| Getter/setter entity | All mutation via property assignment, no intent-named methods | Replace with Cancel(), Approve(), etc. |
| Cross-aggregate loading | Full aggregate loaded just to read one field | Pass scalar; resolve in app service |
| Side effects in handler | Command handler calls multiple services after save | Domain event + separate handlers |
| Cross-service FK | Database FK across microservice schemas | ID reference + event-driven sync |
| Shared Kernel overuse | Two teams, one shared model, constant coordination overhead | Split to Customer-Supplier |
| Missing ACL | External model types bleed into domain classes | ACL at integration boundary |
Skill Workflow
Step 0: Locate Active Plan & Domain Reference (MANDATORY)
- Glob
plans/*/plan.mdsorted by modification time — find active plan directory - Read
plan.md— project scope, goals, prior decisions - Read all
{plan-dir}/research/*.md— avoid duplicating prior work - Read
docs/project-reference/domain-entities-reference.md(if exists) — project's single source of truth for domain entities - Set
{plan-dir}variable — all outputs write to this directory
If no plan directory, create using naming convention from session context.
MUST ATTENTION update {plan-dir}/plan.md with domain model summary section after completing analysis.
Step 1: Load Business Context
Read artifacts from prior workflow steps (search plans/ + team-artifacts/):
- Active plan (
{plan-dir}/plan.md) — scope, goals, constraints - Business evaluation report — value proposition, customer segments
- Refined PBI — acceptance criteria, user stories, features
- Discovery interview notes — problem statement, user roles
Extract and list:
- Nouns — Candidate entities (user, order, product, etc.)
- Verbs — Candidate domain events (created, approved, assigned, etc.)
- Roles — User types with different permissions/views
- Processes — Business workflows (application flow, review cycle, etc.)
Step 2: Identify Bounded Contexts
Group related entities using DDD principles. Apply context boundary signals from reference table above.
### Bounded Context: {Name}
**Purpose:** {What this context owns — one sentence}
**Classification:** Core domain / Supporting / Generic
**Key Responsibility:** {primary business capability}
**Team ownership:** {suggested team or role}
**Ubiquitous language:** {key terms and their meaning in this context}
Context boundary tests:
- Same term used by two teams with different meanings? → separate contexts
- Two entities with same name but different invariants? → separate contexts
- Can this context be worked on without understanding the other? → good separation
MANDATORY IMPORTANT MUST ATTENTION present identified contexts to user by asking the user directly:
- "I identified {N} bounded contexts: {list}. Does this grouping make sense?"
- Options: Agree (Recommended) | Merge {X} and {Y} | Split {Z} | Add missing context
Step 3: Model Entities & Aggregates
Per bounded context: classify each concept using Entity vs VO matrix above, then apply aggregate boundary rules.
### {Context Name}
**Aggregate Root:** {EntityName}
**Identity:** {ULID / UUID / Natural key — justify}
**Lifecycle states:** {Draft → Active → Archived, etc.}
- **Child Entities:** {list — share aggregate boundary, identifying FK}
- **Value Objects:** {list — immutable, no identity, embedded}
- **Invariants:** {business rules this aggregate enforces atomically}
- **Factory method:** {Order.Create(...) / Order.Place(...)}
**Other Aggregates in this Context:**
- {Entity} — {purpose, identity strategy, lifecycle}
Entity Detail Template
| Entity | Classification | Identity | Key Fields | Lifecycle States | Invariants |
|---|
…(truncated)