Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/commands/coreex-scaffold.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: "CoreEx Solution Scaffolder — guides solution shaping after bootstrap (hosts, database, messaging, refdata/outbox/DDD/ROP options) and turns the answers into dotnet new template commands."
description: "CoreEx Solution Scaffolder — guides solution shaping after bootstrap (hosts, database, messaging, refdata/outbox/DDD/ROP options, and an optional Aspire AppHost for local multi-host orchestration/dashboard) and turns the answers into dotnet new template commands."
allowed-tools: [Read, Glob, Grep, Edit, Write, Bash]
---

Expand Down
3 changes: 2 additions & 1 deletion .github/agents/coreex-expert.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ Do not run `/coreex-docs-sync` silently — always offer and wait for confirmati
- Separate explanation, plan, and implementation guidance clearly.
- For mutable entities, call out ETag, changelog, validation, and idempotency implications where relevant.
- For messaging, explicitly distinguish API-only, API plus outbox relay, API plus subscriber, and full orchestration shapes.
- The Api/Relay/Subscribe host split is a workload-isolation convention, not a technical requirement — for a small, low-traffic solution, consolidating hosted-service processing (outbox relay, subscriber receiving) into the Api host is a legitimate simplification. Mention it when a user's stated scale/traffic profile suggests the extra processes may not be earning their operational cost; see [Hosts Layer Guide § Choosing a Host Topology](https://github.com/Avanade/CoreEx/blob/main/samples/docs/hosts-layer.md#choosing-a-host-topology-split-vs-consolidate).
- Never recommend editing `*.g.cs`, `*.g.sql`, or `*.g.pgsql` files — direct the user to the owning generator instead (Roslyn source generator for `*.g.cs`; `*.Database` project for `*.g.sql`/`*.g.pgsql`).

