- Don't code until instructed to. Standard workflow is questions back and forth coming up with what to do. Then the go-ahead to code is given. Questions are not instructions to start coding.
- Test thoroughly: Always run the full test suite before finalizing.
- Performance tests: If they fail, rerun. Repeated failures are NOT acceptable — do not report success.
COMPOSABLE_PERFORMANCE_TESTS_STRESS_TEST_ONLY: Set totrue(the default in CI andC-Test) to run performance tests as stress tests only, disabling timing assertions. Set tofalseto re-enable timing checks.COMPOSABLE_MACHINE_SLOWNESS: Set this environment variable (e.g.,5.0) to adjust performance test timing expectations on slow machines. Only applies when stress-test-only mode is off.
- REFUSE to start work when you lack what you need to succeed. If the target design is unclear, if you don't know what the end state should look like, if the instructions leave a fundamental gap — SAY SO immediately. Do not guess.
- Moving files is not separating concerns. If you would need to add a cross-reference back to make it compile, the dependency hasn't changed — call this out.
- Ask the design question upfront. When structural work requires a design decision you cannot make alone — stop and ask.
- Name what you don't know. "I don't know how X should work after this change" is always preferable to silently preserving the old architecture and reporting success.
- Never hide behind "existing architecture." Existing entanglement is the problem to be solved, not a constraint.
Compze is a .NET framework for building expressive domains through:
- Teventive programming: Type-routed events (also called "Semantic Events") that leverage .NET type compatibility for elegant event modeling
- Typermedia APIs: Type-based message routing that extends hypermedia principles with .NET types
- Language: C# (.NET 10, see
src/global.json) - Testing: xUnit v3 (via
Compze.xUnit,Compze.xUnitBDD,Compze.xUnitMatrix) - Build System: MSBuild (.NET SDK), solution file:
src/Compze.AllProjects.slnx - References: FlexRef — auto-switches between
ProjectReferenceandPackageReferencedepending on which projects are in the current solution - Dependency Injection: Pluggable (Microsoft DI, Autofac)
- Persistence: Pluggable (SQLite in-memory, SQL Server, PostgreSQL, MySQL)
- Serialization: Pluggable (Newtonsoft)
- Transport: Pluggable (AspNetCore)
- Documentation: DocFX (site in
src/Websites/Website/) - Development Tools: PowerShell module (
DevScripts/Compze.psm1)
- .NET SDK (version specified in
src/global.json) - PowerShell with DevScripts module imported:
Import-Module <repo>/DevScripts/Compze.psm1 -DisableNameChecking
C-Build # Build the solution (preferred)
C-Build -Clean # Deep clean then build
dotnet build src/Compze.AllProjects.slnx # Alternative: direct .NET CLI# DevScripts (preferred) — C-Test BUILDS BY DEFAULT
C-Test # Build + run all tests
C-Test -NoBuild # Skip building, just run tests
C-Test -SingleThreadedTesting # Sequential execution (for debugging)
C-Test -Iterations 5 # Run suite 5 times, show summary
C-Test -Clean # Deep clean + build + test
C-Test -FullGitReset # Full git clean + build + test
# Running a subset of tests (no DevScripts support, use dotnet directly)
dotnet test src/Compze.AllProjects.slnx --no-build --filter "FullyQualifiedName~MyTestClass"- Config file:
src/TestUsingPluggableComponentCombinations(auto-created from.defaultson first build) - Format:
PersistenceLayer:DIContainer:Serializer:Transport(one combination per line,#to comment out) - Default active combination:
SqliteMemory:Microsoft:Newtonsoft:AspNetCore
Flat layout — each project has its own top-level directory:
- Library projects:
src/<ProjectName>/<ProjectName>.csproj - Test projects:
test/<ProjectName>/<ProjectName>.csproj - Multiple
.slnxfiles exist for different subsets;Compze.AllProjects.slnxis the monolith
.Specifications— BDD-style specification projects (preferred for new projects).Tests.— older integration/unit test projects
- Don't write one test per pluggable component — use
[PCT](see Pluggable Component Testing below). - DevScripts must be imported before using
C-*commands. Import with:Import-Module <repo>/DevScripts/Compze.psm1 -DisableNameChecking - New package versions: When creating a new packable project, set
<Version>to an early pre-release version (e.g.,0.1.0-alpha.1). NEVER use1.0.0or any stable-looking version for something new and in development.
The sections below are ported from scoped Copilot instruction files. Apply the relevant section based on what files you are working with.
- When implementing new functionality, if a missing abstraction makes the implementation inconsistent — introduce that abstraction. Refactoring existing code to better accommodate new changes is expected.
- Do not bolt new behavior onto an ill-fitting structure just to avoid creating classes. If the right design calls for a new class, record, interface, or helper — create it.
- Behavior belongs with data. If a static method's first parameter is an object it primarily operates on, that method should be an instance method on that type. Prefer
object.DoSomething(...)overStaticHelper.DoSomething(object, ...). Static utility classes are for genuinely cross-cutting operations, and those should usually be extension methods. - Logic belongs where it fits, not where it's first needed. Move methods to the type they operate on.
- Indentation: 3 spaces.
- File-scoped namespaces: Always
namespace Foo.Bar;— never block-scoped. - Namespace = folder path: The namespace must match the project name + subfolder structure.
- Braces: Allman style for type and member declarations. No space between keyword and parenthesis in control flow:
if(condition),foreach(var x in items),while(true).
vareverywhere: Usevarfor all local variables — even for built-in types.- Expression bodies (
=>): Prefer for single-expression methods, properties, constructors, and operators. - Primary constructors: Use when appropriate but create explicit fields for the arguments. Do NOT use primary constructor argument capturing.
- Access modifiers: Omit default access modifiers. No explicit
privateon fields or methods; no explicitinternalon classes. Only write modifiers that change the default. readonly: Use on fields for immutable state.
| Element | Convention |
|---|---|
| Classes | PascalCase |
| Interfaces | I prefix: IEndpoint, ITessage |
| Methods | PascalCase |
| Fields | _camelCase (no explicit private) |
| Properties | PascalCase |
| Constants | PascalCase |
| Enums/values | PascalCase |
| Generic params | T prefix: TEntity, TKey |
| Extension classes | {TypeName}CE suffix: StringCE, EnumCE, TimeSpanCE |
| Extension 1st param | @this: this string @this |
| Functional utils | Lowercase for "language-like" helpers: caf(), then(), mutate(), tap() |
- Make names however long they need to be. The most common problem is that trying to keep names short means they do NOT describe what they do. If you need a comment to explain what a method or variable does, the name should be improved instead.
- Place at file top, outside namespace.
- Prefer
using static Compze.Contracts.Assert;to callArgument.NotNull(),State.Is()etc. without theAssert.prefix.
- Nullable reference types enabled in all projects.
- Use Compze.Contracts:
Assert.Argument.NotNull(value),Assert.State.Is(condition),Assert.Result.NotNull(result),Assert.Invariant.Is(condition) - Use
.NotNull()extension for quick null-dereferencing.
- Use extension blocks, not the old
@thissyntax:extension(ContractAsserter @this) { public ContractAsserter NotNull<T>([NotNull] T? value, ...) { ... @this.ThrowNull(...); ... } }
- Do not use
recordorrecord struct. If value equality is genuinely needed, implementIEquatable<T>explicitly.
- This codebase uses default interface methods and extension methods extensively as a mixin pattern. Always check interfaces for default method implementations AND extension methods before assuming a method doesn't exist.
- Use collection expression syntax
[]for initialization:List<Task> tasks = [];. - Use immutable types for exposed data:
IReadOnlyList<T>,IReadOnlyDictionary<K,V>,IReadOnlySet<T> - Prefer LINQ method syntax when not cumbersome.
- Target-typed
new()for well-known types.
.caf()instead of.ConfigureAwait(false)— apply to everyawaitin library code.
- String interpolation
$"..."everywhere — no concatenation. - Raw string literals
$"""..."""for multi-line strings. nameof()in exception messages.
- CRITICAL: Never swallow exceptions in a catch block without rethrowing.
- Prefer
Assert.*from Compze.Contracts over manual if/throw.
- Prefer self-documenting code over comments — extract well-named methods instead.
- XML doc comments: be brief and concise. Omit on obvious/internal code.
- Generally one primary type per file; nested classes and related small types in same file are fine.
- Partial classes split across files using
ClassName.Aspect.csnaming. - Underscore-prefix filenames (
_TessageTypes..Interfaces.cs) for grouped/supporting types.
- Multiple or complex constraints on separate lines, indented 3 spaces.
All new specifications should use BDD-style nested specification classes using [XF] (ExclusiveFact).
When adding specs to an existing flat-style class:
- If easy, refactor existing specs to BDD-style, then add new ones.
- If complex, create a new BDD-style class instead.
- Do NOT add new specs to old flat structure.
- No mocking frameworks — use real implementations with the real DI container.
- No direct constructor calls to domain services — resolve through the container.
- Never make constructors, fields, or methods public just so tests can access them.
- Don't duplicate initialization details in tests.
Reading only the full specification names — from namespace through nested classes to method name — must be enough to correctly implement the specified behavior. No one should need to read the test code.
Example spec lines:
Specifications.Contracts.AssertionMethods.NotNull.Throws_when_argument_is.null_stringSpecifications.UserAccounts.Registration.When_a_user_attempts_to_register.with_valid_data.registration_succeeds
- Nested classes inherit from parent; each level's constructor performs that level's setup.
- Folders/namespaces = categorization. Nested classes = accumulated context.
- If a nesting level has no constructor body, it's just categorization — use a folder/namespace instead.
- Use
[XF], never[Fact]—[Fact]causes inherited specs to re-run in every descendant. - Class names describe context:
with_invalid_email,that_is_empty - Method names describe expected behavior:
registration_is_rejected - Each level's constructor is the "act"
- Specs at each level are only assertions — single-expression
=>bodies callingMust().
value.Must().Be(expected);
value.Must().NotBeNull();
collection.Must().HaveCount(5);
Invoking(() => action()).Must().Throw<SomeException>();
await InvokingAsync(async () => await asyncAction()).Must().ThrowAsync<SomeException>();- Set up state in the constructor — not in
[SetUp]or separate methods.
- Return
Taskorasync Task. - No
.ConfigureAwait(false)/.caf()in specification code.
| Attribute | Purpose |
|---|---|
[PCT] |
Pluggable Component Theory — runs for every configured component combination |
[PCTSerializer] |
Varies only the Serializer component |
[PCTDIContainer] |
Varies only the DIContainer component |
[Performance] |
Marks performance tests |
[LongRunning] |
Marks long-running tests |
- Never write one test per pluggable component. Use
[PCT]+UniversalTestBase+TestEnv. - Inherit from
UniversalTestBasefor lifecycle management. - Override
DisposeInternal(),InitializeAsyncInternal(),DisposeAsyncInternal()instead of implementingIDisposable/IAsyncLifetimedirectly.
- Create a
TestingEndpointHost, register endpoints, start/stop in lifecycle methods. - Use
IServiceLocatorfor resolving services. - Use
IThreadGatefor controlling concurrency timing.
- Use
TimeAsserter.Execute(action, iterations: N, maxTotal: duration). - Use
EnvDivide()/EnvMultiply()on thresholds for slow machines. - Mark performance test projects with
[assembly: PerformanceAttribute]. - Test parallelization is disabled in performance test projects.
FlexRef auto-switches between ProjectReference and PackageReference depending on which projects are in the current solution. This lets the repo maintain multiple .slnx solutions.
- Never hand-edit flex reference sections in csproj files — they are generated by
flexref sync. - Never hand-edit the FlexRef section in
Directory.Build.props— generated byflexref sync. - Always run
C-FlexRef-Syncafter structural changes — adding/renaming/removing projects or changing references. - Focused solutions require
C-Packfirst — thePackageReferencepath needs packages innupkgs/. - The monolithic solution (
Compze.AllProjects.slnx) needs no packages — all references resolve asProjectReference.
Import: Import-Module <repo>/DevScripts/Compze.psm1 -DisableNameChecking
Discover all commands: C-Get-Commands or C-<Tab> in PowerShell.
Do not write output for each step. Success is silent. Only output if the function's purpose is to provide information.
| Command | Purpose |
|---|---|
C-Test |
Build + run full test suite |
C-Build |
Build the solution |
C-Clean |
Deep clean the solution |
C-Create-Project |
Create new projects with proper structure |
C-Delete-Project |
Delete a project |
C-Rename-Project |
Rename a project |
C-Split-Project |
Split a project into multiple |
C-Merge-Project |
Merge projects |
C-FlexRef-Sync |
Sync FlexRef infrastructure after reference changes |
C-Validate-SolutionStructure |
Validate solution structure |
- Use
x:DataType="vm:XxxViewModel"on root element of Windows/Dialogs with a ViewModel. - UserControls may omit
x:DataType— they receive DataContext from parent.
- Never set
DataContextin AXAML — always in code-behind constructor. - Use
Design.DataContextseparately for the AXAML previewer.
- Prefer command bindings (
{Binding MyCommand}) for all user interactions. - Use event handlers only when commands are impractical.
DockPanelfor top-level,StackPanel/Gridfor sections.- All styling is inline — no external stylesheet files.
- Be concise. State conclusions, not the reasoning journey.
- No historical baggage. Rewrite to reflect current state, don't append corrections.
- No redundancy. Don't explain the obvious.
- When editing, check if the document has grown bloated. Tighten it.
jb inspectcode src/Compze.AllProjects.slnx `
--output=inspection-results.sarif `
--severity=SUGGESTION `
--include="**/*.cs" `
--exclude="**/_docs/**"- Group issues by file. Read each file once, apply all fixes, move on.
- Build after each batch (~10-20 files). Catching errors early avoids cascading confusion.
- Run the full test suite once at the end after the build is clean.
- Do NOT blindly apply changes at line numbers — as you edit files, line numbers shift. Always read the file to find the actual code.
- Override methods: all overrides must also become
internal. - Serialized properties: Properties serialized by Newtonsoft must have
publicgetter. Making itinternalcauses silent data loss. - Solution completeness: Analysis is only correct if the solution contains all consumer projects.
- Events use interface inheritance for type-based routing.
- Example:
IUserImported : IUserRegistered : IUserEvent : IAggregateEvent - Subscribers receive events they're compatible with through type hierarchy.
- Documentation lives in
_docs/folders next to the code it documents. - See
src/Documentation-CoLocation.README.mdfor details.