Skip to content

Latest commit

 

History

History
266 lines (186 loc) · 15.1 KB

File metadata and controls

266 lines (186 loc) · 15.1 KB

AGENTS.md

Architecture

.NET Aspire distributed app (net10.0) for recipe/menu management. Backend projects live under backend/, and backend/Menu.AppHost orchestrates all services:

  • MenuApi – Minimal API (Auth0 JWT-secured). Endpoints defined via MapGroup extensions in backend/MenuApi/Recipes/.
  • MenuDB – EF Core MenuDbContext + entity definitions (backend/MenuDB/Data/) + migrations (backend/MenuDB/Migrations/).
  • Menu.MigrationService – BackgroundService that applies EF migrations on startup, then exits. The API (MenuApi) waits for this to complete before starting (WaitForCompletion).
  • Redis – AddRedis("cache") resource for caching.
  • Menu.ServiceDefaults / Menu.ApiServiceDefaults – Shared Aspire service defaults (OpenTelemetry, health checks, Swagger).
  • ui/menu-website – Vue 3 + Quasar + Vite frontend (pnpm). Connected to the API via Aspire's AddJavaScriptApp.

Additional Reference Docs

Consult these before working in the relevant area:

  • docs/database-migrations.md — EF Core migration creation and review workflow, naming conventions, and what not to hand-edit.
  • docs/integration-tests.md — ApiTestFixture architecture, Auth0 M2M test authentication, seed data, and the sequential execution model.
  • docs/ci-path-filters.md — CI jobs selected for backend-only, frontend-only, and open-api/-only changes.
  • docs/specs/ — architecture and design specifications for larger features.

Layered Model Pattern

Three distinct model layers — never mix them:

Layer Namespace / Location Purpose
EF Entities backend/MenuDB/Data/ (e.g. RecipeEntity) Database rows; configured in MenuDbContext.OnModelCreating
DB Models backend/MenuApi/DBModel/ (e.g. DBModel.Recipe) Intermediate records using Vogen value objects; returned by repositories
ViewModels backend/MenuApi/ViewModel/ (e.g. ViewModel.Recipe, NewRecipe, FullRecipe) API request/response DTOs

Mapping between layers uses Riok.Mapperly (source-generated, zero-reflection) in backend/MenuApi/MappingProfiles/ViewModelMapper.cs. When adding properties, update the [MapProperty] attributes there.

Vogen Value Objects

Primitive types are wrapped with Vogen (backend/MenuApi/ValueObjects/). Example: RecipeId, RecipeName, IngredientAmount. Assembly-wide defaults in VogenDefaults.cs enable EF Core value converters and Swagger mapping. When creating a new value object:

[ValueObject<int>]
public readonly partial struct MyNewId { }

Repositories must use .Value to unwrap and TypeName.From(x) to wrap.

Key Commands

# Run the full stack (API + SQL container + migrations + UI)
cd backend
dotnet run --project Menu.AppHost

# EF migrations (always from the backend solution root)
dotnet ef migrations add <Name> --project MenuDB --startup-project MenuApi
dotnet ef migrations remove --project MenuDB --startup-project MenuApi

# Unit tests
dotnet test MenuApi.Tests

# Integration tests (requires Docker for SQL Server container + Auth0 secrets)
dotnet test MenuApi.Integration.Tests

Worktrees

  • Repository-wide rule: create temporary git worktrees only under worktrees/ at the repository root.
  • Keep worktrees/.gitkeep committed so the directory exists for automation.
  • When dependency update work is split into multiple independent pull request groups, create one worktree per planned branch under worktrees/ and use separate subagents to apply and validate those groups in parallel.

Windows / PowerShell Notes

Agents running on Windows must substitute Bash idioms in skill commands:

Bash PowerShell equivalent
tail -n N Select-Object -Last N
grep 'pattern' Select-String 'pattern'
cmd1 && cmd2 cmd1; if ($LASTEXITCODE -eq 0) { cmd2 }
rm -rf <path> Remove-Item <path> -Recurse -Force
cp src dst Copy-Item src dst

When pushing a branch containing / from a detached HEAD worktree, always use the full refspec:

git push origin HEAD:refs/heads/<branch-name>

Running a command with timeout using Start-Job: When a command may block the shell (e.g. Playwright/Chromium tests), use this pattern to run it with an explicit timeout and propagate the exit status:

$job = Start-Job { Set-Location <working-directory>; <command> }
$completed = Wait-Job $job -Timeout <timeout-seconds>
$output = Receive-Job $job
$state = $job.State
Remove-Job $job -Force
$output
if ($null -eq $completed) { throw "Command timed out after <timeout-seconds> seconds" }
if ($state -ne 'Completed') { throw "Command failed (job state: $state)" }