## Decision routing
Expand Down Expand Up @@ -145,7 +146,7 @@ These skills are part of the CoreEx AI workflow set and live in `.github/skills/

**Broader routing:**

- Greenfield solution or host scaffolding → `/coreex-scaffold` (`coreex-solution-scaffolder` skill), which runs the matching [CoreEx.Template](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx.Template/README.md) `dotnet new coreex*` commands.
- Greenfield solution or host scaffolding, or adding a .NET Aspire AppHost for local multi-host orchestration/dashboard → `/coreex-scaffold` (`coreex-solution-scaffolder` skill), which runs the matching [CoreEx.Template](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx.Template/README.md) `dotnet new coreex*` commands.
- Repo mapping or onboarding documentation → `/acquire-codebase-knowledge`.
- Retrofit that no single skill covers → inspect the current code and recommend the smallest manual changes aligned to the samples and instructions.

Expand Down
3 changes: 2 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ solution; the rule applies to consumer solutions where these assets are installe
- **Test**: `dotnet test CoreEx.sln` or target specific projects.
- **Single test**: `dotnet test <proj> --filter "FullyQualifiedName~<name>"`
- **Samples**: docker-compose infrastructure + dotnet run for Database projects + Aspire AppHost.
- **`CoreEx.Template` changes**: `dotnet build`/`dotnet test` do **not** exercise `src/CoreEx.Template/content/**` — that's raw `dotnet new` template content, not compiled C#. Any change under `src/CoreEx.Template/content/` (a host's `Program.cs`/`GlobalUsing.cs`/`.csproj`, a `template.json` symbol, etc.) must be validated by actually scaffolding it: run [`tools/validate-template-pack.ps1`](../tools/validate-template-pack.ps1), which packs the template, installs it, scaffolds every parameter combination it knows about into temp directories, and `dotnet build`s the ones flagged `Build = $true`. It also runs in CI (`.github/workflows/CI.yml`). If you add a new template, host, or parameter combination, add a matching scenario to the script's `$testScenarios` array — parameter-conditional bugs (an unconditional `global using`/`ProjectReference` that should have been gated behind a symbol like `has-data-provider` or `implement-servicebus`) only surface when the generated code is actually compiled, which most existing host-template scenarios don't yet do since they scaffold in isolation without their `coreex` solution siblings.
- **Linting**: No separate `dotnet format`. Build is the lint pass (nullable, LangVersion=preview, TreatWarningsAsErrors in `src\Directory.Build.props`).
- **Formatting**: 4 spaces for `*.cs`, 2 spaces for `*.json|*.xml|*.yaml|*.props|*.csproj|*.sln|*.sql` per `.editorconfig`.
- **Ad-hoc spike/reflection projects**: this repo uses Central Package Management (root `Directory.Packages.props`). A throwaway `dotnet new console` project scaffolded *inside* the repo tree (even outside `src\`/`tests\`) will silently inherit it and can fail to restore (`NU1008`) if it references a package/version not centrally pinned. Scaffold spike projects outside the repo tree, or set `<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>` in the spike project's own `.csproj` to opt out locally.
Expand Down Expand Up @@ -189,7 +190,7 @@ see [coreex-ai-workflows.md](./coreex-ai-workflows.md).

| Command | Type | When to use |
|---------|------|-------------|
| `CoreEx.Template` | Template pack | Deterministic `dotnet new` scaffolding. Pin the version — `dotnet new install CoreEx.Template::<version>` — then `dotnet new coreex` (solution), `coreex-api` / `coreex-relay` / `coreex-subscribe` (hosts), or `coreex-ai` (AI workflow assets). |
| `CoreEx.Template` | Template pack | Deterministic `dotnet new` scaffolding. Pin the version — `dotnet new install CoreEx.Template::<version>` — then `dotnet new coreex` (solution), `coreex-api` / `coreex-relay` / `coreex-subscribe` (hosts), `coreex-domain` (optional DDD layer), `coreex-aspire` (optional local-orchestration AppHost), or `coreex-ai` (AI workflow assets). Validate any change to `src/CoreEx.Template/content/**` with [`tools/validate-template-pack.ps1`](../tools/validate-template-pack.ps1) — see "Build, Test, and Run" above. |
| `CoreEx Expert` | Agent | Architecture guidance, pattern recommendations, and design review. Invoke via `/coreex-expert` (or `@coreex-expert`). |
| `/coreex-scaffold` | Skill-backed prompt | Guided greenfield solution scaffolding (chooses the smallest safe shape, runs the `dotnet new coreex*` commands). |
| `/coreex-docs-sync` | Skill | Refresh the whole AI asset bundle (instructions, skills, prompts, the `coreex-expert` agent, and the `.github/docs/coreex/` doc cache) to a new pinned CoreEx version after a version bump. |
Expand Down
2 changes: 2 additions & 0 deletions .github/instructions/coreex-domain.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ tags: ["domain", "ddd", "aggregates", "entities", "value-objects", "result"]

The Domain layer is **optional**. It is introduced only when a domain contains aggregates with meaningful business rules and invariants that must be enforced at the model level — not in orchestration code. For example, a checkout/basket domain with state-machine transitions and nested item rules benefits from this layer; a simple CRUD-oriented domain (like a product catalog) typically does not.

**Adoption is per-aggregate, not all-or-nothing.** Once a `*.Domain` project exists for a domain, that does not obligate every entity in the domain to become a full `Aggregate<TId, TSelf>`/`Entity<TId, TSelf>` with mutation guards and `PersistenceState` tracking. Only apply the extra ceremony to the entities that benefit from it — those with real invariants, state transitions, or integration events to raise. Other entities in the same domain can remain CRUD-oriented, orchestrated directly by an Application service against a repository. It is normal and expected to mix and match within a single domain.

> **Related skill:** to scaffold a new aggregate root, entity, or value object, invoke the [`coreex-aggregate`](/.github/skills/coreex-aggregate/SKILL.md) skill.
> This file holds the invariants that must hold on **any** edit to a Domain-layer file; the skill drives the
> step-by-step **creation** procedure.
Expand Down
2 changes: 2 additions & 0 deletions .github/instructions/coreex-host-setup.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ tags: ["program-cs", "host-setup", "middleware", "dependency-registration", "cac

The host is a **composition root only** — no business logic. There are three host types in a CoreEx solution depending on the capabilities required. Each follows the same opening skeleton, then diverges based on its responsibilities.

> **Split vs. consolidate:** Api/Relay/Subscribe as separate processes is a workload-isolation convention, not a technical requirement — see [Hosts Layer Guide § Choosing a Host Topology](/.github/docs/coreex/hosts-layer.md#choosing-a-host-topology-split-vs-consolidate) before assuming a small/low-traffic solution needs all three.

> **Related skill:** to scaffold a solution or an additional host (Api / Subscribe / Relay), invoke the [`coreex-solution-scaffolder`](/.github/skills/coreex-solution-scaffolder/SKILL.md) skill.
> This file holds the invariants that must hold on **any** edit to a host `Program.cs`; the skill drives the
> step-by-step **creation** procedure. (The per-host "Scaffolding an … host" blocks below stay here — they carry the
Expand Down
8 changes: 8 additions & 0 deletions .github/instructions/coreex-tooling.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,13 @@ These `.g.sql` / `.g.pgsql` files are generated by DbEx — never edit them dire

Seed data in `Data/ref-data.seed.yaml` is **cross-environment** — it is applied in every environment including production. It should therefore contain only shared **reference data** (lookup tables, code lists) that must exist everywhere. Do not seed master or transactional data here unless it is genuinely required in all environments; test-specific data belongs in the test project's own `data.yaml`, applied only during test setup.

> ⚠️ **`$`/`$^` merge is safe only for reference/lookup-shaped tables — not transactional or master data.** DbEx's generated `MERGE` upserts every non-key column it's given, unconditionally, on every run. That's fine for small `Code`/`Text`/`IsActive`/`SortOrder`-shaped reference tables with no other state to protect. It is **not** safe for transactional or master tables, which typically carry columns a blind upsert would silently corrupt:
> - `IChangeLog` audit columns (`CreatedBy`/`CreatedDate`/`UpdatedBy`/`UpdatedDate`) — a re-run merge overwrites the real audit trail with the seed's values.
> - Concurrency tokens (`RowVersion`/ETag) — merge doesn't participate in optimistic concurrency, so it can silently clobber a row a user has since updated.
> - `IsDeleted` soft-delete flags and FK-heavy business columns — merge has no notion of "this row was intentionally deleted" or referential business rules; it just overwrites.
>
> If a table has any of these shapes, seed it with a plain **unprefixed** INSERT (as `coreex-tests.instructions.md` already does for test seed data) instead of `$`/`$^`, and treat it as environment-specific (test/dev only), not something reused across environments via `ref-data.seed.yaml`.

**Structure** — there is exactly one valid shape, three levels deep:

```
Expand Down Expand Up @@ -593,6 +600,7 @@ products:
- Do not declare the `IsDeleted` column under a table's `columns:` (and there is no `isDeleted` column flag) — it is recognised by convention from the live schema; keep table entries to `- name: Xxx` unless an override is genuinely needed.
- Do not add a per-table `schema:` override for reference data (e.g. a `Ref` schema) — reference and transactional tables both live in the domain's root `schema:` unless a different schema actually exists.
- Do not use the wrong casing in seed data — match the provider (SQL Server PascalCase `Code`/`Text`/`IsActive`/`SortOrder`; PostgreSQL snake_case `code`/`text`/`is_active`/`sort_order`), and do not hand-write `id`/`IsActive`/`SortOrder` rows — prefer the `Code: Text` shorthand.
- Do not use a `$`/`$^` merge prefix on a transactional or master table entry — merge blindly upserts every column it's given, which silently corrupts `IChangeLog` audit columns, concurrency tokens (`RowVersion`/ETag), `IsDeleted` soft-delete flags, and FK-heavy business columns on re-run. Merge prefixes are for small `Code`/`Text`/`IsActive`/`SortOrder`-shaped reference tables only; seed transactional/master data with a plain unprefixed INSERT instead.

## Further Reading

Expand Down
2 changes: 1 addition & 1 deletion .github/prompts/coreex-scaffold.prompt.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: Guide me through choosing and running the right CoreEx.Template dotnet new commands for a new solution
description: Guide me through choosing and running the right CoreEx.Template dotnet new commands for a new solution, including hosts, database/messaging choices, and an optional Aspire AppHost for local orchestration
---

<!--
Expand Down
9 changes: 8 additions & 1 deletion .github/skills/coreex-aggregate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ then follows the corresponding pattern from `CoreEx.DomainDriven`.
- Add mutation methods, factory methods, or invariant guards to an existing domain object
- Decide whether a domain concept needs a Domain layer at all, or should stay CRUD-oriented

> **Mix and match within a domain.** Once a `*.Domain` project exists, adopting it is not all-or-nothing across
> every entity in that domain. Only give an entity the full aggregate/entity/value-object ceremony (mutation
> guards, `PersistenceState`, factory methods) when it genuinely benefits — meaningful invariants, state
> transitions, or integration events. A simple reference/lookup-style entity in the same domain can remain
> CRUD-oriented (Application service → repository directly), even while a sibling aggregate in the same
> `*.Domain` project uses the full pattern.

## When Not to Use

- The domain is CRUD-oriented with no meaningful invariants to protect at the model level — let the
Expand All @@ -39,7 +46,7 @@ then follows the corresponding pattern from `CoreEx.DomainDriven`.
> **Confirm this skill applies before writing any domain object.** It is gated on the presence of the
> `*.Domain` project (`src/*.Domain/`). If it is absent, the domain is CRUD-oriented — stop and use
> `coreex-app-service` against repository interfaces instead. To add the Domain layer, run
> `dotnet new coreex-domain -n Company.Product.Domain` and wire it into the solution first.
> `dotnet new coreex-domain -n Company.Product.Books.Domain` and wire it into the solution first.

## Quick Reference

Expand Down
2 changes: 1 addition & 1 deletion .github/skills/coreex-aggregate/references/workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Before creating anything, confirm a Domain layer is warranted at all:
| Question | Guidance |
|---|---|
| Does this concept have invariants that must be enforced at the model level (state machines, child-collection rules, cross-property constraints)? | If no → skip the Domain layer; let the Application service orchestrate directly against repository interfaces. |
| Does the domain already have a `*.Domain` project? | If no and the answer above is yes, run `dotnet new coreex-domain -n Company.Product.Domain`, wire it into the solution (`dotnet sln Company.Product.Domain.slnx add src/Company.Product.Domain.Domain`), and add the required `Application → Domain` project reference (`dotnet add src/Company.Product.Domain.Application/Company.Product.Domain.Application.csproj reference src/Company.Product.Domain.Domain/Company.Product.Domain.Domain.csproj`) -- without it, Application-layer services and mappers cannot compile against the aggregate. This is a solution-scaffolding concern, not something this skill creates. Confirm with the developer before proceeding. |
| Does the domain already have a `*.Domain` project? | If no and the answer above is yes, run `dotnet new coreex-domain -n Company.Product.Books.Domain`, wire it into the solution (`dotnet sln Company.Product.Books.slnx add src/Company.Product.Books.Domain`), and add the required `Application → Domain` project reference (`dotnet add src/Company.Product.Books.Application/Company.Product.Books.Application.csproj reference src/Company.Product.Books.Domain/Company.Product.Books.Domain.csproj`) -- without it, Application-layer services and mappers cannot compile against the aggregate. This is a solution-scaffolding concern, not something this skill creates. Confirm with the developer before proceeding. |
| Is this a CRUD-oriented entity (simple get/create/update/delete, no business rules beyond validation)? | If yes → skip the Domain layer entirely; use `coreex-contract` + `coreex-repository` + `coreex-app-service` directly. |

Only proceed past this point when a Domain layer is confirmed to exist and be warranted.
Expand Down
Loading
Loading