Skip to content
Open
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
77 changes: 77 additions & 0 deletions .ai/skills/add-attribute/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
name: add-attribute
description: Scaffold a new xUnit2 parameter attribute in the Core project. Use this skill when adding a new [AttributeName] that applies to all mock modules (AutoMoq, AutoFakeItEasy, AutoNSubstitute). Encodes the extension model, coding conventions, and test conventions for this repository. Invoke as: add-attribute AttributeName.
---

# add-attribute

Scaffold a new parameter attribute in `Core` following all repository conventions.

Parameter attributes apply to individual test method parameters and affect how AutoFixture
generates values for them. They derive from `CustomizeAttribute` (AutoFixture.Xunit2) and
override `GetCustomization`.

See `references/extension-model.md` for an annotated full example.

## Steps

**1. Understand the attribute's purpose**

- What constraint does it place on the generated value?
- Does it accept constructor parameters? What are their types and valid ranges?
- Review existing similar attributes for patterns:
- `[Except]` — excludes specific values
- `[PickFromRange(min, max)]` — constrains to a numeric range
- `[PickFromValues(v1, v2)]` — picks from an explicit list
- `[PickNegative]` — generates negative numbers only

**2. Create the attribute class**

Path: `src/Objectivity.AutoFixture.XUnit2.Core/Attributes/<Name>Attribute.cs`

Required conventions:

- `sealed class` deriving from `CustomizeAttribute` (decision-28)
- `using` directives go **inside** the namespace block (StyleCop SA1210)
- `global::` prefix on all external packages (AutoFixture, etc.)
- `[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]`
- `parameter.NotNull(nameof(parameter))` guard in `GetCustomization` (decision-29)
- No XML doc comments on public members (decision-27); use self-documenting names

**3. Create the test class**

Path: `src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/<Name>AttributeTests.cs`

Required structure:

- `[Collection("<Name>Attribute")]` — use the **subject** class name
- `[Trait("Category", "Attributes")]`
- BDD DisplayName on every `[Fact]` and `[Theory]` (decision-24)
- AAA structure with mandatory `// Arrange`, `// Act`, `// Assert` comments (decision-25)
- `[AutoMockData]` placed **above** `[Theory]`
- No `new Fixture()` — inject `IFixture` via test method parameters

Minimum test coverage:

- Constructor validation (invalid arguments throw expected exceptions)
- Property values are stored correctly
- `GetCustomization` returns a customization that causes AutoFixture to generate
values satisfying the constraint

**4. Update AGENTS.md**

Add a row to the "Parameter Attributes (Core, apply to all modules)" table:

```text
| `[<Name>]` | <short description of what it does> |
```

**5. Run validate**

Follow `.ai/skills/validate/SKILL.md` to confirm build and tests pass.

## Rules

- Add to `Core` only if the attribute applies to all three mock modules
- Never add Moq/FakeItEasy/NSubstitute-specific logic to `Core`
- Build must pass with 0 warnings before marking the task complete
123 changes: 123 additions & 0 deletions .ai/skills/add-attribute/references/extension-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Extension Model Reference — Parameter Attributes

This document walks through `PickFromRangeAttribute` as a canonical example of how parameter
attributes are implemented in this repository.

## Annotated source

```csharp
namespace Objectivity.AutoFixture.XUnit2.Core.Attributes
{
// ① using directives go INSIDE the namespace block (StyleCop SA1210)
using System;
using System.Reflection;

// ② global:: prefix on all external packages (decision — C# style)
using global::AutoFixture;
using global::AutoFixture.Kernel;
using global::AutoFixture.Xunit2;

using Objectivity.AutoFixture.XUnit2.Core.Common;
using Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders;

// ③ AttributeUsage restricts to Parameter; AllowMultiple = false is typical
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
// ④ sealed class (decision-28) — all attribute-derived types are sealed
// ⑤ derives from CustomizeAttribute (AutoFixture.Xunit2), not DataAttribute
public sealed class PickFromRangeAttribute : CustomizeAttribute
{
// Public constructor overloads for each supported numeric type
public PickFromRangeAttribute(int minimum, int maximum)
: this((object)minimum, maximum)
{
}

// ... additional overloads (uint, long, ulong, double, float) follow the same pattern

// Private canonical constructor validates the range
private PickFromRangeAttribute(object minimum, object maximum)
{
if (((IComparable)minimum).CompareTo((IComparable)maximum) > 0)
{
throw new ArgumentOutOfRangeException(
nameof(minimum),
$"Parameter {nameof(minimum)} must be lower or equal to {nameof(maximum)}.");
}

this.Minimum = minimum;
this.Maximum = maximum;
}

public object Minimum { get; }

public object Maximum { get; }

// ⑥ override GetCustomization — the single required method from CustomizeAttribute
public override ICustomization GetCustomization(ParameterInfo parameter)
{
// ⑦ parameter.NotNull(nameof(parameter)) — use NotNull() guard (decision-29)
// NotNull() is an extension method from Core/Common/
// It throws ArgumentNullException when parameter is null
return new FilteringSpecimenBuilder(
new RequestFactoryRelay(
(type) => new RangedNumberRequest(type, this.Minimum, this.Maximum)),
new EqualRequestSpecification(parameter.NotNull(nameof(parameter))))
.ToCustomization();
}
}
}
```