Testing Conventions

  • Unit tests (MenuApi.Tests): xUnit + AutoFixture + FakeItEasy + AwesomeAssertions. Custom ValueObjectSpecimenBuilder in CustomGenerator.cs auto-constructs Vogen types via reflection; use [CustomAutoData] (from CustomAutoDataAttribute.cs) on test methods to wire it up.
  • Integration tests (MenuApi.Integration.Tests): Aspire Testing spins up the full AppHost with a containerised SQL Server. All test classes must use [Collection("API Host Collection")] for sequential execution against a shared host. ShortStringAutoDataAttribute limits string length to fit varchar(50) columns and empties collection properties.
  • Assertions use AwesomeAssertions (.Should()) — not FluentAssertions.

End-to-end tests

For full-stack Playwright execution, Auth0/AppHost prerequisites, reuse and cold-start behavior, report/trace triage, and assertion/data-isolation rules, read .agents/skills/e2e-tests/SKILL.md. Run pnpm test:e2e before merging changes to ui/menu-website/src/boot/auth0.ts, ui/menu-website/src/services/auth.ts, ui/menu-website/src/services/recipe-api.ts, ui/menu-website/src/router/, AppHost wiring, or recipe CRUD endpoints. A new page-level route warrants an E2E smoke path; a new component alone does not. An unexplained failure is a diagnosis task: preserve auth, network, status, and UI assertions, and never silence it with a removed assertion, skip/fixme/only, weakened status check, or mocked real path.

Code Style

  • TreatWarningsAsErrors is enabled in Debug and Release for all projects.
  • StyleCop is configured via backend/MenuApi/stylecop.json.
  • ConfigureAwait(false) is used on all async calls in service/repository layers.
  • Program.cs exposes a public partial class Program for integration test WebApplicationFactory compatibility.

Frontend (ui/menu-website)

Vue 3 SPA using Quasar component library, Vite bundler, and pnpm package manager.

Tech Stack

  • Vue 3 (Composition API) + TypeScript + Pinia (stores) + Vue Router
  • Quasar v2 – UI components, SCSS variables in src/css/quasar.variables.scss
  • TanStack Vue Query – server-state management with query invalidation (src/services/recipe-service.ts)
  • openapi-fetch + openapi-typescript – type-safe API client generated from the backend's OpenAPI spec
  • Auth0 (@auth0/auth0-vue) – authentication; configured in src/boot/auth0.ts
  • Storybook 10 – component stories co-located with components (e.g. *.stories.ts)
  • Vitest (unit, jsdom) + Playwright (e2e in e2e/)

API Client Pattern

Types and HTTP calls are generated from the backend OpenAPI spec — never hand-write API types:

# Regenerate after any API endpoint change (run from ui/menu-website)
pnpm generate-openapi

This reads open-api/menu-api.json (generated by the .NET build) and outputs src/generated/open-api/menu-api.ts. The generated types are consumed by src/services/recipe-api.ts which creates a typed openapi-fetch client with Auth0 bearer-token middleware (auth logic extracted into src/services/auth.ts).

Service Layer Convention

Two-layer pattern for API interaction:

Layer File Purpose
API layer src/services/recipe-api.ts Low-level openapi-fetch calls; exports typed functions (postRecipe, getRecipes, etc.)
Service layer src/services/recipe-service.ts Composable wrapping API calls in TanStack Query hooks (useRecipes, useCreateRecipe); handles cache invalidation

Pages/components consume the service layer, never the API layer directly.

Routing

Routes are split into src/router/public.routes.ts (unauthenticated) and src/router/authenticated.routes.ts (protected by Auth0 authGuard). Both groups use MainLayout.vue as the parent layout.

Component Structure

  • src/components/generic/form/ – reusable form fields (text-field, select-field) with co-located Storybook stories
  • src/components/generic/header/ – reusable header buttons (header-button) with co-located Storybook stories
  • src/components/recipe/ – recipe-specific components (new-recipe-form) and fields/ subfolder for recipe field components
  • src/components/buttons/ – auth and navigation buttons (LoginButton, LogoutButton, ProfileButton, NewRecipeHeaderButton, RecipeListButton)
  • src/pages/ – route-level page components

Key Commands (from ui/menu-website/)

pnpm install              # Install dependencies
pnpm dev                  # Dev server (standalone, port 5173)
pnpm aspire               # Dev server started by Aspire (port 5173)
pnpm build                # Type-check + production build
pnpm test                 # Vitest: unit + Storybook projects
pnpm test:unit            # Vitest unit tests only (jsdom)
pnpm test:storybook       # Storybook interaction tests (run after any UI change)
pnpm test:e2e             # Full-stack Playwright tests; starts Aspire (Docker, SQL, migrations, API, UI)
pnpm lint                 # ESLint
pnpm lint-fix             # ESLint with auto-fix
pnpm format               # Prettier
pnpm generate-openapi     # Regenerate API types from OpenAPI spec
pnpm storybook            # Storybook dev server (port 6006)

