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
1 change: 1 addition & 0 deletions .claude/rules/backend/backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Guidelines for C# backend development, including code style, naming, exceptions,
- Logging messages should not include a period
- Use structured logging
- Never introduce new NuGet dependencies
- Never use `#pragma warning disable CS####`—fix the warning instead
- Don't do defensive coding (e.g., don't add exception handling for situations we don't know will happen)
- Use `user?.IsActive == true` over `user != null && user.IsActive == true`
- Avoid try-catch unless we cannot fix the root cause—global exception handling covers unknown exceptions
Expand Down
6 changes: 4 additions & 2 deletions .claude/rules/backend/database-migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Guidelines for creating database migrations using PostgreSQL conventions with sn

1. Create migrations manually rather than using Entity Framework tooling:
- Place migrations in `/[scs-name]/Core/Database/Migrations`
- Name migration files with 14-digit timestamp prefix: `YYYYMMDDHHmmss_MigrationName.cs`
- Name migration files with 14-digit timestamp prefix: `YYYYMMDDHHmmss_MigrationName.cs`—`HHmm` must be the actual current time, never `0000`
- Only implement the `Up` method--don't create `Down` migration

2. Follow this strict column ordering in table creation statements:
Expand Down Expand Up @@ -46,7 +46,8 @@ Guidelines for creating database migrations using PostgreSQL conventions with sn
- Filtered indexes use PostgreSQL `WHERE` clause syntax (e.g., `filter: "deleted_at IS NULL"`)

7. Migrate existing data:
- Use `migrationBuilder.Sql("UPDATE table_name SET column_name = value WHERE condition")` with care
- Use `migrationBuilder.Sql("UPDATE table_name SET column_name = value WHERE condition;")` with care
- End SQL with `;`. Deploy wraps it: `... THEN <sql> END IF;`. No `;` → collides with `END IF;` → Postgres errors `syntax error at or near "END"`

## Examples

Expand Down Expand Up @@ -83,6 +84,7 @@ public sealed class AddUserPreferences : Migration
}

// ❌ DON'T: Forget to add the attribute [DbContext(typeof(XxxDbContext))] for the self-contained system
[Migration("20250507000000_AddUserPrefs")] // ❌ HHmm is `0000`—use actual current time
[Migration("20250507_AddUserPrefs")] // ❌ Missing proper 14-digit timestamp
public class AddUserPrefsMigration : Migration // ❌ Not sealed, incorrect naming, suffixed with Migration
{
Expand Down
22 changes: 11 additions & 11 deletions .claude/rules/backend/repositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Guidelines for implementing DDD repositories in the backend, including structure
8. Repositories are automatically registered in the DI container
9. Aggregates with `ITenantScopedEntity` are automatically filtered by tenant using EF Core query filters:
- In rare cases, disable this using `IgnoreQueryFilters` (e.g., looking up anonymous user by email)
- Always name the filter via `QueryFilterNames` (e.g., `.IgnoreQueryFilters([QueryFilterNames.Tenant])`)—never the parameterless overload
- If using `IgnoreQueryFilters`, add an `UnfilteredAsync` suffix and an XML comment warning about disabled filters
10. Use `IEntityTypeConfiguration<TAggregate>` for EF Core model configuration
11. Map strongly typed IDs in EF Core configurations using:
Expand Down Expand Up @@ -75,21 +76,20 @@ public sealed class AccountDbContext(DbContextOptions<AccountDbContext> options,

### Use of IgnoreQueryFilters

If you use `.IgnoreQueryFilters()`, the repository method must have an `UnfilteredAsync` suffix and an XML comment warning that this is dangerous and disables tenant and soft-delete filters.
Always pass a named filter via `QueryFilterNames`. The method must have an `UnfilteredAsync` suffix and an XML comment warning that the filter is disabled.

```csharp
/// <summary> // ✅ DO: Add XML comment explaining why ignoring query filters is acceptable
/// Retrieves a user by email without applying tenant query filters.
/// This method should only be used during the login processes where tenant context is not yet established.
/// <summary> // ✅ DO: XML comment explaining why
/// Retrieves a user by email without applying the tenant query filter.
/// </summary>
public async Task<User?> GetUserByEmailUnfilteredAsync(string email, CancellationToken cancellationToken) // ✅ DO: Add `Unfiltered` to the surffix
public async Task<User?> GetUserByEmailUnfilteredAsync(string email, CancellationToken cancellationToken) // ✅ DO: UnfilteredAsync suffix
{
return await DbSet.IgnoreQueryFilters().FirstOrDefaultAsync(u => u.Email == email.ToLowerInvariant(), cancellationToken); // ✅ DO: Use .IgnoreQueryFilters() only in rare cases, with UnfilteredAsync suffix and XML comment
return await DbSet.IgnoreQueryFilters([QueryFilterNames.Tenant]).FirstOrDefaultAsync(u => u.Email == email.ToLowerInvariant(), cancellationToken); // ✅ DO: Named filter
}

// ❌ DON'T: Use .IgnoreQueryFilters() without UnfilteredAsync suffix or without an XML warning comment
public async Task<User?> GetUserByEmail(string email, CancellationToken cancellationToken)
{
return await DbSet.IgnoreQueryFilters().FirstOrDefaultAsync(u => u.Email == email.ToLowerInvariant(), cancellationToken); // ❌ Missing UnfilteredAsync suffix and XML comment
}
// ❌ DON'T: Parameterless overload—disables every filter at once
return await DbSet.IgnoreQueryFilters().FirstOrDefaultAsync(...);

// ❌ DON'T: Missing UnfilteredAsync suffix and XML comment
public async Task<User?> GetUserByEmail(...) { ... }
```
2 changes: 1 addition & 1 deletion .claude/skills/commit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Speed critical. No slow commands unless changes warrant.

## Translations

Frontend changes touching user-facing strings: run `translate` to check `.po` files. Missing keys: flag user; don't commit until resolved.
Frontend changes touching user-facing strings: inspect `.po` files for missing translations and translate them. Missing keys: flag user; don't commit until resolved.

## Validation (judgment)

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/create-pull-request/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ If STEP 4 confirmed a rename, rename the branch and verify. Safe in a worktree (
## STEP 9: Push

1. Re-check upstream tracking with `git rev-parse @{u} 2>/dev/null`.
2. **If unset** (branch never pushed, or just renamed): ask "Branch is local-only. Push to origin?". On confirm: `git push -u origin HEAD`. The `-u` is required to set upstream tracking.
2. **If unset** (branch never pushed, or just renamed): ask "Branch is local-only. Push to origin?". On confirm, push and ensure the pushed branch has upstream tracking (`git push --set-upstream origin "$(git branch --show-current)"`).
3. **If upstream exists**, check divergence:
- `git log --oneline @{u}..HEAD` (local-ahead)
- `git log --oneline HEAD..@{u}` (remote-ahead)
Expand Down
2 changes: 2 additions & 0 deletions .claude/skills/db-query/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ description: Query the local Postgres database of the active Aspire worktree via

**Read-only. Every write needs explicit user approval for that exact statement, every time — prior approvals never carry over.**

**For destructive operations (DROP, TRUNCATE, DELETE, ALTER, or anything that loses data), take extreme care. If anything in the request is even slightly unclear about the scope, target, or intent, stop and ask for clarification before executing. Always assume the most conservative interpretation.**

## Prerequisites

If `psql --version` fails, tell the user:
Expand Down