diff --git a/astro.config.mjs b/astro.config.mjs index 14d363d..0f2246f 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -12,7 +12,7 @@ const sidebars = { { label: 'Start here', items: ['minimalapi/getting-started', 'minimalapi/guides/run-locally'] }, { label: 'Guides', - items: ['minimalapi/guides/add-an-endpoint', 'minimalapi/guides/database', 'minimalapi/guides/secure-the-api', 'minimalapi/guides/ship-the-container', 'minimalapi/guides/set-up-ci'], + items: ['minimalapi/guides/add-an-endpoint', 'minimalapi/guides/test-your-changes', 'minimalapi/guides/database', 'minimalapi/guides/secure-the-api', 'minimalapi/guides/ship-the-container', 'minimalapi/guides/set-up-ci', 'minimalapi/guides/work-with-a-coding-agent'], }, { label: 'Reference', items: [{ autogenerate: { directory: 'minimalapi/reference' } }] }, { @@ -24,7 +24,7 @@ const sidebars = { { label: 'Start here', items: ['webapi/getting-started', 'webapi/guides/run-locally'] }, { label: 'Guides', - items: ['webapi/guides/add-an-endpoint', 'webapi/guides/database', 'webapi/guides/secure-the-api', 'webapi/guides/ship-the-container', 'webapi/guides/set-up-ci'], + items: ['webapi/guides/add-an-endpoint', 'webapi/guides/test-your-changes', 'webapi/guides/database', 'webapi/guides/secure-the-api', 'webapi/guides/ship-the-container', 'webapi/guides/set-up-ci', 'webapi/guides/work-with-a-coding-agent'], }, { label: 'Reference', items: [{ autogenerate: { directory: 'webapi/reference' } }] }, { @@ -36,7 +36,7 @@ const sidebars = { { label: 'Start here', items: ['graphql/getting-started', 'graphql/guides/run-locally'] }, { label: 'Guides', - items: ['graphql/guides/add-a-field', 'graphql/guides/database', 'graphql/guides/secure-the-api', 'graphql/guides/ship-the-container', 'graphql/guides/set-up-ci'], + items: ['graphql/guides/add-a-field', 'graphql/guides/test-your-changes', 'graphql/guides/database', 'graphql/guides/secure-the-api', 'graphql/guides/ship-the-container', 'graphql/guides/set-up-ci', 'graphql/guides/work-with-a-coding-agent'], }, { label: 'Reference', items: [{ autogenerate: { directory: 'graphql/reference' } }] }, { @@ -48,7 +48,7 @@ const sidebars = { { label: 'Start here', items: ['mcp/getting-started', 'mcp/guides/run-locally'] }, { label: 'Guides', - items: ['mcp/guides/connect-a-client', 'mcp/guides/add-a-tool', 'mcp/guides/database', 'mcp/guides/secure-the-server', 'mcp/guides/ship-the-container', 'mcp/guides/set-up-ci'], + items: ['mcp/guides/connect-a-client', 'mcp/guides/add-a-tool', 'mcp/guides/test-your-changes', 'mcp/guides/database', 'mcp/guides/secure-the-server', 'mcp/guides/ship-the-container', 'mcp/guides/set-up-ci', 'mcp/guides/work-with-a-coding-agent'], }, { label: 'Reference', items: [{ autogenerate: { directory: 'mcp/reference' } }] }, { @@ -60,7 +60,7 @@ const sidebars = { { label: 'Start here', items: ['lambda/getting-started', 'lambda/guides/run-locally'] }, { label: 'Guides', - items: ['lambda/guides/add-a-function', 'lambda/guides/database', 'lambda/guides/deploy-to-aws', 'lambda/guides/secure-the-function', 'lambda/guides/set-up-ci'], + items: ['lambda/guides/add-a-function', 'lambda/guides/test-your-changes', 'lambda/guides/database', 'lambda/guides/deploy-to-aws', 'lambda/guides/secure-the-function', 'lambda/guides/set-up-ci', 'lambda/guides/work-with-a-coding-agent'], }, { label: 'Reference', items: [{ autogenerate: { directory: 'lambda/reference' } }] }, { diff --git a/src/content/docs/graphql/explanation/how-its-built.mdx b/src/content/docs/graphql/explanation/how-its-built.mdx index f2f08c0..5480490 100644 --- a/src/content/docs/graphql/explanation/how-its-built.mdx +++ b/src/content/docs/graphql/explanation/how-its-built.mdx @@ -7,6 +7,8 @@ sidebar: import ReportCard from '../../../../components/docs/ReportCard.astro'; +This page is about how we build the template itself. Your solution starts with a simpler pipeline, covered in [Set up CI](/graphql/guides/set-up-ci/), and you can borrow any of the checks below when you're ready. + These are templates we build for fun, but the pipeline doesn't know that. Every pull request goes through the same checks, and some of them are allowed to say no. **Gatekeepers** fail the build and block the merge. **Tattletales** report what they find and leave the decision to a human. diff --git a/src/content/docs/graphql/guides/test-your-changes.mdx b/src/content/docs/graphql/guides/test-your-changes.mdx new file mode 100644 index 0000000..8ed2941 --- /dev/null +++ b/src/content/docs/graphql/guides/test-your-changes.mdx @@ -0,0 +1,74 @@ +--- +title: "Test your changes" +description: "Which test project a test belongs in, how the tests are written, and how to check the 95% coverage bar before CI does." +sidebar: + order: 5 +--- + +import { Aside } from '@astrojs/starlight/components'; + +Your solution starts with four test projects and a pipeline that fails under 95% line coverage. That number isn't the goal, it's the floor. The goal is that every behaviour you add has a test that would fail if someone broke it, including you, six months from now. + +## Where a test goes + +| Project | What it tests | How | +| --- | --- | --- | +| `tests/Application.Tests.Unit` | Handlers, validators and pipeline behaviours | xUnit, Shouldly and NSubstitute, with the repositories substituted | +| `tests/Infrastructure.Tests.Integration` | The repositories against a real database | A private in-memory SQLite database per test class, built from the migrations and the fixed seed | +| `tests/Presentation.Tests.Unit` | The error filter, cursors and settings | Plain unit tests, no host | +| `tests/Presentation.Tests.Integration` | Real GraphQL requests for every query and mutation, authentication, health checks, and the schema snapshot | `WebApplicationFactory` with a private database | + +Rule of thumb: test a rule where it lives. A validation rule gets a validator test, a query's filter gets a repository test, and an error code or a field's shape gets an integration test. + +## What each kind of code needs + +Every test has `// Arrange`, `// Act` and `// Assert` sections, tests one behaviour, and is named `Method_Should_`, so a failure reads as a sentence. + +| Code | Prove it works | Prove it says no | +| --- | --- | --- | +| Validator | A valid request has no errors, and each boundary value passes | One failing case per rule, asserting the message, and each value just outside a boundary | +| Handler | The happy path returns the result and passes the values and the cancellation token to the repository | Each documented failure throws the right exception, and nothing after it runs | +| Repository | The right rows come back or change, in order, with paging and search applied | Unknown ids return `null` or `false`, and nothing else changes | +| Query, mutation or type | One test per documented success | One test per documented failure, with the error checked | + +For ranges, test both sides of each edge: stars `1` and `5` pass, `0` and `6` fail. A `[Theory]` with `[InlineData]` keeps that to one test. + +```csharp +// tests/Application.Tests.Unit/Reviews/Commands/CreateReview/CreateReviewValidatorTests.cs +[Theory] +[InlineData(-1)] +[InlineData(0)] +[InlineData(6)] +public void Validator_ShouldHaveValidationErrorFor_Stars(int stars) +``` + +## Run them + +```bash +dotnet test +``` + +No test shares a database with another, so they run in parallel and never trip over each other. Nothing touches `movie-reviews.db` either, so your local data survives a test run. + +## Check coverage before CI does + +CI merges the coverage of all four projects into one total and fails under 95%. Run the same measurement from the solution root: + +```bash +dotnet test -m:1 \ + -p:CollectCoverage=true -p:CoverletOutput=../results/ -p:MergeWith="../results/coverage.json" \ + -p:CoverletOutputFormat=opencover%2cjson \ + -p:ExcludeByFile="**/Program.cs%2c**/Migrations/*.cs%2c**/obj/**/*.cs" \ + -p:ExcludeByAttribute="GeneratedCodeAttribute" +``` + +The last table it prints is the merged total. `Program.cs`, the EF Core migrations and source-generated code are left out on purpose. Everything else counts. + + + +## Where to next + +- [Add a field](/graphql/guides/add-a-field/) ends with a test for the new field. +- [Work with a coding agent](/graphql/guides/work-with-a-coding-agent/): the `test-design`, `unit-tests`, `integration-tests` and `coverage` skills hold these same rules, so an agent writes tests the same way you would. diff --git a/src/content/docs/graphql/guides/work-with-a-coding-agent.mdx b/src/content/docs/graphql/guides/work-with-a-coding-agent.mdx new file mode 100644 index 0000000..429d1a7 --- /dev/null +++ b/src/content/docs/graphql/guides/work-with-a-coding-agent.mdx @@ -0,0 +1,43 @@ +--- +title: "Work with a coding agent" +description: "What AGENTS.md, Jacques and the skills give your coding agent, and how to get the most out of them." +sidebar: + order: 6 +--- + +import { Aside } from '@astrojs/starlight/components'; + +Your solution ships with instructions for coding agents, the same ones we use to build the template. Point Claude Code, GitHub Copilot, Cursor, Codex or anything else that reads `AGENTS.md` at the solution, and it already knows where code goes, how it's tested and which checks must pass. You still decide what gets built. The agent just stops guessing how. + +## What's in the box + +| File | What it does | +| --- | --- | +| `AGENTS.md` | The map: what each project is for, the commands CI runs, the rules that fail a change, and a table of skills | +| `CLAUDE.md` | One line, `@AGENTS.md`, so Claude Code reads the same map | +| `.agents/agents/jacques.md` | Jacques, the sous-chef persona: does the prep, follows the recipes, proves every change and leaves the big calls to you | +| `.agents/skills/` | 33 skills, one folder each, covering every kind of change in the solution: use cases, validation, migrations, logging, telemetry, tests, coverage, Docker, CI and the GraphQL types, DataLoaders and the schema snapshot | + +The skills are written for this solution, not for .NET in general. `add-a-use-case` shows the folder, the file names and the house style for a new command. `database-migrations` has the exact `dotnet ef` command. `coverage` has the same coverage command CI runs. Ask for something and the agent follows the recipe instead of improvising one. + +## Get started + +1. Open the solution in your agent of choice. Most read `AGENTS.md` without being asked. If yours doesn't, start with "Read AGENTS.md". +2. Ask for a change the way you'd ask a colleague: "Add a query that lists the reviews an author wrote, with paging, and put it on a query." +3. For anything bigger than a few lines, Jacques agrees a plan first: the open decisions one at a time, each with a recommendation, then the scope in a paragraph. Answer those and the work starts. +4. Before it says it's done, the agent runs `dotnet build`, `dotnet test` and `dotnet format --verify-no-changes`, the same checks as CI. + +## Make it yours + +- **Where we track work.** `AGENTS.md` has a line that says "Not set". Replace it with your GitHub project, Jira board or Azure Boards, and agents put their plans and handoffs there instead of asking each time. +- **Change a rule, change the skill.** When your team decides to do something differently, update the matching `SKILL.md` in the same pull request. A skill that disagrees with the code is worse than no skill at all. +- **Add a skill for a new pattern.** Bring in something the solution doesn't do yet, such as a message queue, and add a skill for it while it's fresh. `writing-a-skill` has the shape. +- **Rename Jacques if you like.** He won't mind. He's a sous-chef, and he's used to it. + + + +## What the agent won't do + +`AGENTS.md` tells every agent never to push to `main` or merge a pull request, never to skip, delete or weaken a test or the coverage gate, never to suppress a warning to get a green build, never to add a package with a paid licence, and never to commit secrets. It stops and asks before changing the shared layers, adding a package, or changing anything a client depends on. diff --git a/src/content/docs/lambda/explanation/how-its-built.mdx b/src/content/docs/lambda/explanation/how-its-built.mdx index 78b8826..3c469c8 100644 --- a/src/content/docs/lambda/explanation/how-its-built.mdx +++ b/src/content/docs/lambda/explanation/how-its-built.mdx @@ -7,6 +7,8 @@ sidebar: import ReportCard from '../../../../components/docs/ReportCard.astro'; +This page is about how we build the template itself. Your solution starts with a simpler pipeline, covered in [Set up CI](/lambda/guides/set-up-ci/), and you can borrow any of the checks below when you're ready. + These are templates we build for fun, but the pipeline doesn't know that. Every pull request goes through the same checks, and some of them are allowed to say no. **Gatekeepers** fail the build and block the merge. **Tattletales** report what they find and leave the decision to a human. diff --git a/src/content/docs/lambda/guides/test-your-changes.mdx b/src/content/docs/lambda/guides/test-your-changes.mdx new file mode 100644 index 0000000..bcd0cfb --- /dev/null +++ b/src/content/docs/lambda/guides/test-your-changes.mdx @@ -0,0 +1,74 @@ +--- +title: "Test your changes" +description: "Which test project a test belongs in, how the tests are written, and how to check the 95% coverage bar before CI does." +sidebar: + order: 5 +--- + +import { Aside } from '@astrojs/starlight/components'; + +Your solution starts with four test projects and a pipeline that fails under 95% line coverage. That number isn't the goal, it's the floor. The goal is that every behaviour you add has a test that would fail if someone broke it, including you, six months from now. + +## Where a test goes + +| Project | What it tests | How | +| --- | --- | --- | +| `tests/Application.Tests.Unit` | Handlers, validators and pipeline behaviours | xUnit, Shouldly and NSubstitute, with the repositories substituted | +| `tests/Infrastructure.Tests.Integration` | The repositories against a real database | A private in-memory SQLite database per test class, built from the migrations and the fixed seed | +| `tests/Presentation.Tests.Unit` | The source-generated JSON serializer | Plain unit tests, no host | +| `tests/Presentation.Tests.Integration` | Real calls to every function, through the handler and the classes Lambda Annotations generates | A `TestLambdaContext` and a private database | + +Rule of thumb: test a rule where it lives. A validation rule gets a validator test, a query's filter gets a repository test, and what a caller gets back from a function gets an integration test. + +## What each kind of code needs + +Every test has `// Arrange`, `// Act` and `// Assert` sections, tests one behaviour, and is named `Method_Should_`, so a failure reads as a sentence. + +| Code | Prove it works | Prove it says no | +| --- | --- | --- | +| Validator | A valid request has no errors, and each boundary value passes | One failing case per rule, asserting the message, and each value just outside a boundary | +| Handler | The happy path returns the result and passes the values and the cancellation token to the repository | Each documented failure throws the right exception, and nothing after it runs | +| Repository | The right rows come back or change, in order, with paging and search applied | Unknown ids return `null` or `false`, and nothing else changes | +| Function | One test per documented success | One test per documented failure, with the error checked | + +For ranges, test both sides of each edge: stars `1` and `5` pass, `0` and `6` fail. A `[Theory]` with `[InlineData]` keeps that to one test. + +```csharp +// tests/Application.Tests.Unit/Reviews/Commands/CreateReview/CreateReviewValidatorTests.cs +[Theory] +[InlineData(-1)] +[InlineData(0)] +[InlineData(6)] +public void Validator_ShouldHaveValidationErrorFor_Stars(int stars) +``` + +## Run them + +```bash +dotnet test +``` + +No test shares a database with another, so they run in parallel and never trip over each other. Nothing touches the function's database either, so your local data survives a test run. + +## Check coverage before CI does + +CI merges the coverage of all four projects into one total and fails under 95%. Run the same measurement from the solution root: + +```bash +dotnet test -m:1 \ + -p:CollectCoverage=true -p:CoverletOutput=../results/ -p:MergeWith="../results/coverage.json" \ + -p:CoverletOutputFormat=opencover%2cjson \ + -p:ExcludeByFile="**/Program.cs%2c**/Migrations/*.cs%2c**/obj/**/*.cs" \ + -p:ExcludeByAttribute="GeneratedCodeAttribute" +``` + +The last table it prints is the merged total. `Program.cs`, the EF Core migrations and source-generated code are left out on purpose. Everything else counts. + + + +## Where to next + +- [Add a function](/lambda/guides/add-a-function/) ends with a test for the new function. +- [Work with a coding agent](/lambda/guides/work-with-a-coding-agent/): the `test-design`, `unit-tests`, `integration-tests` and `coverage` skills hold these same rules, so an agent writes tests the same way you would. diff --git a/src/content/docs/lambda/guides/work-with-a-coding-agent.mdx b/src/content/docs/lambda/guides/work-with-a-coding-agent.mdx new file mode 100644 index 0000000..61abdc8 --- /dev/null +++ b/src/content/docs/lambda/guides/work-with-a-coding-agent.mdx @@ -0,0 +1,43 @@ +--- +title: "Work with a coding agent" +description: "What AGENTS.md, Jacques and the skills give your coding agent, and how to get the most out of them." +sidebar: + order: 6 +--- + +import { Aside } from '@astrojs/starlight/components'; + +Your solution ships with instructions for coding agents, the same ones we use to build the template. Point Claude Code, GitHub Copilot, Cursor, Codex or anything else that reads `AGENTS.md` at the solution, and it already knows where code goes, how it's tested and which checks must pass. You still decide what gets built. The agent just stops guessing how. + +## What's in the box + +| File | What it does | +| --- | --- | +| `AGENTS.md` | The map: what each project is for, the commands CI runs, the rules that fail a change, and a table of skills | +| `CLAUDE.md` | One line, `@AGENTS.md`, so Claude Code reads the same map | +| `.agents/agents/jacques.md` | Jacques, the sous-chef persona: does the prep, follows the recipes, proves every change and leaves the big calls to you | +| `.agents/skills/` | 32 skills, one folder each, covering every kind of change in the solution: use cases, validation, migrations, logging, telemetry, tests, coverage, Docker, CI and the Lambda functions and the emulator | + +The skills are written for this solution, not for .NET in general. `add-a-use-case` shows the folder, the file names and the house style for a new command. `database-migrations` has the exact `dotnet ef` command. `coverage` has the same coverage command CI runs. Ask for something and the agent follows the recipe instead of improvising one. + +## Get started + +1. Open the solution in your agent of choice. Most read `AGENTS.md` without being asked. If yours doesn't, start with "Read AGENTS.md". +2. Ask for a change the way you'd ask a colleague: "Add a query that lists the reviews an author wrote, with paging, and put it on a function." +3. For anything bigger than a few lines, Jacques agrees a plan first: the open decisions one at a time, each with a recommendation, then the scope in a paragraph. Answer those and the work starts. +4. Before it says it's done, the agent runs `dotnet build`, `dotnet test` and `dotnet format --verify-no-changes`, the same checks as CI. + +## Make it yours + +- **Where we track work.** `AGENTS.md` has a line that says "Not set". Replace it with your GitHub project, Jira board or Azure Boards, and agents put their plans and handoffs there instead of asking each time. +- **Change a rule, change the skill.** When your team decides to do something differently, update the matching `SKILL.md` in the same pull request. A skill that disagrees with the code is worse than no skill at all. +- **Add a skill for a new pattern.** Bring in something the solution doesn't do yet, such as a message queue, and add a skill for it while it's fresh. `writing-a-skill` has the shape. +- **Rename Jacques if you like.** He won't mind. He's a sous-chef, and he's used to it. + + + +## What the agent won't do + +`AGENTS.md` tells every agent never to push to `main` or merge a pull request, never to skip, delete or weaken a test or the coverage gate, never to suppress a warning to get a green build, never to add a package with a paid licence, and never to commit secrets. It stops and asks before changing the shared layers, adding a package, or changing anything a client depends on. diff --git a/src/content/docs/mcp/explanation/how-its-built.mdx b/src/content/docs/mcp/explanation/how-its-built.mdx index 95961c8..42705e3 100644 --- a/src/content/docs/mcp/explanation/how-its-built.mdx +++ b/src/content/docs/mcp/explanation/how-its-built.mdx @@ -7,6 +7,8 @@ sidebar: import ReportCard from '../../../../components/docs/ReportCard.astro'; +This page is about how we build the template itself. Your solution starts with a simpler pipeline, covered in [Set up CI](/mcp/guides/set-up-ci/), and you can borrow any of the checks below when you're ready. + These are templates we build for fun, but the pipeline doesn't know that. Every pull request goes through the same checks, and some of them are allowed to say no. **Gatekeepers** fail the build and block the merge. **Tattletales** report what they find and leave the decision to a human. diff --git a/src/content/docs/mcp/guides/test-your-changes.mdx b/src/content/docs/mcp/guides/test-your-changes.mdx new file mode 100644 index 0000000..32f9b1c --- /dev/null +++ b/src/content/docs/mcp/guides/test-your-changes.mdx @@ -0,0 +1,74 @@ +--- +title: "Test your changes" +description: "Which test project a test belongs in, how the tests are written, and how to check the 95% coverage bar before CI does." +sidebar: + order: 5 +--- + +import { Aside } from '@astrojs/starlight/components'; + +Your solution starts with four test projects and a pipeline that fails under 95% line coverage. That number isn't the goal, it's the floor. The goal is that every behaviour you add has a test that would fail if someone broke it, including you, six months from now. + +## Where a test goes + +| Project | What it tests | How | +| --- | --- | --- | +| `tests/Application.Tests.Unit` | Handlers, validators and pipeline behaviours | xUnit, Shouldly and NSubstitute, with the repositories substituted | +| `tests/Infrastructure.Tests.Integration` | The repositories against a real database | A private in-memory SQLite database per test class, built from the migrations and the fixed seed | +| `tests/Presentation.Tests.Unit` | Tools, the result mapper and the error filters | Plain unit tests, no host | +| `tests/Presentation.Tests.Integration` | A real MCP client calling tools, resources, prompts and completions, plus OAuth and health checks | `WebApplicationFactory` and the MCP C# SDK client | + +Rule of thumb: test a rule where it lives. A validation rule gets a validator test, a query's filter gets a repository test, and what a client sees from a tool gets an integration test through the MCP client. + +## What each kind of code needs + +Every test has `// Arrange`, `// Act` and `// Assert` sections, tests one behaviour, and is named `Method_Should_`, so a failure reads as a sentence. + +| Code | Prove it works | Prove it says no | +| --- | --- | --- | +| Validator | A valid request has no errors, and each boundary value passes | One failing case per rule, asserting the message, and each value just outside a boundary | +| Handler | The happy path returns the result and passes the values and the cancellation token to the repository | Each documented failure throws the right exception, and nothing after it runs | +| Repository | The right rows come back or change, in order, with paging and search applied | Unknown ids return `null` or `false`, and nothing else changes | +| Tool, resource or prompt | One test per documented success | One test per documented failure, with the error checked | + +For ranges, test both sides of each edge: stars `1` and `5` pass, `0` and `6` fail. A `[Theory]` with `[InlineData]` keeps that to one test. + +```csharp +// tests/Application.Tests.Unit/Reviews/Commands/CreateReview/CreateReviewValidatorTests.cs +[Theory] +[InlineData(-1)] +[InlineData(0)] +[InlineData(6)] +public void Validator_ShouldHaveValidationErrorFor_Stars(int stars) +``` + +## Run them + +```bash +dotnet test +``` + +No test shares a database with another, so they run in parallel and never trip over each other. Nothing touches `movie-reviews.db` either, so your local data survives a test run. + +## Check coverage before CI does + +CI merges the coverage of all four projects into one total and fails under 95%. Run the same measurement from the solution root: + +```bash +dotnet test -m:1 \ + -p:CollectCoverage=true -p:CoverletOutput=../results/ -p:MergeWith="../results/coverage.json" \ + -p:CoverletOutputFormat=opencover%2cjson \ + -p:ExcludeByFile="**/Program.cs%2c**/Migrations/*.cs%2c**/obj/**/*.cs" \ + -p:ExcludeByAttribute="GeneratedCodeAttribute" +``` + +The last table it prints is the merged total. `Program.cs`, the EF Core migrations and source-generated code are left out on purpose. Everything else counts. + + + +## Where to next + +- [Add a tool](/mcp/guides/add-a-tool/) ends with a test for the new tool. +- [Work with a coding agent](/mcp/guides/work-with-a-coding-agent/): the `test-design`, `unit-tests`, `integration-tests` and `coverage` skills hold these same rules, so an agent writes tests the same way you would. diff --git a/src/content/docs/mcp/guides/work-with-a-coding-agent.mdx b/src/content/docs/mcp/guides/work-with-a-coding-agent.mdx new file mode 100644 index 0000000..9a1866a --- /dev/null +++ b/src/content/docs/mcp/guides/work-with-a-coding-agent.mdx @@ -0,0 +1,43 @@ +--- +title: "Work with a coding agent" +description: "What AGENTS.md, Jacques and the skills give your coding agent, and how to get the most out of them." +sidebar: + order: 6 +--- + +import { Aside } from '@astrojs/starlight/components'; + +Your solution ships with instructions for coding agents, the same ones we use to build the template. Point Claude Code, GitHub Copilot, Cursor, Codex or anything else that reads `AGENTS.md` at the solution, and it already knows where code goes, how it's tested and which checks must pass. You still decide what gets built. The agent just stops guessing how. + +## What's in the box + +| File | What it does | +| --- | --- | +| `AGENTS.md` | The map: what each project is for, the commands CI runs, the rules that fail a change, and a table of skills | +| `CLAUDE.md` | One line, `@AGENTS.md`, so Claude Code reads the same map | +| `.agents/agents/jacques.md` | Jacques, the sous-chef persona: does the prep, follows the recipes, proves every change and leaves the big calls to you | +| `.agents/skills/` | 32 skills, one folder each, covering every kind of change in the solution: use cases, validation, migrations, logging, telemetry, tests, coverage, Docker, CI and the MCP tools, resources and prompts | + +The skills are written for this solution, not for .NET in general. `add-a-use-case` shows the folder, the file names and the house style for a new command. `database-migrations` has the exact `dotnet ef` command. `coverage` has the same coverage command CI runs. Ask for something and the agent follows the recipe instead of improvising one. + +## Get started + +1. Open the solution in your agent of choice. Most read `AGENTS.md` without being asked. If yours doesn't, start with "Read AGENTS.md". +2. Ask for a change the way you'd ask a colleague: "Add a query that lists the reviews an author wrote, with paging, and put it on a tool." +3. For anything bigger than a few lines, Jacques agrees a plan first: the open decisions one at a time, each with a recommendation, then the scope in a paragraph. Answer those and the work starts. +4. Before it says it's done, the agent runs `dotnet build`, `dotnet test` and `dotnet format --verify-no-changes`, the same checks as CI. + +## Make it yours + +- **Where we track work.** `AGENTS.md` has a line that says "Not set". Replace it with your GitHub project, Jira board or Azure Boards, and agents put their plans and handoffs there instead of asking each time. +- **Change a rule, change the skill.** When your team decides to do something differently, update the matching `SKILL.md` in the same pull request. A skill that disagrees with the code is worse than no skill at all. +- **Add a skill for a new pattern.** Bring in something the solution doesn't do yet, such as a message queue, and add a skill for it while it's fresh. `writing-a-skill` has the shape. +- **Rename Jacques if you like.** He won't mind. He's a sous-chef, and he's used to it. + + + +## What the agent won't do + +`AGENTS.md` tells every agent never to push to `main` or merge a pull request, never to skip, delete or weaken a test or the coverage gate, never to suppress a warning to get a green build, never to add a package with a paid licence, and never to commit secrets. It stops and asks before changing the shared layers, adding a package, or changing anything a client depends on. diff --git a/src/content/docs/minimalapi/explanation/how-its-built.mdx b/src/content/docs/minimalapi/explanation/how-its-built.mdx index 8d925ca..ffc444e 100644 --- a/src/content/docs/minimalapi/explanation/how-its-built.mdx +++ b/src/content/docs/minimalapi/explanation/how-its-built.mdx @@ -7,6 +7,8 @@ sidebar: import ReportCard from '../../../../components/docs/ReportCard.astro'; +This page is about how we build the template itself. Your solution starts with a simpler pipeline, covered in [Set up CI](/minimalapi/guides/set-up-ci/), and you can borrow any of the checks below when you're ready. + These are templates we build for fun, but the pipeline doesn't know that. Every pull request goes through the same checks, and some of them are allowed to say no. **Gatekeepers** fail the build and block the merge. **Tattletales** report what they find and leave the decision to a human. diff --git a/src/content/docs/minimalapi/guides/test-your-changes.mdx b/src/content/docs/minimalapi/guides/test-your-changes.mdx new file mode 100644 index 0000000..78cbc68 --- /dev/null +++ b/src/content/docs/minimalapi/guides/test-your-changes.mdx @@ -0,0 +1,74 @@ +--- +title: "Test your changes" +description: "Which test project a test belongs in, how the tests are written, and how to check the 95% coverage bar before CI does." +sidebar: + order: 5 +--- + +import { Aside } from '@astrojs/starlight/components'; + +Your solution starts with four test projects and a pipeline that fails under 95% line coverage. That number isn't the goal, it's the floor. The goal is that every behaviour you add has a test that would fail if someone broke it, including you, six months from now. + +## Where a test goes + +| Project | What it tests | How | +| --- | --- | --- | +| `tests/Application.Tests.Unit` | Handlers, validators and pipeline behaviours | xUnit, Shouldly and NSubstitute, with the repositories substituted | +| `tests/Infrastructure.Tests.Integration` | The repositories against a real database | A private in-memory SQLite database per test class, built from the migrations and the fixed seed | +| `tests/Presentation.Tests.Unit` | Endpoints and the error handler, called directly | Plain unit tests, no host | +| `tests/Presentation.Tests.Integration` | Real HTTP requests to every endpoint, authentication, health checks, OpenAPI and telemetry | `WebApplicationFactory` with a private database | + +Rule of thumb: test a rule where it lives. A validation rule gets a validator test, a query's filter gets a repository test, and a status code or a Problem Details body gets an integration test. + +## What each kind of code needs + +Every test has `// Arrange`, `// Act` and `// Assert` sections, tests one behaviour, and is named `Method_Should_`, so a failure reads as a sentence. + +| Code | Prove it works | Prove it says no | +| --- | --- | --- | +| Validator | A valid request has no errors, and each boundary value passes | One failing case per rule, asserting the message, and each value just outside a boundary | +| Handler | The happy path returns the result and passes the values and the cancellation token to the repository | Each documented failure throws the right exception, and nothing after it runs | +| Repository | The right rows come back or change, in order, with paging and search applied | Unknown ids return `null` or `false`, and nothing else changes | +| Endpoint | One test per documented success | One test per documented failure, with the error checked | + +For ranges, test both sides of each edge: stars `1` and `5` pass, `0` and `6` fail. A `[Theory]` with `[InlineData]` keeps that to one test. + +```csharp +// tests/Application.Tests.Unit/Reviews/Commands/CreateReview/CreateReviewValidatorTests.cs +[Theory] +[InlineData(-1)] +[InlineData(0)] +[InlineData(6)] +public void Validator_ShouldHaveValidationErrorFor_Stars(int stars) +``` + +## Run them + +```bash +dotnet test +``` + +No test shares a database with another, so they run in parallel and never trip over each other. Nothing touches `movie-reviews.db` either, so your local data survives a test run. + +## Check coverage before CI does + +CI merges the coverage of all four projects into one total and fails under 95%. Run the same measurement from the solution root: + +```bash +dotnet test -m:1 \ + -p:CollectCoverage=true -p:CoverletOutput=../results/ -p:MergeWith="../results/coverage.json" \ + -p:CoverletOutputFormat=opencover%2cjson \ + -p:ExcludeByFile="**/Program.cs%2c**/Migrations/*.cs%2c**/obj/**/*.cs" \ + -p:ExcludeByAttribute="GeneratedCodeAttribute" +``` + +The last table it prints is the merged total. `Program.cs`, the EF Core migrations and source-generated code are left out on purpose. Everything else counts. + + + +## Where to next + +- [Add an endpoint](/minimalapi/guides/add-an-endpoint/) ends with a test for the new endpoint. +- [Work with a coding agent](/minimalapi/guides/work-with-a-coding-agent/): the `test-design`, `unit-tests`, `integration-tests` and `coverage` skills hold these same rules, so an agent writes tests the same way you would. diff --git a/src/content/docs/minimalapi/guides/work-with-a-coding-agent.mdx b/src/content/docs/minimalapi/guides/work-with-a-coding-agent.mdx new file mode 100644 index 0000000..708011c --- /dev/null +++ b/src/content/docs/minimalapi/guides/work-with-a-coding-agent.mdx @@ -0,0 +1,43 @@ +--- +title: "Work with a coding agent" +description: "What AGENTS.md, Jacques and the skills give your coding agent, and how to get the most out of them." +sidebar: + order: 6 +--- + +import { Aside } from '@astrojs/starlight/components'; + +Your solution ships with instructions for coding agents, the same ones we use to build the template. Point Claude Code, GitHub Copilot, Cursor, Codex or anything else that reads `AGENTS.md` at the solution, and it already knows where code goes, how it's tested and which checks must pass. You still decide what gets built. The agent just stops guessing how. + +## What's in the box + +| File | What it does | +| --- | --- | +| `AGENTS.md` | The map: what each project is for, the commands CI runs, the rules that fail a change, and a table of skills | +| `CLAUDE.md` | One line, `@AGENTS.md`, so Claude Code reads the same map | +| `.agents/agents/jacques.md` | Jacques, the sous-chef persona: does the prep, follows the recipes, proves every change and leaves the big calls to you | +| `.agents/skills/` | 32 skills, one folder each, covering every kind of change in the solution: use cases, validation, migrations, logging, telemetry, tests, coverage, Docker, CI and the Minimal API endpoints and OpenAPI | + +The skills are written for this solution, not for .NET in general. `add-a-use-case` shows the folder, the file names and the house style for a new command. `database-migrations` has the exact `dotnet ef` command. `coverage` has the same coverage command CI runs. Ask for something and the agent follows the recipe instead of improvising one. + +## Get started + +1. Open the solution in your agent of choice. Most read `AGENTS.md` without being asked. If yours doesn't, start with "Read AGENTS.md". +2. Ask for a change the way you'd ask a colleague: "Add a query that lists the reviews an author wrote, with paging, and put it on an endpoint." +3. For anything bigger than a few lines, Jacques agrees a plan first: the open decisions one at a time, each with a recommendation, then the scope in a paragraph. Answer those and the work starts. +4. Before it says it's done, the agent runs `dotnet build`, `dotnet test` and `dotnet format --verify-no-changes`, the same checks as CI. + +## Make it yours + +- **Where we track work.** `AGENTS.md` has a line that says "Not set". Replace it with your GitHub project, Jira board or Azure Boards, and agents put their plans and handoffs there instead of asking each time. +- **Change a rule, change the skill.** When your team decides to do something differently, update the matching `SKILL.md` in the same pull request. A skill that disagrees with the code is worse than no skill at all. +- **Add a skill for a new pattern.** Bring in something the solution doesn't do yet, such as a message queue, and add a skill for it while it's fresh. `writing-a-skill` has the shape. +- **Rename Jacques if you like.** He won't mind. He's a sous-chef, and he's used to it. + + + +## What the agent won't do + +`AGENTS.md` tells every agent never to push to `main` or merge a pull request, never to skip, delete or weaken a test or the coverage gate, never to suppress a warning to get a green build, never to add a package with a paid licence, and never to commit secrets. It stops and asks before changing the shared layers, adding a package, or changing anything a client depends on. diff --git a/src/content/docs/webapi/explanation/how-its-built.mdx b/src/content/docs/webapi/explanation/how-its-built.mdx index b73936a..9bddff6 100644 --- a/src/content/docs/webapi/explanation/how-its-built.mdx +++ b/src/content/docs/webapi/explanation/how-its-built.mdx @@ -7,6 +7,8 @@ sidebar: import ReportCard from '../../../../components/docs/ReportCard.astro'; +This page is about how we build the template itself. Your solution starts with a simpler pipeline, covered in [Set up CI](/webapi/guides/set-up-ci/), and you can borrow any of the checks below when you're ready. + These are templates we build for fun, but the pipeline doesn't know that. Every pull request goes through the same checks, and some of them are allowed to say no. **Gatekeepers** fail the build and block the merge. **Tattletales** report what they find and leave the decision to a human. diff --git a/src/content/docs/webapi/guides/test-your-changes.mdx b/src/content/docs/webapi/guides/test-your-changes.mdx new file mode 100644 index 0000000..b605bec --- /dev/null +++ b/src/content/docs/webapi/guides/test-your-changes.mdx @@ -0,0 +1,74 @@ +--- +title: "Test your changes" +description: "Which test project a test belongs in, how the tests are written, and how to check the 95% coverage bar before CI does." +sidebar: + order: 5 +--- + +import { Aside } from '@astrojs/starlight/components'; + +Your solution starts with four test projects and a pipeline that fails under 95% line coverage. That number isn't the goal, it's the floor. The goal is that every behaviour you add has a test that would fail if someone broke it, including you, six months from now. + +## Where a test goes + +| Project | What it tests | How | +| --- | --- | --- | +| `tests/Application.Tests.Unit` | Handlers, validators and pipeline behaviours | xUnit, Shouldly and NSubstitute, with the repositories substituted | +| `tests/Infrastructure.Tests.Integration` | The repositories against a real database | A private in-memory SQLite database per test class, built from the migrations and the fixed seed | +| `tests/Presentation.Tests.Unit` | The error handler, settings, the OpenAPI transformer and the JSON options | Plain unit tests, no host | +| `tests/Presentation.Tests.Integration` | Real HTTP requests to every controller action, authentication, health checks, OpenAPI and telemetry | `WebApplicationFactory` with a private database | + +Rule of thumb: test a rule where it lives. A validation rule gets a validator test, a query's filter gets a repository test, and a status code or a Problem Details body gets an integration test. + +## What each kind of code needs + +Every test has `// Arrange`, `// Act` and `// Assert` sections, tests one behaviour, and is named `Method_Should_`, so a failure reads as a sentence. + +| Code | Prove it works | Prove it says no | +| --- | --- | --- | +| Validator | A valid request has no errors, and each boundary value passes | One failing case per rule, asserting the message, and each value just outside a boundary | +| Handler | The happy path returns the result and passes the values and the cancellation token to the repository | Each documented failure throws the right exception, and nothing after it runs | +| Repository | The right rows come back or change, in order, with paging and search applied | Unknown ids return `null` or `false`, and nothing else changes | +| Controller action | One test per documented success | One test per documented failure, with the error checked | + +For ranges, test both sides of each edge: stars `1` and `5` pass, `0` and `6` fail. A `[Theory]` with `[InlineData]` keeps that to one test. + +```csharp +// tests/Application.Tests.Unit/Reviews/Commands/CreateReview/CreateReviewValidatorTests.cs +[Theory] +[InlineData(-1)] +[InlineData(0)] +[InlineData(6)] +public void Validator_ShouldHaveValidationErrorFor_Stars(int stars) +``` + +## Run them + +```bash +dotnet test +``` + +No test shares a database with another, so they run in parallel and never trip over each other. Nothing touches `movie-reviews.db` either, so your local data survives a test run. + +## Check coverage before CI does + +CI merges the coverage of all four projects into one total and fails under 95%. Run the same measurement from the solution root: + +```bash +dotnet test -m:1 \ + -p:CollectCoverage=true -p:CoverletOutput=../results/ -p:MergeWith="../results/coverage.json" \ + -p:CoverletOutputFormat=opencover%2cjson \ + -p:ExcludeByFile="**/Program.cs%2c**/Migrations/*.cs%2c**/obj/**/*.cs" \ + -p:ExcludeByAttribute="GeneratedCodeAttribute" +``` + +The last table it prints is the merged total. `Program.cs`, the EF Core migrations and source-generated code are left out on purpose. Everything else counts. + + + +## Where to next + +- [Add an endpoint](/webapi/guides/add-an-endpoint/) ends with a test for the new action. +- [Work with a coding agent](/webapi/guides/work-with-a-coding-agent/): the `test-design`, `unit-tests`, `integration-tests` and `coverage` skills hold these same rules, so an agent writes tests the same way you would. diff --git a/src/content/docs/webapi/guides/work-with-a-coding-agent.mdx b/src/content/docs/webapi/guides/work-with-a-coding-agent.mdx new file mode 100644 index 0000000..ef86627 --- /dev/null +++ b/src/content/docs/webapi/guides/work-with-a-coding-agent.mdx @@ -0,0 +1,43 @@ +--- +title: "Work with a coding agent" +description: "What AGENTS.md, Jacques and the skills give your coding agent, and how to get the most out of them." +sidebar: + order: 6 +--- + +import { Aside } from '@astrojs/starlight/components'; + +Your solution ships with instructions for coding agents, the same ones we use to build the template. Point Claude Code, GitHub Copilot, Cursor, Codex or anything else that reads `AGENTS.md` at the solution, and it already knows where code goes, how it's tested and which checks must pass. You still decide what gets built. The agent just stops guessing how. + +## What's in the box + +| File | What it does | +| --- | --- | +| `AGENTS.md` | The map: what each project is for, the commands CI runs, the rules that fail a change, and a table of skills | +| `CLAUDE.md` | One line, `@AGENTS.md`, so Claude Code reads the same map | +| `.agents/agents/jacques.md` | Jacques, the sous-chef persona: does the prep, follows the recipes, proves every change and leaves the big calls to you | +| `.agents/skills/` | 32 skills, one folder each, covering every kind of change in the solution: use cases, validation, migrations, logging, telemetry, tests, coverage, Docker, CI and the controllers and OpenAPI | + +The skills are written for this solution, not for .NET in general. `add-a-use-case` shows the folder, the file names and the house style for a new command. `database-migrations` has the exact `dotnet ef` command. `coverage` has the same coverage command CI runs. Ask for something and the agent follows the recipe instead of improvising one. + +## Get started + +1. Open the solution in your agent of choice. Most read `AGENTS.md` without being asked. If yours doesn't, start with "Read AGENTS.md". +2. Ask for a change the way you'd ask a colleague: "Add a query that lists the reviews an author wrote, with paging, and put it on a controller." +3. For anything bigger than a few lines, Jacques agrees a plan first: the open decisions one at a time, each with a recommendation, then the scope in a paragraph. Answer those and the work starts. +4. Before it says it's done, the agent runs `dotnet build`, `dotnet test` and `dotnet format --verify-no-changes`, the same checks as CI. + +## Make it yours + +- **Where we track work.** `AGENTS.md` has a line that says "Not set". Replace it with your GitHub project, Jira board or Azure Boards, and agents put their plans and handoffs there instead of asking each time. +- **Change a rule, change the skill.** When your team decides to do something differently, update the matching `SKILL.md` in the same pull request. A skill that disagrees with the code is worse than no skill at all. +- **Add a skill for a new pattern.** Bring in something the solution doesn't do yet, such as a message queue, and add a skill for it while it's fresh. `writing-a-skill` has the shape. +- **Rename Jacques if you like.** He won't mind. He's a sous-chef, and he's used to it. + + + +## What the agent won't do + +`AGENTS.md` tells every agent never to push to `main` or merge a pull request, never to skip, delete or weaken a test or the coverage gate, never to suppress a warning to get a green build, never to add a package with a paid licence, and never to commit secrets. It stops and asks before changing the shared layers, adding a package, or changing anything a client depends on.