From f240c7abff6b383d555aa4217fb263db034cbfb8 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 3 Apr 2026 21:03:03 +0200 Subject: [PATCH 1/8] Add /debug-module slash command for module diagnostic workflow --- .claude/commands/debug-module.md | 127 +++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 .claude/commands/debug-module.md diff --git a/.claude/commands/debug-module.md b/.claude/commands/debug-module.md new file mode 100644 index 00000000..7102192e --- /dev/null +++ b/.claude/commands/debug-module.md @@ -0,0 +1,127 @@ +Diagnose why a module isn't being discovered or working correctly in SimpleModule. + +If no module name was supplied as an argument, ask: "Which module would you like to debug? (e.g. Products, Orders)" + +Work through all 7 checks below **in order**. For each check, mark it ✅ pass / ❌ fail / ⚠️ warning, then apply the described fix before moving to the next check. Do not skip checks even if earlier ones fail. + +Replace `{Name}` with the module name throughout. + +--- + +## Check 1 — Module class + +Read `modules/{Name}/src/SimpleModule.{Name}/{Name}Module.cs`. + +Verify: +- The file exists. +- The class is decorated with `[Module(...)]`. +- The class implements `IModule`. +- The class is `public`. +- The class is **not** `sealed`. + +Fix if failing: Create or correct the file so the class is `public`, non-sealed, carries `[Module(...)]`, and implements `IModule`. + +--- + +## Check 2 — Contracts SDK + +Read `modules/{Name}/src/SimpleModule.{Name}.Contracts/SimpleModule.{Name}.Contracts.csproj`. + +Verify: +- The file exists. +- `Sdk="Microsoft.NET.Sdk"` (must NOT be `Microsoft.NET.Sdk.Razor` or `Microsoft.NET.Sdk.Web`). + +Fix if failing: Set the `Sdk` attribute to `Microsoft.NET.Sdk`. + +--- + +## Check 3 — Implementation SDK + FrameworkReference + +Read `modules/{Name}/src/SimpleModule.{Name}/SimpleModule.{Name}.csproj`. + +Verify: +- The file exists. +- `Sdk="Microsoft.NET.Sdk.Razor"`. +- Contains ``. + +Fix if failing: Set `Sdk="Microsoft.NET.Sdk.Razor"` and add the `` element inside an `` if it is missing. + +--- + +## Check 4 — Host reference + +Read `template/SimpleModule.Host/SimpleModule.Host.csproj`. + +Verify: +- A `` to `..\..\..\..\modules\{Name}\src\SimpleModule.{Name}\SimpleModule.{Name}.csproj` (or equivalent relative path) exists. + +Fix if failing: Add the `` inside an `` in the host `.csproj`. + +--- + +## Check 5 — Solution file + +Read `SimpleModule.slnx`. + +Verify: +- The contracts `.csproj` path appears: `modules/{Name}/src/SimpleModule.{Name}.Contracts/SimpleModule.{Name}.Contracts.csproj` +- The implementation `.csproj` path appears: `modules/{Name}/src/SimpleModule.{Name}/SimpleModule.{Name}.csproj` + +Fix if failing: Add a `` block with `` entries for both paths. + +--- + +## Check 6 — dotnet build diagnostics + +Run: +``` +dotnet build --no-incremental 2>&1 | grep -E "SM0|error" | head -50 +``` + +Surface any SM00xx source generator diagnostic codes. Their meanings: + +| Code | Meaning | +|------|---------| +| SM0001 | Module class must be public and non-sealed | +| SM0010 | IEndpoint implementation must be internal sealed | +| SM0020 | IViewEndpoint implementation must be internal sealed | +| SM0030 | [Dto] types must live in a Contracts assembly | +| SM0040 | No impl→impl project references allowed between modules | +| SM0044 | Inertia.Render component name not found in Pages/index.ts | + +Fix each reported code using the guidance above before continuing. + +--- + +## Check 7 — Page registry + +Run: +``` +npm run validate-pages +``` + +If it exits with an error, identify each missing entry and show the exact line to add to `modules/{Name}/src/SimpleModule.{Name}/Pages/index.ts`: + +```typescript +" \"{Name}/{ViewName}\": () => import(\"../Views/{ViewName}\")," +``` + +Add the missing entries to the `pages` record in `Pages/index.ts`, then re-run `npm run validate-pages` to confirm it passes. + +--- + +## Final Summary + +Print a summary table of all 7 checks: + +| Check | Status | Notes | +|-------|--------|-------| +| 1. Module class | ✅/❌/⚠️ | | +| 2. Contracts SDK | ✅/❌/⚠️ | | +| 3. Implementation SDK + FrameworkReference | ✅/❌/⚠️ | | +| 4. Host reference | ✅/❌/⚠️ | | +| 5. Solution file | ✅/❌/⚠️ | | +| 6. dotnet build diagnostics | ✅/❌/⚠️ | | +| 7. Page registry | ✅/❌/⚠️ | | + +For every ❌ row, include specific fix instructions. If all checks pass, confirm the module should be discovered correctly on next build. From 5810401bf7f91af7342e51f58973a9c54c7dc24a Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 3 Apr 2026 21:07:45 +0200 Subject: [PATCH 2/8] Fix debug-module command: correct SDK, path depth, slnx structure, TS snippet format --- .claude/commands/debug-module.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.claude/commands/debug-module.md b/.claude/commands/debug-module.md index 7102192e..b5693023 100644 --- a/.claude/commands/debug-module.md +++ b/.claude/commands/debug-module.md @@ -4,6 +4,8 @@ If no module name was supplied as an argument, ask: "Which module would you like Work through all 7 checks below **in order**. For each check, mark it ✅ pass / ❌ fail / ⚠️ warning, then apply the described fix before moving to the next check. Do not skip checks even if earlier ones fail. +**Status marker meanings:** ✅ = passes all requirements · ❌ = fails, fix required · ⚠️ = exists and will work, but has a non-critical configuration difference (e.g., SM00xx warnings but no errors, or a file exists but a setting differs from the expected default). + Replace `{Name}` with the module name throughout. --- @@ -41,10 +43,10 @@ Read `modules/{Name}/src/SimpleModule.{Name}/SimpleModule.{Name}.csproj`. Verify: - The file exists. -- `Sdk="Microsoft.NET.Sdk.Razor"`. +- `Sdk="Microsoft.NET.Sdk"` (must NOT be `Microsoft.NET.Sdk.Razor` or `Microsoft.NET.Sdk.Web`). - Contains ``. -Fix if failing: Set `Sdk="Microsoft.NET.Sdk.Razor"` and add the `` element inside an `` if it is missing. +Fix if failing: Set `Sdk="Microsoft.NET.Sdk"` if it is wrong. If `` is missing, add it inside an ``. --- @@ -53,7 +55,7 @@ Fix if failing: Set `Sdk="Microsoft.NET.Sdk.Razor"` and add the `` to `..\..\..\..\modules\{Name}\src\SimpleModule.{Name}\SimpleModule.{Name}.csproj` (or equivalent relative path) exists. +- A `` whose path contains `modules\{Name}\src\SimpleModule.{Name}\SimpleModule.{Name}.csproj` (use a substring match to tolerate slash style differences). The correct relative path from the host project is `..\..\modules\{Name}\src\SimpleModule.{Name}\SimpleModule.{Name}.csproj`. Fix if failing: Add the `` inside an `` in the host `.csproj`. @@ -67,7 +69,7 @@ Verify: - The contracts `.csproj` path appears: `modules/{Name}/src/SimpleModule.{Name}.Contracts/SimpleModule.{Name}.Contracts.csproj` - The implementation `.csproj` path appears: `modules/{Name}/src/SimpleModule.{Name}/SimpleModule.{Name}.csproj` -Fix if failing: Add a `` block with `` entries for both paths. +Fix if failing: Locate the existing `` element in `SimpleModule.slnx` and add a new child `` block inside it with `` entries for both the contracts and implementation `.csproj` files. --- @@ -103,9 +105,11 @@ npm run validate-pages If it exits with an error, identify each missing entry and show the exact line to add to `modules/{Name}/src/SimpleModule.{Name}/Pages/index.ts`: ```typescript -" \"{Name}/{ViewName}\": () => import(\"../Views/{ViewName}\")," +'{Name}/{ViewName}': () => import('../Views/{ViewName}'), ``` +Derive `{ViewName}` from the component name reported missing by `npm run validate-pages` (e.g., if it reports `Products/Browse`, the ViewName is `Browse`). + Add the missing entries to the `pages` record in `Pages/index.ts`, then re-run `npm run validate-pages` to confirm it passes. --- From 949b70010f2ebe0d42ff4e04fdb64a803914c8e5 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 3 Apr 2026 21:14:50 +0200 Subject: [PATCH 3/8] Add /review-module slash command for convention review workflow --- .claude/commands/review-module.md | 128 ++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 .claude/commands/review-module.md diff --git a/.claude/commands/review-module.md b/.claude/commands/review-module.md new file mode 100644 index 00000000..7529a69b --- /dev/null +++ b/.claude/commands/review-module.md @@ -0,0 +1,128 @@ +Convention review for a SimpleModule module. Checks the module against the Constitution and project conventions. + +If no module name was supplied as an argument, ask: "Which module would you like to review? (e.g. Products, Orders)" + +Work through all 7 areas below in order. Do **not** auto-fix violations — record them and report at the end. For each violation found, record: **file path**, **approximate line or pattern**, **what's wrong**, **how to fix it**. + +Replace `{Name}` with the module name throughout. + +--- + +## Area 1 — Architecture: No impl→impl dependencies + +Read `modules/{Name}/src/SimpleModule.{Name}/SimpleModule.{Name}.csproj`. + +For every `` path in that file, check whether the path points into another module's implementation assembly. A violation is a path that: +- Contains `modules/` (or `modules\`) +- Does NOT end with `.Contracts/SimpleModule.*.Contracts.csproj` (or the backslash equivalent) + +References to `.Contracts` projects are fine. References to the host or shared infrastructure are fine. + +Violation fix: Replace the implementation reference with a reference to the other module's `.Contracts` project. Inject `I{OtherModule}Contracts` via constructor rather than using the concrete class directly. + +--- + +## Area 2 — Endpoints: CrudEndpoints helpers and TypedResults + +Grep for `MapGet|MapPost|MapPut|MapDelete` in `modules/{Name}/src/SimpleModule.{Name}/Endpoints/`. + +Check each match: + +1. **CrudEndpoints helpers** — Standard CRUD operations should use `CrudEndpoints.GetAll`, `CrudEndpoints.GetById`, `CrudEndpoints.Create`, `CrudEndpoints.Update`, or `CrudEndpoints.Delete` rather than raw inline `Results.*` returns for the same operation. Flag any endpoint that manually reimplements what a CrudEndpoints helper already provides. + +2. **TypedResults** — API endpoints must use `TypedResults.*` (e.g., `TypedResults.Ok(...)`, `TypedResults.NotFound()`) not the non-generic `Results.*` static methods. Flag any use of `Results.Ok`, `Results.NotFound`, etc. + +3. **RequirePermission** — Endpoints must use `.RequirePermission(...)` not bare `.RequireAuthorization()`. Flag any `.RequireAuthorization()` call without a permission argument. + +--- + +## Area 3 — Naming conventions + +Grep for class definitions (`class `) in `modules/{Name}/src/SimpleModule.{Name}/Endpoints/` and `modules/{Name}/src/SimpleModule.{Name}/Views/`. + +For each source file found: + +1. **One class per file** — if a file contains more than one `class` definition, flag it. +2. **Class name matches file name** — e.g., `CreateEndpoint.cs` must contain `class CreateEndpoint`. Flag mismatches. +3. **internal sealed** — endpoint and view classes must be declared `internal sealed`. Flag any that are `public`, `private`, missing `sealed`, or missing `internal`. +4. **File-scoped namespaces** — check for `namespace Foo {` (brace-style). Must be `namespace Foo;` (file-scoped). Flag any brace-style namespace declarations. +5. **Private field naming** — grep for `private` field declarations. Fields must use `_camelCase` prefix. Flag any private field that does not start with `_`. + +--- + +## Area 4 — Frontend: Page registry completeness + +Grep for `Inertia.Render(` in `modules/{Name}/src/SimpleModule.{Name}/`. Extract the first string argument from each call — this is the component key (e.g., `"Products/Browse"`). + +Read `modules/{Name}/src/SimpleModule.{Name}/Pages/index.ts`. Extract all keys defined in the `pages` record. + +Compare the two sets: every component key from an `Inertia.Render` call must appear as a key in `pages`. Flag any that are missing. + +Then run `npm run validate-pages` for authoritative output and report any additional mismatches it finds. + +Violation fix: Add a matching entry to `Pages/index.ts`: +```typescript +"{Name}/{ViewName}": () => import("../Views/{ViewName}"), +``` + +--- + +## Area 5 — Events: Proper definition and registration + +Grep for `: IEvent` in `modules/{Name}/src/SimpleModule.{Name}.Contracts/`. + +For each event type found: + +1. **Record type** — events must be `record` types, not `class`. Flag any `class` that implements `IEvent`. + +Grep for `IEventHandler<` in `modules/{Name}/src/SimpleModule.{Name}/`. + +For each handler found, check that a corresponding `services.AddScoped, THandler>()` call exists in `ConfigureServices` (or wherever DI is configured in the module class). Flag any handler with no registration. + +Violation fix for missing registration: Add `services.AddScoped, {HandlerType}>();` in `ConfigureServices`. + +--- + +## Area 6 — Tests: Coverage by endpoint file + +List all files matching `*Endpoint.cs` in: +- `modules/{Name}/src/SimpleModule.{Name}/Endpoints/` +- `modules/{Name}/src/SimpleModule.{Name}/Views/` + +List all test files in `modules/{Name}/tests/SimpleModule.{Name}.Tests/`. + +For each endpoint class, check whether a corresponding test file exists (match by class name, e.g., `CreateEndpoint.cs` → `CreateEndpointTests.cs` or `CreateTests.cs`). Flag endpoint classes with no matching test file as missing coverage. + +Note: integration tests using `SimpleModuleWebApplicationFactory` with `CreateAuthenticatedClient` are strongly preferred over unit tests that mock HTTP context. + +--- + +## Area 7 — Permissions: Sealed class with const strings + +Grep for `IModulePermissions` in `modules/{Name}/src/SimpleModule.{Name}.Contracts/`. + +For the permissions class found: + +1. **Sealed** — the class must be `sealed`. Flag if not. +2. **Naming pattern** — all `const string` values must follow the `"Module.Action"` format (e.g., `"Products.Create"`, `"Products.Delete"`). Flag any permission string that does not match this pattern. + +Grep for `AddPermissions<` or `builder.AddPermissions` in `modules/{Name}/src/SimpleModule.{Name}/{Name}Module.cs`. + +3. **ConfigurePermissions registration** — `ConfigurePermissions` must call `builder.AddPermissions<{Name}Permissions>()`. Flag if the call is absent or uses the wrong type. + +--- + +## Final Report + +After completing all 7 areas, print a grouped report: + +- If no violations in an area: `✅ [Area Name] — OK` +- If violations exist: `❌ [Area Name]` followed by a bulleted list of violations, each with: + - File path (absolute or relative from repo root) + - Approximate line or pattern where the problem appears + - What is wrong + - Specific fix + +End the report with one of: +- `✅ {Name} module passes convention review.` (if zero violations found) +- `Found N violations across N areas.` (if any violations found) From 2b05139fa193e8f812d09f4b7e95392c77263834 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 3 Apr 2026 21:20:58 +0200 Subject: [PATCH 4/8] Fix review-module command: correct endpoint visibility, permissions grep path, AddPermissions conditionality --- .claude/commands/review-module.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.claude/commands/review-module.md b/.claude/commands/review-module.md index 7529a69b..16280f92 100644 --- a/.claude/commands/review-module.md +++ b/.claude/commands/review-module.md @@ -44,7 +44,7 @@ For each source file found: 1. **One class per file** — if a file contains more than one `class` definition, flag it. 2. **Class name matches file name** — e.g., `CreateEndpoint.cs` must contain `class CreateEndpoint`. Flag mismatches. -3. **internal sealed** — endpoint and view classes must be declared `internal sealed`. Flag any that are `public`, `private`, missing `sealed`, or missing `internal`. +3. **public class** — endpoint and view classes must be declared `public class`. Flag any that are `internal`, `private`, or `sealed`. 4. **File-scoped namespaces** — check for `namespace Foo {` (brace-style). Must be `namespace Foo;` (file-scoped). Flag any brace-style namespace declarations. 5. **Private field naming** — grep for `private` field declarations. Fields must use `_camelCase` prefix. Flag any private field that does not start with `_`. @@ -99,16 +99,19 @@ Note: integration tests using `SimpleModuleWebApplicationFactory` with `CreateAu ## Area 7 — Permissions: Sealed class with const strings -Grep for `IModulePermissions` in `modules/{Name}/src/SimpleModule.{Name}.Contracts/`. +Grep for `IModulePermissions` in `modules/{Name}/src/` (covers both the implementation assembly and the `.Contracts` assembly). For the permissions class found: 1. **Sealed** — the class must be `sealed`. Flag if not. 2. **Naming pattern** — all `const string` values must follow the `"Module.Action"` format (e.g., `"Products.Create"`, `"Products.Delete"`). Flag any permission string that does not match this pattern. -Grep for `AddPermissions<` or `builder.AddPermissions` in `modules/{Name}/src/SimpleModule.{Name}/{Name}Module.cs`. +If a permissions class implementing `IModulePermissions` was found above, grep for `AddPermissions<` or `builder.AddPermissions` in `modules/{Name}/src/SimpleModule.{Name}/{Name}Module.cs`. -3. **ConfigurePermissions registration** — `ConfigurePermissions` must call `builder.AddPermissions<{Name}Permissions>()`. Flag if the call is absent or uses the wrong type. +3. **ConfigurePermissions registration** — This check is conditional: + - If a permissions class implementing `IModulePermissions` exists **and** `builder.AddPermissions<{Name}Permissions>()` is absent from `{Name}Module.cs`: flag it as a violation. + - If no permissions class exists and no `AddPermissions` call exists: mark Area 7 as N/A. + - If both exist and the type matches: OK. --- From 194cba338f2e43c2b455e401e0880dde17bdfcea Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 3 Apr 2026 21:22:44 +0200 Subject: [PATCH 5/8] Add /module-status slash command for module health snapshot --- .claude/commands/module-status.md | 129 ++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 .claude/commands/module-status.md diff --git a/.claude/commands/module-status.md b/.claude/commands/module-status.md new file mode 100644 index 00000000..111a03e1 --- /dev/null +++ b/.claude/commands/module-status.md @@ -0,0 +1,129 @@ +# /module-status + +Print a health snapshot of a SimpleModule module. No builds required — reads and greps only. + +## Step 0 — Resolve Module Name + +If no argument was supplied, ask: "Which module would you like to inspect? (e.g., Products, Orders, Users)" + +Use the provided name as `{Name}` throughout. Derive paths: +- `IMPL = modules/{Name}/src/SimpleModule.{Name}` +- `CONTRACTS = modules/{Name}/src/SimpleModule.{Name}.Contracts` +- `TESTS = modules/{Name}/tests/SimpleModule.{Name}.Tests` + +--- + +## Step 1 — Inventory + +**API Endpoints** +Use Glob with pattern `modules/{Name}/src/SimpleModule.{Name}/Endpoints/**/*Endpoint.cs`. Count the results and collect the bare file names (strip path and `.cs`). + +**View Endpoints** +Use Glob with pattern `modules/{Name}/src/SimpleModule.{Name}/Views/**/*Endpoint.cs`. Count and collect bare names. Note whether any view endpoints exist — this drives Sections 2 and 5. + +**Entities** +Check whether `modules/{Name}/src/SimpleModule.{Name}/EntityConfigurations/` exists. If it does, grep for `HasKey(` in that directory (all `.cs` files). For each matching file, derive the entity name by stripping `Configuration` from the class name (e.g., `ProductConfiguration` → `Product`). If the directory does not exist, note "N/A — no EntityConfigurations directory". + +**Domain Events** +Grep for `: IEvent` in `modules/{Name}/src/SimpleModule.{Name}.Contracts/` (all `.cs` files). Extract the `record` name from each match. List them; if none, show `0`. + +**Services** +Grep for `class.*I{Name}Contracts` in `modules/{Name}/src/SimpleModule.{Name}/` (all `.cs` files). List each class name found. + +--- + +## Step 2 — Page Registry Coverage + +Skip this section entirely (output "N/A — no view endpoints") if no `*Endpoint.cs` files exist in the Views/ directory. + +Otherwise: + +1. Grep for `Inertia.Render(` in `modules/{Name}/src/SimpleModule.{Name}/` (all `.cs` files). For each match, extract the first string argument — the component name — from the call (e.g., `Inertia.Render("Products/Browse", ...)` → `Products/Browse`). Collect these as **C# renders**. + +2. Read `modules/{Name}/src/SimpleModule.{Name}/Pages/index.ts`. Extract every key from the `pages` record (quoted strings before the `:` on each entry line). Collect these as **TS entries**. + +3. Compute: + - **Missing**: keys present in C# renders but absent from TS entries. + - **Orphaned**: keys present in TS entries but absent from C# renders. + - **Coverage**: `(count of C# renders with a matching TS entry) / (total C# renders)` shown as `N/N (X%)`. + +--- + +## Step 3 — Test File Coverage + +1. Collect all endpoint class names from both Endpoints/ and Views/ (same lists from Step 1). +2. Use Glob `modules/{Name}/tests/SimpleModule.{Name}.Tests/**/*.cs` to list all test files. +3. For each endpoint class name (e.g., `GetAllEndpoint`), check whether that string appears in any test file name or — if needed — grep for it across the test directory. +4. Report covered count / total. List any endpoint classes with no test file found. + +--- + +## Step 4 — Cross-Module Dependencies + +**Consumes** +Read `modules/{Name}/src/SimpleModule.{Name}/SimpleModule.{Name}.csproj`. List every `` `Include` path that points to another module's `.Contracts` project (i.e., paths containing `Contracts` but not `SimpleModule.{Name}.Contracts` itself). Extract just the project name. + +**Consumed by** +Grep for `SimpleModule.{Name}.Contracts` in all `.csproj` files under `modules/`, excluding the module's own projects (`modules/{Name}/`). For each match, extract the module name from the file path. + +--- + +## Step 5 — Required Files + +For each entry, check whether the file or directory exists and assign a status symbol. + +| File | Required | Status | +|------|----------|--------| +| `{Name}Constants.cs` in Contracts | Yes | ✅ if exists, ❌ if not | +| `I{Name}Contracts.cs` in Contracts | Yes | ✅ if exists, ❌ if not | +| `{Name}Module.cs` in IMPL | Yes | ✅ if exists, ❌ if not | +| `Pages/index.ts` in IMPL | If view endpoints exist | ✅/❌ or N/A | +| `vite.config.ts` in IMPL | If view endpoints exist | ✅/❌ or N/A | +| `package.json` in IMPL | If view endpoints exist | ✅/❌ or N/A | +| `tests/` directory (`TESTS`) | Recommended | ✅ if exists, ⚠️ if not | + +Mark Pages/index.ts, vite.config.ts, and package.json as `N/A` when no view endpoints exist. + +--- + +## Output Format + +Print exactly this structure (fill in real values): + +``` +## Module Status: {Name} + +### Inventory +- API Endpoints: N — [Name1, Name2, ...] +- View Endpoints: N — [Name1, Name2, ...] +- Entities: N — [Name1, Name2, ...] (or "N/A — no EntityConfigurations directory") +- Domain Events: N — [Name1, Name2, ...] (or "0") +- Services: N — [ClassName, ...] + +### Page Registry +- Coverage: N/N (X%) +- Missing (C# → no TS entry): [list] or "none" +- Orphaned (TS entry → no C# render): [list] or "none" +(or "N/A — no view endpoints") + +### Test Coverage +- Covered: N/N endpoint files +- Missing tests for: [EndpointClass1, ...] or "all covered" + +### Dependencies +- Consumes: [Module1.Contracts, ...] or "none" +- Consumed by: [Module2, ...] or "none" + +### Required Files +| File | Required | Status | +|------|----------|--------| +| {Name}Constants.cs | Yes | ✅/❌ | +| I{Name}Contracts.cs | Yes | ✅/❌ | +| {Name}Module.cs | Yes | ✅/❌ | +| Pages/index.ts | If view endpoints | ✅/❌/N/A | +| vite.config.ts | If view endpoints | ✅/❌/N/A | +| package.json | If view endpoints | ✅/❌/N/A | +| tests/ directory | Recommended | ✅/⚠️ | +``` + +Do not include any additional commentary outside these sections. From 8a18957f9d1bca636eaf586161285cb68c399e1b Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 3 Apr 2026 21:26:26 +0200 Subject: [PATCH 6/8] Fix module-status command: robust file detection, contracts grep, services pattern, path validation --- .claude/commands/module-status.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.claude/commands/module-status.md b/.claude/commands/module-status.md index 111a03e1..1b4d158d 100644 --- a/.claude/commands/module-status.md +++ b/.claude/commands/module-status.md @@ -6,10 +6,12 @@ Print a health snapshot of a SimpleModule module. No builds required — reads a If no argument was supplied, ask: "Which module would you like to inspect? (e.g., Products, Orders, Users)" -Use the provided name as `{Name}` throughout. Derive paths: -- `IMPL = modules/{Name}/src/SimpleModule.{Name}` -- `CONTRACTS = modules/{Name}/src/SimpleModule.{Name}.Contracts` -- `TESTS = modules/{Name}/tests/SimpleModule.{Name}.Tests` +Use the provided name as `{Name}` throughout. Construct the paths: +- `CONTRACTS = modules/{Name}/src/SimpleModule.{Name}.Contracts/` +- `IMPL = modules/{Name}/src/SimpleModule.{Name}/` +- `TESTS = modules/{Name}/tests/SimpleModule.{Name}.Tests/` + +First, verify that IMPL exists by checking if `{Name}Module.cs` or any `.csproj` is present at that path. If not, note: "Note: The implementation directory may use a non-standard name. Check `modules/{Name}/src/` to find the correct directory name and adjust paths accordingly." --- @@ -28,7 +30,7 @@ Check whether `modules/{Name}/src/SimpleModule.{Name}/EntityConfigurations/` exi Grep for `: IEvent` in `modules/{Name}/src/SimpleModule.{Name}.Contracts/` (all `.cs` files). Extract the `record` name from each match. List them; if none, show `0`. **Services** -Grep for `class.*I{Name}Contracts` in `modules/{Name}/src/SimpleModule.{Name}/` (all `.cs` files). List each class name found. +Grep for `class\s+\w+\s*:\s*I\w+Contracts` in `modules/{Name}/src/SimpleModule.{Name}/` (all `.cs` files). List each class name found. If no matches found, show "none detected". --- @@ -74,8 +76,8 @@ For each entry, check whether the file or directory exists and assign a status s | File | Required | Status | |------|----------|--------| -| `{Name}Constants.cs` in Contracts | Yes | ✅ if exists, ❌ if not | -| `I{Name}Contracts.cs` in Contracts | Yes | ✅ if exists, ❌ if not | +| `*Constants.cs` anywhere under `modules/{Name}/src/` | Yes | ✅ if at least one found (list filenames), ❌ if none | +| `I*Contracts.cs` in CONTRACTS | Yes | ✅ if at least one found (list filenames), ❌ if none | | `{Name}Module.cs` in IMPL | Yes | ✅ if exists, ❌ if not | | `Pages/index.ts` in IMPL | If view endpoints exist | ✅/❌ or N/A | | `vite.config.ts` in IMPL | If view endpoints exist | ✅/❌ or N/A | @@ -117,8 +119,8 @@ Print exactly this structure (fill in real values): ### Required Files | File | Required | Status | |------|----------|--------| -| {Name}Constants.cs | Yes | ✅/❌ | -| I{Name}Contracts.cs | Yes | ✅/❌ | +| *Constants.cs (any, under src/) | Yes | ✅ [filename(s)] / ❌ | +| I*Contracts.cs (any, in Contracts/) | Yes | ✅ [filename(s)] / ❌ | | {Name}Module.cs | Yes | ✅/❌ | | Pages/index.ts | If view endpoints | ✅/❌/N/A | | vite.config.ts | If view endpoints | ✅/❌/N/A | From 23f7da9fb985b72b247b470631e157b68e8996f5 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 3 Apr 2026 21:28:08 +0200 Subject: [PATCH 7/8] Enrich simplemodule skill: add pitfalls, events, settings, and SM00xx diagnostics sections --- .claude/skills/simplemodule/SKILL.md | 97 +++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/.claude/skills/simplemodule/SKILL.md b/.claude/skills/simplemodule/SKILL.md index 5446b87d..ea436904 100644 --- a/.claude/skills/simplemodule/SKILL.md +++ b/.claude/skills/simplemodule/SKILL.md @@ -7,7 +7,9 @@ description: > understanding the project architecture. Triggers on: "add module", "new module", "add feature", "create endpoint", "module architecture", "how does SimpleModule work", "event bus", "permissions", "menu", "settings", "database context", "contracts", - "IModule", "IEndpoint", "IViewEndpoint", "Inertia", "CrudEndpoints". + "IModule", "IEndpoint", "IViewEndpoint", "Inertia", "CrudEndpoints", + "debug module", "review module", "module status", "SM00", "source generator diagnostic", + "module not found", "page 404", "event bus", "settings", "IEventBus", "SettingDefinition". --- # SimpleModule Framework Guide @@ -171,6 +173,99 @@ When adding a new module, ensure: 7. Run `dotnet build` to verify source generator picks up the module 8. Run `npm run validate-pages` to verify page registry matches endpoints +## Common Pitfalls + +| Symptom | Cause | Fix | +|---------|-------|-----| +| Module not discovered at build | Missing `[Module]` attribute or wrong project not referenced in Host | Add `[Module]` to module class; add `` to Host `.csproj` | +| Page navigates but shows blank | Missing `Pages/index.ts` entry | Add `'Module/Page': () => import('../Views/Page')` to Pages/index.ts | +| SM0001 diagnostic | Module class is `sealed` or not `public` | Make class `public` and remove `sealed` | +| SM0010/SM0020 diagnostic | Endpoint class visibility wrong | Check Constitution — endpoint visibility must match what the generator expects | +| SM0030 diagnostic | `[Dto]` type in implementation assembly | Move `[Dto]` type to the `.Contracts` project | +| SM0040 diagnostic | Direct impl→impl project reference | Reference only the `.Contracts` project; inject `I{Name}Contracts` interface | +| `TreatWarningsAsErrors` build failure | Nullable, unused variable, or analyzer warning | Fix the warning; suppress in `.editorconfig` only if genuinely intentional | +| Event handler never called | Handler not registered in DI | Add `services.AddScoped, MyHandler>()` in `ConfigureServices` | +| Cross-module data wrong | Injecting impl class directly | Always inject `I{Name}Contracts` interface, never the concrete service class | + +## Events Pattern + +Define events in the **Contracts** assembly as `record` types: + +```csharp +// In SimpleModule.{Name}.Contracts +public record OrderPlaced(OrderId OrderId, decimal Total) : IEvent; +``` + +Publish from a service: + +```csharp +// Awaits all handlers before returning +await _eventBus.PublishAsync(new OrderPlaced(order.Id, order.Total), ct); + +// Fire-and-forget (background task, not awaited) +_eventBus.PublishInBackground(new OrderPlaced(order.Id, order.Total)); +``` + +Handle in any module: + +```csharp +public sealed class SendConfirmationEmailHandler : IEventHandler +{ + public async Task HandleAsync(OrderPlaced evt, CancellationToken ct) + { + // use evt.OrderId, evt.Total + } +} +``` + +Register in `ConfigureServices`: + +```csharp +services.AddScoped, SendConfirmationEmailHandler>(); +``` + +**Semantics:** Handlers run sequentially. A failing handler throws `AggregateException`. Write handlers to be idempotent. + +## Settings Pattern + +Register setting definitions in `ConfigureSettings` on the module class: + +```csharp +public void ConfigureSettings(ISettingsBuilder builder) +{ + builder.AddDefinition(new SettingDefinition + { + Key = "Orders.MaxItemsPerOrder", + DisplayName = "Max Items Per Order", + Group = "Orders", + Scope = SettingScope.Application, // System | Application | User + Type = SettingType.Number, // Text | Number | Bool | Json + DefaultValue = "50", + }); +} +``` + +Read a setting in a service: + +```csharp +var max = await _settings.GetAsync("Orders.MaxItemsPerOrder", ct); +``` + +**Scopes:** `System` = application-wide (e.g., feature flags), `Application` = per tenant/deployment, `User` = per authenticated user. + +## Constitution Diagnostics (SM00xx) + +The source generator enforces these rules at build time. Run `/debug-module {Name}` to check all at once. + +| Code | Rule | Common cause | +|------|------|-------------| +| SM0001 | Module class must be `public` and not `sealed` | Added `sealed` by reflex | +| SM0010 | `IEndpoint` impl class rule | Check Constitution for required visibility | +| SM0020 | `IViewEndpoint` impl class rule | Check Constitution for required visibility | +| SM0030 | `[Dto]` types must live in the Contracts assembly | Created DTO in impl project | +| SM0040 | No impl→impl project references allowed | Took a shortcut and referenced another module's impl | +| SM0044 | `Inertia.Render` component has no `Pages/index.ts` entry | Forgot to register the page | + ## Key Constraints - Source generator targets **netstandard2.0** with `IIncrementalGenerator` From 522acf1cc05566074821f67988b0cc518d864402 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 3 Apr 2026 21:32:00 +0200 Subject: [PATCH 8/8] Fix simplemodule skill: correct SM diagnostic codes and Settings API --- .claude/skills/simplemodule/SKILL.md | 30 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/.claude/skills/simplemodule/SKILL.md b/.claude/skills/simplemodule/SKILL.md index ea436904..5244e2ed 100644 --- a/.claude/skills/simplemodule/SKILL.md +++ b/.claude/skills/simplemodule/SKILL.md @@ -179,10 +179,10 @@ When adding a new module, ensure: |---------|-------|-----| | Module not discovered at build | Missing `[Module]` attribute or wrong project not referenced in Host | Add `[Module]` to module class; add `` to Host `.csproj` | | Page navigates but shows blank | Missing `Pages/index.ts` entry | Add `'Module/Page': () => import('../Views/Page')` to Pages/index.ts | -| SM0001 diagnostic | Module class is `sealed` or not `public` | Make class `public` and remove `sealed` | -| SM0010/SM0020 diagnostic | Endpoint class visibility wrong | Check Constitution — endpoint visibility must match what the generator expects | -| SM0030 diagnostic | `[Dto]` type in implementation assembly | Move `[Dto]` type to the `.Contracts` project | -| SM0040 diagnostic | Direct impl→impl project reference | Reference only the `.Contracts` project; inject `I{Name}Contracts` interface | +| SM0011 diagnostic | Direct impl→impl project reference | Reference only the `.Contracts` project; inject `I{Name}Contracts` interface | +| SM0014 diagnostic | No public interfaces in Contracts assembly | Add `I{Name}Contracts` to the Contracts project | +| SM0025 diagnostic | No implementation for contract interface | Create `{Name}ContractsService` implementing `I{Name}Contracts` and register in `ConfigureServices` | +| SM0041/SM0042 diagnostic | View endpoint misconfigured | Ensure `Inertia.Render` name is prefixed with module name; add `ViewPrefix` to `[Module]` attribute | | `TreatWarningsAsErrors` build failure | Nullable, unused variable, or analyzer warning | Fix the warning; suppress in `.editorconfig` only if genuinely intentional | | Event handler never called | Handler not registered in DI | Add `services.AddScoped, MyHandler>()` in `ConfigureServices` | | Cross-module data wrong | Injecting impl class directly | Always inject `I{Name}Contracts` interface, never the concrete service class | @@ -248,7 +248,7 @@ public void ConfigureSettings(ISettingsBuilder builder) Read a setting in a service: ```csharp -var max = await _settings.GetAsync("Orders.MaxItemsPerOrder", ct); +var max = await _settings.GetSettingAsync("Orders.MaxItemsPerOrder", SettingScope.Application); ``` **Scopes:** `System` = application-wide (e.g., feature flags), `Application` = per tenant/deployment, `User` = per authenticated user. @@ -259,12 +259,20 @@ The source generator enforces these rules at build time. Run `/debug-module {Nam | Code | Rule | Common cause | |------|------|-------------| -| SM0001 | Module class must be `public` and not `sealed` | Added `sealed` by reflex | -| SM0010 | `IEndpoint` impl class rule | Check Constitution for required visibility | -| SM0020 | `IViewEndpoint` impl class rule | Check Constitution for required visibility | -| SM0030 | `[Dto]` types must live in the Contracts assembly | Created DTO in impl project | -| SM0040 | No impl→impl project references allowed | Took a shortcut and referenced another module's impl | -| SM0044 | `Inertia.Render` component has no `Pages/index.ts` entry | Forgot to register the page | +| SM0001 | No duplicate DbSet property names across modules | Two modules declare a DbSet with the same property name | +| SM0007 | No duplicate entity configurations | Registered the same `IEntityTypeConfiguration` twice | +| SM0010 | No circular module dependencies | Contract projects reference each other in a cycle | +| SM0011 | No direct module-to-module implementation references | Took a shortcut referencing another module's impl project | +| SM0014 | Contracts assembly has no public interfaces | Forgot to add `I{Name}Contracts` to the Contracts project | +| SM0015 | No duplicate view page names | Two `IViewEndpoint`s return the same Inertia component name | +| SM0025 | No implementation found for contract interface | Forgot to create the `{Name}ContractsService` class | +| SM0032 | Permission class must be `sealed` | Permissions class is not sealed | +| SM0040 | No duplicate module names | Two modules share the same `[Module]` name string | +| SM0041 | View page name must be prefixed with module name | Inertia component name doesn't start with the module name | +| SM0042 | `ViewPrefix` required when module has view endpoints | Module has `IViewEndpoint`s but no `ViewPrefix` on `[Module]` | +| SM0043 | Module must override at least one `IModule` method | Empty or placeholder module class | + +For the full list of diagnostics, see `docs/CONSTITUTION.md`. ## Key Constraints