## Key points

| # | Rule | Where enforced |
| --- | --- | --- |
| ① | `using` inside namespace | StyleCop SA1210 (build fails if violated) |
| ② | `global::` prefix on external packages | StyleCop SA1135 / team convention |
| ③ | `[AttributeUsage(AttributeTargets.Parameter)]` | Restricts attribute usage to parameters |
| ④ | `sealed` | decision-28 |
| ⑤ | Derives from `CustomizeAttribute` | AutoFixture.Xunit2 integration contract |
| ⑦ | `NotNull()` guard | decision-29 |

## SpecimenBuilders used in data-narrowing attributes

| Class | Purpose |
| --- | --- |
| `FilteringSpecimenBuilder` | Wraps a builder and applies it only to matching requests |
| `RequestFactoryRelay` | Creates an AutoFixture request from the parameter type |
| `EqualRequestSpecification` | Matches requests equal to the given `ParameterInfo` |
| `RangedNumberRequest` | Built-in AutoFixture request for a number within a range |

## Minimal attribute skeleton

Use this as a starting point for a new attribute:

```csharp
namespace Objectivity.AutoFixture.XUnit2.Core.Attributes
{
using System;
using System.Reflection;

using global::AutoFixture;
using global::AutoFixture.Xunit2;

using Objectivity.AutoFixture.XUnit2.Core.Common;

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class MyNewAttribute : CustomizeAttribute
{
public MyNewAttribute(/* constructor parameters */)
{
// validate and store
}

public override ICustomization GetCustomization(ParameterInfo parameter)
{
parameter.NotNull(nameof(parameter));

// return an ICustomization that applies the constraint
throw new NotImplementedException();
}
}
}
```
78 changes: 78 additions & 0 deletions .ai/skills/create-branch-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
name: create-branch-pr
description: Create a feature branch and open a pull request following repository conventions. Validates branch naming, prefills the PR description from the project template, and walks through the full PR checklist. Use when ready to publish changes. Invoke as: create-branch-pr [TASK-N].
---

# create-branch-pr

Create a properly named branch and open a PR with the standard description template.

See `assets/pr-template.md` for the full prefilled PR description.

## Steps

**1. Confirm the branch name**

Format: `<type>/<kebab-description>`

The type must match the Conventional Commits type of the primary commit:

| Type | Use when |
| --- | --- |
| `feat/` | new attribute or feature |
| `fix/` | bug fix |
| `refactor/` | code restructuring without behaviour change |
| `chore/` | maintenance, tooling, dependency updates |
| `ci/` | CI/CD workflow changes |
| `docs/` | documentation only |

Examples: `feat/pick-from-list-attribute`, `fix/enumerable-allocation`, `docs/add-skills`

**2. Create and push the branch**

```bash
git checkout -b <branch-name>
git push -u origin <branch-name>
```

**3. Walk through the PR checklist**

Confirm each item before opening the PR:

- [ ] Commit messages follow Conventional Commits (`type(scope): description`)
- [ ] `dotnet build src/Objectivity.AutoFixture.XUnit2.AutoMock.sln` passes with no warnings
- [ ] `dotnet test src/Objectivity.AutoFixture.XUnit2.AutoMock.sln` passes on all framework slices
- [ ] Code coverage is not degraded (Codecov verifies on CI)
- [ ] Mutation score is not degraded (Stryker verifies on CI)
- [ ] New tests follow GIVEN/WHEN/THEN naming and AAA structure
- [ ] No new `[SuppressMessage]` without a justification comment
- [ ] No `// TODO:` comments added — open a GitHub issue instead
- [ ] No new dependencies incompatible with the MIT license

**4. Open the PR**

Use the prefilled template from `assets/pr-template.md`:

```bash
gh pr create \
--title "<type>(scope): <description>" \
--body "$(cat .ai/skills/create-branch-pr/assets/pr-template.md)"
```

In the PR body:

- Replace `Closes #` with the actual GitHub issue number if applicable
- Add a label to the PR after creation (`gh pr edit <number> --add-label "<label>"`)
- The `@coderabbitai summary` placeholder will be filled automatically by CodeRabbit on review

**5. Link the backlog task** (if a TASK-N was provided)

```bash
npx backlog task edit TASK-N --status "Done"
```

## Rules

- Direct pushes to `master` are not allowed — always use a feature branch
- All checklist items must be confirmed before opening the PR
- The PR title must follow Conventional Commits format
21 changes: 21 additions & 0 deletions .ai/skills/create-branch-pr/assets/pr-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Summary

<!-- Placeholder in the PR description that CodeRabbit replaces with the high-level summary. -->
@coderabbitai summary

Closes #

- Please add a description of the issue this PR is addressing. Link the relevant issue if applicable.
- Add a label to the PR.