pnpm test:e2e requires Docker. It waits up to five minutes for the UI at http://localhost:65276 (API http://localhost:65273) and reuses an existing stack on that UI address. The command is long-running on Windows, so agents must use the Start-Job timeout pattern above. The HTML report is written to ui/menu-website/playwright-report/; failure artifacts and traces are written to ui/menu-website/test-results/.

Authenticated e2e tests use a dedicated Auth0 test user. Set E2E_AUTH0_USERNAME and E2E_AUTH0_PASSWORD as Windows environment variables in the shell running Playwright. Keep non-secret Parameters__Auth0Domain and Parameters__Auth0Audience in ui/menu-website/.env.e2e. Credentials are obtained from the repository owner; never commit or print credentials or Playwright storageState. If credentials are missing, ask the owner rather than replacing the real Auth0 flow with mocks or an injected token. The account is local-only; CI must use a separate user and GitHub Actions secrets.

Always run pnpm test:storybook after making any change under ui/menu-website/src/ — it catches regressions in components exercised by existing stories, not just changes to story files themselves.

Volta is the supported Node.js and pnpm toolchain for ui/menu-website/. Its package.json pins the project Node.js and pnpm versions. Volta's pnpm support is experimental, so set VOLTA_FEATURE_PNPM=1 in local environments before running pnpm; Volta then selects the pinned tools when commands run from the frontend directory. GitHub Actions uses volta-cli/action@v4 with the nested frontend package.json and the same feature flag. pnpm-workspace.yaml sets confirmModulesPurge: false so changing pnpm versions self-heals a stale node_modules directory automatically; if you ever see ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY, run pnpm install once by hand from ui/menu-website/.

Component Test Coverage

Every Vue component that is added or changed must come with both:

  • A co-located Storybook story (<component>.stories.ts) covering the new/changed rendering and any client-side validation, with play interaction assertions.
  • A co-located unit test (<component>.test.ts) run by the unit Vitest project (jsdom + @vue/test-utils). Mount with global: { plugins: [Quasar] }; add VueQueryPlugin and a memory vue-router for components that use them.

Split the two by what each can verify reliably:

  • Stories cover rendering, props and validation messages, including MSW-mocked response bodies — register handlers per-story via the beforeEach({ msw }) { msw.use(handler); } hook (not the legacy parameters.msw object, which msw-storybook-addon's CSF-Next API silently ignores). See RecipeList.stories.ts and RecipeDetail.stories.ts for examples.
  • Unit tests cover request payloads, success/error branches and routing, by mocking @/services/recipe-api (the API layer) so the real service/TanStack Query wiring is still exercised. See src/components/organisms/recipe/new-recipe-form.test.ts.

Style & Linting

  • ESLint flat config (eslint.config.ts): Vue recommended + TypeScript type-checked + Vitest + Playwright + Storybook + TanStack Query plugins
  • @typescript-eslint/consistent-type-imports enforced — use import type for type-only imports
  • Prettier for formatting (config in .prettierrc.json), ESLint skips formatting rules via @vue/eslint-config-prettier
  • Path alias: @/ → src/ (configured in tsconfig.app.json and vite.config.ts)
  • .npmrc sets shamefully-hoist=true (required by Quasar)

Adding a New API Endpoint

  1. Add ViewModel DTOs in backend/MenuApi/ViewModel/.
  2. Add DB model records in backend/MenuApi/DBModel/ (if new data shapes are needed).
  3. Add/update Mapperly mappings in backend/MenuApi/MappingProfiles/ViewModelMapper.cs.
  4. Add repository method (interface in backend/MenuApi/Repositories/I*Repository.cs, impl in *Repository.cs).
  5. Add service method (interface + impl in backend/MenuApi/Services/).
  6. Add the endpoint in the relevant backend/MenuApi/Recipes/*Api.cs file using the MapGroup pattern.
  7. Register new DI services in backend/MenuApi/Program.cs.

Agent identity in git and GitHub

When Codex writes to git or GitHub, it must make its authorship clear.

PR review thread comments — start the comment with this line, then a blank line, then the body:

> 🤖 **This comment was written by Codex.**

Do not add this to PR titles/descriptions, issue bodies, or issue comments.

Commit messages — end the message with a Co-authored-by: trailer naming the agent:

Co-authored-by: Codex <codex@openai.com>

Keep the subject line a normal descriptive summary — identity belongs in the trailer, not the subject, and the 🤖 comment block never goes in a commit message.

Full details in .agents/skills/agent-identity/SKILL.md.

Agent skills

Repo-local skills live in .agents/skills/<name>/SKILL.md. Invoke a skill with $<name> in Codex.

Issue tracker

Issues live in GitHub Issues (gh CLI); repo is inferred from git remote -v. See docs/agents/issue-tracker.md.

Domain docs

Single-context layout — root CONTEXT.md + docs/adr/. See docs/agents/domain.md.