Working with AI agents
AppointMe ships CLAUDE.md files and Claude Code skills that teach coding agents its .NET slice, value object and API client rules. Use them, extend them, and feed agents these docs.
- You'll get
- An agent that follows AppointMe’s conventions
- Time
- 15 minutes
- Touches
CLAUDE.md,.claude/skills/, a newAGENTS.md- Before you start
- Claude Code or another coding agent
What ships for agents
Section titled “What ships for agents”Two CLAUDE.md files and three skills tell an agent how the codebase is built and which commands prove a change works.
| File | Scope | Tells the agent |
|---|---|---|
CLAUDE.md |
The whole repo | Build, test and run commands, module layout, slice rules, value object factories, naming rules, when to regenerate the API client, local ports |
src/AppointMe.Frontend/CLAUDE.md |
The frontend | Vite+ commands, folder layout, data fetching, modal dialogs, forms, date and time rules, and a list of “don’t”s |
.claude/skills/regenerate-api/SKILL.md |
Contract changes | Build the API, run orval, report the src/api/ diff, then type-check the call sites |
.claude/skills/verify/SKILL.md |
Runtime checks | Start Aspire, sign in through the demo login with curl, send the antiforgery header on mutations |
src/AppointMe.Frontend/.claude/skills/shadcn/ |
UI components | The shadcn/ui CLI and its composition rules, vendored from shadcn/ui (recorded in skills-lock.json) |
src/AppointMe.Frontend/.agents/skills/shadcn/ |
UI components | An identical copy, for agents that read .agents/skills/ |
There’s no AGENTS.md. Every file above also ships in a project generated with dotnet new, renamed to your project. Only .claude/settings.json, upstream’s plugin settings, is left out.
What the two root skills do
Section titled “What the two root skills do”| Skill | Use it when | It runs |
|---|---|---|
/regenerate-api |
An endpoint, request or response shape, route, verb, status code or auth attribute changed | npm run generate:api, which builds AppointMe.Api in Release (writing obj/openapi.json) and runs orval. Then npx vp check --no-fmt --no-lint. |
/verify |
A change needs checking against the running app | dotnet run in src/AppointMe.Aspire, then curl -sk calls against https://localhost:7233 with a demo-login cookie jar |
regenerate-api says when not to run it: internal handlers, domain events, EF configuration, migrations and tests don’t change the contract. Neither skill commits anything.
Use them with Claude Code
Section titled “Use them with Claude Code”Start Claude Code at the repo root. The root CLAUDE.md loads at launch, and the frontend’s loads the first time the agent touches a frontend file.
| File | When Claude Code loads it |
|---|---|
CLAUDE.md |
At launch. Claude Code reads every CLAUDE.md from the working directory up, so starting in a subfolder loads it too. |
src/AppointMe.Frontend/CLAUDE.md |
The first time Claude reads or edits a file under src/AppointMe.Frontend/, or at launch if you start there |
regenerate-api, verify |
At launch. Type /regenerate-api or /verify, or let Claude choose one from its description. |
The frontend’s shadcn skill |
The first time Claude touches a frontend file. It’s user-invocable: false, so Claude invokes it and there’s no /shadcn. |
The shadcn skill’s frontmatter also pre-approves its CLI commands for the turn that uses it:
---name: shadcndescription: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".user-invocable: falseallowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)---To see which instruction files a session loaded, run /context all and check Memory files. The loading rules are in Claude Code’s memory and skills docs.
Use them with other agents
Section titled “Use them with other agents”Agents that follow the AGENTS.md convention look for AGENTS.md, which the repo doesn’t have. Add one that every tool shares.
| Option | How | Trade-off |
|---|---|---|
Move the content to AGENTS.md and import it |
git mv CLAUDE.md AGENTS.md, then a new CLAUDE.md whose first line is @AGENTS.md |
One shared file. Claude-only notes go below the import. |
| Symlink | ln -s CLAUDE.md AGENTS.md |
Nothing to keep in sync, but symlinks need core.symlinks on Windows checkouts |
| Repeat for the frontend | The same in src/AppointMe.Frontend/ |
Agents that read nested AGENTS.md files get the frontend rules too |
Claude Code reads AGENTS.md itself only when there’s no CLAUDE.md. AppointMe has one, so keep the @AGENTS.md import if you take the first option.
Skills follow the Agent Skills standard, so each SKILL.md is a plain Markdown procedure that any agent can follow. The root skills exist only under .claude/skills/, and Claude Code doesn’t read .agents/. Copy them to wherever your agent looks, or list them in AGENTS.md. The shadcn skill’s !`npx shadcn@latest info --json` line is a Claude Code extension: Claude Code runs the command when the skill loads, and other agents see it as text.
Get good results in this codebase
Section titled “Get good results in this codebase”Ask for changes in the codebase’s own terms, point at the slice to copy, and name the check that proves the change.
| Ask the agent to | Because | Check |
|---|---|---|
Put each use case in its own verb-first slice folder, modeled on an existing one such as Customers/RegisterCustomer/ |
That’s the layout CLAUDE.md teaches. See Vertical slices. |
The new folder holds the same kinds of file as the one it copies |
Build value objects from input with Create or CreateOrNull, never new |
The primary constructors don’t validate. They’re for EF Core and Dapper. | Search the diff for new Email( and new PersonName( |
Run /regenerate-api in the same task as any contract change |
Otherwise the committed client drifts from the API | git diff --stat src/AppointMe.Frontend/src/api/ isn’t empty |
Run codegen test after adding or changing a Wolverine handler |
In Development, Wolverine generates handler code at runtime, so a broken handler fails only when a message reaches it | It prints Success! |
Gate new startup code that needs a database or secrets on CodeGenerationDetection.IsRunningGeneration() |
Building the API runs its startup to write obj/openapi.json |
dotnet build passes |
Take CompanyId and IPrincipal in handlers, and call principal.Require(...) |
That’s how the handlers get their tenant and check permissions | Code review |
Filter every Dapper query on [CompanyId] = @CompanyId |
Nothing enforces it. See Multi-tenancy. | Code review |
This is the factory rule as the code applies it, and the example CLAUDE.md points agents at:
var customer = Customer.Register( companyId: companyId, name: PersonName.Create(command.FirstName, command.LastName), dateOfBirth: DateOfBirth.CreateOrNull(command.DateOfBirth, timeProvider), gender: Gender.ParseOrNull(command.Gender), email: Email.CreateOrNull(command.Email), registrationDate: timeProvider.GetUtcNow());The Wolverine check is the one CI runs. It needs no database, because the codegen argument switches the app to the Codegen environment:
dotnet run --project src/AppointMe.Api/AppointMe.Api.csproj --no-launch-profile -- codegen testA good prompt names all three things: “Add a DeactivateCustomer slice to CRM, modeled on Customers/DeleteCustomer/. Build value objects with their factories. It changes the API, so run /regenerate-api and fix the call sites. Finish with dotnet test and npm run check.”
Why tell the agent which slice to copy?1 min · optional
CLAUDE.md describes the rules, but the slices are the ground truth: file names, the extension-method factory, how the endpoint maps the request to a command. An agent that reads one finished slice first copies all of that, including what the prose leaves out. Without one, it has only the prose to go on, plus whatever layout it has seen most often elsewhere.
Extend CLAUDE.md for your product
Section titled “Extend CLAUDE.md for your product”Treat CLAUDE.md like code. Delete what doesn’t apply to you, fix what’s stale, and keep your own rules in files upstream doesn’t edit.
-
Delete what’s only for the upstream repo. In a generated project, the “Template package” subsection describes
templates/and.template.config/, which aren’t there (the rename turns its heading intodotnet new contoso.booking). The nuget.org lines under “Releasing” don’t apply to you either. -
Fix the lines that lag the code. The table below lists them. An agent follows
CLAUDE.mdover its own reading of the code, so a stale line costs you every session. -
Put your product’s rules in
.claude/rules/. Files there load likeCLAUDE.md, and apaths:field scopes one to part of the tree. Keeping them out of upstream’sCLAUDE.mdmakes upstream merges easier.Example: .claude/rules/invoicing.md ---paths:- "src/Invoicing/**"---# Invoicing module- `InvoicingDbContext` uses the `invoicing` schema. Add migrations to it only.- Other modules reference `Contoso.Booking.Invoicing.Contracts`, never `Contoso.Booking.Invoicing`.- Every Dapper query filters on `[CompanyId] = @CompanyId`. -
Turn repeated procedures into skills. Copy the shape of
.claude/skills/regenerate-api/SKILL.md: adescriptionthat says when to use the skill, then preconditions, steps, pitfalls and what’s out of scope. -
Check what loads. Start a new session, run
/context all, and confirm your files are listed. Claude Code’s docs suggest keeping eachCLAUDE.mdunder 200 lines.
Lines that lag the code
Section titled “Lines that lag the code”CLAUDE.md says |
The code at this commit |
|---|---|
.AddCompaniesModule() in the module registration example |
.AddOrganizationsModule(), and .AddBookingModule() is missing from the example |
Booking/AppointMe.Booking.Contracts/ with “Booking contracts (TBD)” |
Booking is a full module: src/Booking/AppointMe.Booking |
| Bounded contexts: Identity, Organizations, CRM | There’s also BookingDbContext, with the booking schema |
CustomerRegisteredEventHandler sits in CreateAttendee/ |
It’s in Attendees/ReconcileAttendees/ |
Keycloak at http://localhost:8082 (the verify skill says the same) |
Keycloak serves https://localhost:8082 once the dev certificate is trusted |
Feed these docs to an agent
Section titled “Feed these docs to an agent”These docs publish machine-readable copies, so you can give an agent the whole set or a single page as Markdown.
| URL | What it holds |
|---|---|
https://appointme.dev/docs/llms.txt |
An index for language models, linking the two sets below |
https://appointme.dev/docs/llms-full.txt |
Every page in one file |
https://appointme.dev/docs/llms-small.txt |
An abridged version, with non-essential content removed |
https://appointme.dev/docs/<section>/<page>.md |
One page as Markdown, for example this page |
Each page’s right rail also has Copy page as Markdown and View as Markdown. The docs are checked against a fixed commit of upstream main. When they disagree with the code in your repository, tell the agent to trust the code.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
The agent calls .AddCompaniesModule() |
A stale example in CLAUDE.md |
Fix the line, as in Lines that lag the code |
The agent hand-edits src/api/client.ts |
It missed the regeneration rule | Run /regenerate-api. Orval’s clean: true overwrites hand edits anyway. |
npm run generate:api fails in GenerateOpenApiDocuments |
New startup code needs a database or real configuration | Gate it on CodeGenerationDetection.IsRunningGeneration() |
| The agent ignores frontend rules | It hasn’t touched a frontend file yet, so the frontend CLAUDE.md isn’t loaded |
Ask it to read src/AppointMe.Frontend/CLAUDE.md first |
/shadcn isn’t in the menu |
The skill is user-invocable: false |
Ask for the UI change, and Claude loads the skill itself |
| Another agent ignores every convention | It reads AGENTS.md, and there isn’t one |
Add one, as in Use them with other agents |
A verify mutation returns Antiforgery Validation Error |
The X-XSRF-TOKEN header is missing or stale |
Send the XSRF-TOKEN cookie’s value, and refresh it with a GET after logging in |