## Checklist

- [ ] Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) (`type(scope): description`)
- [ ] `dotnet build src/Objectivity.AutoFixture.XUnit2.AutoMock.sln` passes with no warnings
- [ ] `dotnet test src/Objectivity.AutoFixture.XUnit2.AutoMock.sln` passes on all framework slices
- [ ] Code coverage remains at least at the level prior the change (verified by Codecov)
- [ ] Mutation score remains at least at the level prior the change (verified by Stryker)
- [ ] New tests follow the GIVEN/WHEN/THEN naming convention and AAA structure (see [AGENTS.md](../AGENTS.md))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a repo-root link for AGENTS.md in the PR template.

Line 18 uses ../AGENTS.md, which is brittle when rendered in PR descriptions. Use /AGENTS.md (or full GitHub URL) to avoid broken links.

Suggested patch
-- [ ] New tests follow the GIVEN/WHEN/THEN naming convention and AAA structure (see [AGENTS.md](../AGENTS.md))
+- [ ] New tests follow the GIVEN/WHEN/THEN naming convention and AAA structure (see [AGENTS.md](/AGENTS.md))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- [ ] New tests follow the GIVEN/WHEN/THEN naming convention and AAA structure (see [AGENTS.md](../AGENTS.md))
- [ ] New tests follow the GIVEN/WHEN/THEN naming convention and AAA structure (see [AGENTS.md](/AGENTS.md))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.ai/skills/create-branch-pr/assets/pr-template.md at line 18, The PR
template checklist uses a relative link "../AGENTS.md" which can break when
rendered; open the PR template entry that contains the checklist line
referencing AGENTS.md (the line reading "New tests follow the GIVEN/WHEN/THEN
naming convention and AAA structure (see [AGENTS.md](../AGENTS.md))") and
replace the link target with a repo-root link "/AGENTS.md" (or a full GitHub
URL) so the AGENTS.md link reliably resolves in PR descriptions.

- [ ] No new `[SuppressMessage]` without a justification comment
- [ ] No `// TODO:` comments added - open a GitHub issue instead
- [ ] No new dependencies introduced that are incompatible with the MIT license (verified by FOSSA)
86 changes: 86 additions & 0 deletions .ai/skills/new-test/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
name: new-test
description: Write a new test class for a given subject type following repository conventions. Use when adding tests for a new or existing class. Encodes BDD naming (decision-24) and AAA structure (decision-25). Invoke as: new-test SubjectClass.
---

# new-test

Scaffold a properly structured test class for a given subject.

See `assets/test-template.md` for a complete scaffolded class ready to copy and fill in.

## Steps

**1. Name the test class and file**

- File: `<SubjectClass>Tests.cs`
- Class: `<SubjectClass>Tests`
- Namespace: mirrors the source project namespace under `.Tests`
- Source: `Objectivity.AutoFixture.XUnit2.Core.Attributes`
- Test: `Objectivity.AutoFixture.XUnit2.Core.Tests.Attributes`
- Location: `src/<Module>.Tests/<same-folder-path-as-source>/`

**2. Apply required class attributes**

```csharp
[Collection("<SubjectClass>")] // subject class name — NOT the test class name
[Trait("Category", "<Area>")] // e.g. "Attributes", "AutoData", "Core"
public class <SubjectClass>Tests
```

**3. Name test methods using BDD convention** (decision-24)

For `[Theory]` (multiple data rows, precondition varies):

- DisplayName: `GIVEN <precondition> WHEN <action> THEN <outcome>`
- Method name: `Given<Precondition>_When<Action>_Then<Outcome>`

For `[Fact]` (single deterministic case):

- DisplayName: `WHEN <action> THEN <outcome>`
- Method name: `When<Action>_Then<Outcome>`

Rules:

- `DisplayName` is **always** set; never omit it
- GIVEN, WHEN, THEN are written in UPPER CASE; the rest is sentence case
- Method name uses PascalCase with underscores between the three BDD sections
- Never use `Test` as a suffix or prefix in method names

**4. Structure every test with AAA** (decision-25)

```csharp
[Fact(DisplayName = "WHEN X THEN Y")]
public void WhenX_ThenY()
{
// Arrange

// Act

// Assert
}
```

All three comment blocks are mandatory even when a section is empty.

**5. Place data-source attributes above `[Theory]`**

```csharp
[AutoMockData]
[Theory(DisplayName = "GIVEN X WHEN Y THEN Z")]
public void GivenX_WhenY_ThenZ(IFixture fixture) { ... }
```

The data-source attribute must appear **above** `[Theory]`, never below.

**6. Inject test data via parameters**

- Never use `new Fixture()` in test methods
- Inject `IFixture` via the test method signature
- For attribute wiring tests: use `Mock<IFixture>` and `Mock<IAutoFixtureAttributeProvider>`

## Rules

- Every `[Fact]` and `[Theory]` must have `DisplayName` set
- `[Collection]` must use the **subject** class name, not the test class name
- Data-source attributes go **above** `[Theory]`
Loading
Loading