Skip to content
131 changes: 131 additions & 0 deletions .claude/commands/debug-module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
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.

**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.

---

## 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"` (must NOT be `Microsoft.NET.Sdk.Razor` or `Microsoft.NET.Sdk.Web`).
- Contains `<FrameworkReference Include="Microsoft.AspNetCore.App" />`.

Fix if failing: Set `Sdk="Microsoft.NET.Sdk"` if it is wrong. If `<FrameworkReference Include="Microsoft.AspNetCore.App" />` is missing, add it inside an `<ItemGroup>`.

---

## Check 4 — Host reference

Read `template/SimpleModule.Host/SimpleModule.Host.csproj`.

Verify:
- A `<ProjectReference>` 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 `<ProjectReference>` inside an `<ItemGroup>` 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: Locate the existing `<Folder Name="/modules/">` element in `SimpleModule.slnx` and add a new child `<Folder Name="/modules/{Name}/">` block inside it with `<Project>` entries for both the contracts and implementation `.csproj` files.

---

## 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}'),
```

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.

---

## 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.
131 changes: 131 additions & 0 deletions .claude/commands/module-status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# /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. 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."

---

## 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\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".

---

## 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 `<ProjectReference>` `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 |
|------|----------|--------|
| `*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 |
| `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 |
|------|----------|--------|
| *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 |
| package.json | If view endpoints | ✅/❌/N/A |
| tests/ directory | Recommended | ✅/⚠️ |
```

Do not include any additional commentary outside these sections.
131 changes: 131 additions & 0 deletions .claude/commands/review-module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
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 `<ProjectReference>` 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. **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 `_`.

---

## 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<IEventHandler<TEvent>, 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<IEventHandler<{EventType}>, {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/` (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.

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** — 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.

---

## 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)
Loading
Loading